LINUX.ORG.RU

трассировка icmp

 ,


0

1

Привет всем. Никак не могу понять как правильно написать. Программа использует icmp протокол. Например я пингую хост, в настройках ip устанавливаю ttl++. В wireshark пишет некоректная чексумма, она такая, а должна быть такая. А как мне известно, что icmp с неправильной чексуммой не дождёться ответа, но, маршрут проходит до хоста, но последний хост в моей программе не отображаеться, хоть я могу сам дописать, но может что-то в чек сумме? Вот код.

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <errno.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <strings.h>
#include <sys/time.h>

unsigned short checksum(unsigned short *, int);

int main(int argc, char *argv[]){

	int sockfd;
	struct sockaddr_in dst;
	struct sockaddr_in from;
	struct hostent *ht;
	struct icmp *icmp;
	struct ip *ip;
	pid_t pid = getpid();
	static int icmplen = 64;
	char sendbuf[1500];
	char readbuf[1500];
	int ttl;
	int seq = 0;
	int len;

	if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1){
		perror("socket() failed");
		exit(-1);
	}

	if (setuid(getuid()) == -1)
		perror("setuid() failed");

	if ((ht = gethostbyname(argv[1])) == NULL){
		herror("gethostbyname() failed");
		exit(-1);
	}

	dst.sin_family = AF_INET;
	dst.sin_port = IPPROTO_ICMP;
	dst.sin_addr = *((struct in_addr *)ht->h_addr);
	

	for(ttl = 1; ttl <= 64; ttl++){
	  if(setsockopt(sockfd, SOL_IP, IP_TTL,&ttl,sizeof(ttl)) == -1){
  	  perror("setsockopt() failed");
   		exit(-1);
  	}

		bzero(&sendbuf, 1500);
		bzero(&readbuf, 1500);
		icmp = (struct icmp *) sendbuf;
		icmp->icmp_type = ICMP_ECHO;
		icmp->icmp_code = 0;
		icmp->icmp_id = pid;
		icmp->icmp_seq = seq;
		icmp->icmp_cksum = 0;
		icmp->icmp_cksum = checksum((unsigned short *)icmp, icmplen);

		if(sendto(	sockfd,
								sendbuf, 
								icmplen, 
								0, 
								(struct sockaddr *)&dst,
								sizeof(dst)) == -1){
			perror("sendto failed");
			exit(-1);
		}

		unsigned int fromlen = sizeof(from);
		if((len =recvfrom(sockfd,
								readbuf,
								icmplen, 
								0,
								(struct sockaddr *)&from,
								&fromlen)) == -1){
			perror("recvfrom() failed");
			exit(-1);
		}

		char *ptr = &readbuf[0];
		int iplen;
		ip = (struct ip *) ptr;
		iplen = ip->ip_hl << 2;

		icmp = (struct icmp *) (ptr + iplen);

		if(icmp->icmp_type == ICMP_TIME_EXCEEDED)
			printf(	"%s ttl=%d len=%d icmplen=%d\n", 
							inet_ntoa(from.sin_addr),ttl, len,icmplen);
		else if(icmp->icmp_type == ICMP_ECHOREPLY){
			printf("%s\n", inet_ntoa(from.sin_addr));
			exit(0);
		}
		//seq++;
	}
	
}

unsigned short checksum(unsigned short *addr, int count){
	register long sum;
//	unsigned short sum;
	unsigned short icmpsum;

	while(count > 1){
		sum += (unsigned short) *addr++;
		count -= 2;
	}

	if(count == 1)
		sum += *(unsigned char *) addr;

	while(sum >> 16)
		sum = (sum & 0xffff) + (sum >> 16);

	icmpsum = ~sum;
	return icmpsum;
}

		bzero(&sendbuf, 1500);
		bzero(&readbuf, 1500);

Не нужно: лучше явно присвоить значения всем полям в пакете. А для чтения тем более не нужно.

		icmp->icmp_cksum = 0;
		icmp->icmp_cksum = checksum((unsigned short *)icmp, icmplen);

Контрольную сумму от нуля считать? Лучше уточнить, для каких именно байтов пакета нужно считать.

gag ★★★★★
()
Ответ на: комментарий от gag

Так как правильно написать? Например я читал, что перед тем как высчитывать сумму для icmp, нужно icmp_cksum обнулить, после вычислить сумму icmp и присвоить icmp_cksum.

u0atgKIRznY5
() автор топика
Ответ на: комментарий от u0atgKIRznY5

Эх, не тому хосту отправлял эхо. Проверил через mtr, там перед хостом не показывает адрес, а после нужный хост. Проверил оправкой на другой хост, всё работает, но всё же, получилось как ping, а мне нужно отлавливать именно сообщения о том, что ttl icmp пакета не хватает.

u0atgKIRznY5
() автор топика
Ответ на: комментарий от u0atgKIRznY5

Это же отлавливать ttl ICMP_TIMXCEED_INTRANS 0 ttl==0 in transit Что-то неправильно делаю, как мне вот это использовать ? /* Codes for TIME_EXCEEDED. */

Я понел что хоть даже я сделаю проверку ICMP_TIMXCEED_INTRANS, то всё равно будет ICMP_ECHOREPLY 0. Новый код

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <errno.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <strings.h>
#include <sys/time.h>
#define BUFSIZE 8
unsigned short checksum(unsigned short *, int);

int main(int argc, char *argv[]){

	int sockfd;
	struct sockaddr_in dst;
	struct sockaddr_in from;
	struct hostent *ht;
	struct icmp *icmp;
	struct ip *ip;
	pid_t pid = getpid();
	static int icmplen = 8;
	char sendbuf[BUFSIZE];
	char readbuf[BUFSIZE];
	bzero(&sendbuf,BUFSIZE);
	bzero(&readbuf,BUFSIZE);
	int ttl;
	int seq = 0;
	int len;

	if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1){
		perror("socket() failed");
		exit(-1);
	}

	if (setuid(getuid()) == -1)
		perror("setuid() failed");

	if ((ht = gethostbyname(argv[1])) == NULL){
		herror("gethostbyname() failed");
		exit(-1);
	}

	dst.sin_family = AF_INET;
	dst.sin_port = IPPROTO_ICMP;
	dst.sin_addr = *((struct in_addr *)ht->h_addr);
	
	for(ttl = 2; ttl <= 30; ttl++){
	  if(setsockopt(sockfd, SOL_IP, IP_TTL,&ttl,sizeof(ttl)) == -1){
  	  perror("setsockopt() failed");
   		exit(-1);
  	}

		icmp = (struct icmp *) sendbuf;
		icmp->icmp_type = ICMP_ECHO;
		icmp->icmp_code = 0;
		icmp->icmp_id = pid;
		icmp->icmp_seq = seq;
		icmp->icmp_cksum = 0;
		icmp->icmp_cksum = checksum((unsigned short *)icmp, icmplen);
		if(sendto(	sockfd,
								sendbuf, 
								icmplen, 
								0, 
								(struct sockaddr *)&dst,
								sizeof(dst)) == -1){
			perror("sendto failed");
			exit(-1);
		}

		unsigned int fromlen = sizeof(from);
		if((len =recvfrom(sockfd,
								readbuf,
								icmplen, 
								0,
								(struct sockaddr *)&from,
								&fromlen)) == -1){
			perror("recvfrom() failed");
			exit(-1);
		}

		char *ptr = &readbuf[0];
		int iplen;
		ip = (struct ip *) ptr;
		iplen = ip->ip_hl << 2;

		icmp = (struct icmp *) (ptr + iplen);

			if(icmp->icmp_type == 0)
			printf(	"%s\n", inet_ntoa(from.sin_addr));
		
	seq++;
	}
	
}

unsigned short checksum(unsigned short *addr, int count){
	register long sum;
	unsigned short icmpsum;

	while(count > 1){
		sum +=  (unsigned short) *(addr)++;
		count -= 2;
	}

	if(count == 1)
		sum += *(unsigned char *) addr;

	while(sum >> 32)
		sum = (sum & 0xffff) + (sum >> 32);
	

	icmpsum = ~sum;
	return icmpsum;
}

u0atgKIRznY5
() автор топика
Ответ на: комментарий от u0atgKIRznY5

Сделал

Понел, что как, буфер массива можно оставить как mtu, а размер icmp 64, делать хеш сумму только для 8 бит icmp. И тогда посылая icmp->icmp_type ICMP_ECHO, можно получить icmp->icmp_type ICMP_TIME_EXCEEDED, если время жизни пакета ttl ( time to live) не хватило, а если пришёл icmp->icmp_type ICMP_ECHOREPLY, значит нужный узел уже доступен с нужным ttl. Вот код

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <errno.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <strings.h>
#include <sys/time.h>

#define BUFSIZE 1500

unsigned short checksum(unsigned short *, int);

int main(int argc, char *argv[]){

	int sockfd;
	struct sockaddr_in dst;
	struct sockaddr_in from;
	struct hostent *ht;
	struct icmp *icmp;
	struct ip *ip;
	pid_t pid = getpid();
	static int icmplen = 64;
	char sendbuf[BUFSIZE];
	char readbuf[BUFSIZE];
	int ttl;
	int seq = 0;
	int len;

	if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1){
		perror("socket() failed");
		exit(-1);
	}

	if (setuid(getuid()) == -1)
		perror("setuid() failed");

	if ((ht = gethostbyname(argv[1])) == NULL){
		herror("gethostbyname() failed");
		exit(-1);
	}

	dst.sin_family = AF_INET;
	dst.sin_port = IPPROTO_ICMP;
	dst.sin_addr = *((struct in_addr *)ht->h_addr);

	for(ttl = 1; ttl <= 30;ttl++){

	  if(setsockopt(sockfd, SOL_IP, IP_TTL,&ttl,sizeof(ttl)) == -1){
  	  perror("setsockopt() failed");
   		exit(-1);
  	}
		
		icmp = (struct icmp *) &sendbuf[0];
		icmp->icmp_type = ICMP_ECHO;
		icmp->icmp_id = pid;
		icmp->icmp_seq = seq;
		icmp->icmp_cksum = 0;
		icmp->icmp_cksum = checksum((unsigned short *)icmp, 8);

		if(sendto(	sockfd,
								sendbuf, 
								icmplen, 
								0, 
								(struct sockaddr *)&dst,
								sizeof(dst)) == -1){
			perror("sendto failed");
			exit(-1);
		}

		unsigned int fromlen = sizeof(from);
		if((len =recvfrom(sockfd,
								readbuf,
								icmplen, 
								0,
								(struct sockaddr *)&from,
								&fromlen)) == -1){
			perror("recvfrom() failed");
			exit(-1);
		}

		char *ptr = &readbuf[0];
		int iplen;
		ip = (struct ip *) ptr;
		iplen = ip->ip_hl << 2;

		icmp = (struct icmp *) (ptr + iplen);

		if(icmp->icmp_type == ICMP_TIME_EXCEEDED)
			printf("%s\n",inet_ntoa(from.sin_addr));
		else if(icmp->icmp_type == ICMP_ECHOREPLY){
			printf(	"%s\n", inet_ntoa(from.sin_addr));
			exit(0);
		}
		
	}
	
}

unsigned short checksum(unsigned short *addr, int count){
	register long sum;
	unsigned short icmpsum;

	while(count > 1){
		sum +=  (unsigned short) *(addr)++;
		count -= 2;
	}

	if(count == 1)
		sum += *(unsigned char *) addr;

	while(sum >> 16)
		sum = (sum & 0xffff) + (sum >> 16);
	

	icmpsum = ~sum;
	return icmpsum;
}

u0atgKIRznY5
() автор топика
Ответ на: Сделал от u0atgKIRznY5

Понел, что как, буфер массива можно оставить как mtu, а размер icmp 64, делать хеш сумму только для 8 бит icmp.

Длина ICMP - 64 байта, и глянув в Wireshark, выходит, что и чексумму надо брать от всех 64-ёх (предварительно обнулив icmp_cksum поле, как ты и сделал). А почему оно работает, так это потому что остальная часть - нули, которые не влияют на сумму.

Кстати, ты инициализируешь только 8 байт ICMP-пакета, значит надо явно обнулить остальные 56.

gag ★★★★★
()
Ответ на: комментарий от gag

Используя в icmp->icmp_data неменяемые данные, и не увеличивая seq. ECHO доходит до адресата при чек сумме в 64. Используя gettimeofday для переменной не относящейся к icmp, sendto выдаёт perror sendto failed: Address family not supported by protocol Хотя причём здесь sendto.

    gettimeofday((struct timeval *)&fest,NULL);
    //gettimeofday((struct timeval *)icmp->icmp_data,NULL);
При 64, при ttl 1 получается правильная контрольная сумма, а дальше нет. Я не знаю как правильно написать, как же сделать?

u0atgKIRznY5
() автор топика
Ответ на: комментарий от gag

Да и gettimeofday(даёт одно и тоже число, странно) пока ttl увеличивается. Если ttl хватает изначально до узла, то проверка контрольной суммы в 64, и использование gettimeofday для icmp->icmp_data успешно завершиться, но если ttl нехватает, то последующие попытки с gettimeofday и проверки контрольной суммы в 64 не завершаются успехом. Почему?

u0atgKIRznY5
() автор топика

Всё сделал

Оказывается, перед применением вычисления контрольной суммы, нужно только один раз обнулить icmp->icmp_cksum, тоесть при ttl == 1.

    if(ttl == 1)
    icmp->icmp_cksum = 0;
    icmp->icmp_cksum = checksum((unsigned short *)icmp, 64);
Значит контрольная сумма вычисляется в последующие разы с имеющейся контрольной суммой.

u0atgKIRznY5
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.