在C程序中发送ICMP数据包

我正在尝试在C中创建一个ICMP ping测试程序,但是在成功发送数据包时遇到了困难。 sendto函数返回字节数和所有内容但实际上没有发送数据包。 我已经在目标计算机上使用WireSharkvalidation了这一点。 主机上的常规ping工作正常,但在WireShark中显示。

这是我的代码:

 #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  unsigned short cksum(unsigned short *addr, int len); int main(int argc, char *argv[]) { int sock; char send_buf[400], recv_buf[400], src_name[256], src_ip[15], dst_ip[15]; struct ip *ip = (struct ip *)send_buf; struct icmp *icmp = (struct icmp *)(ip + 1); struct hostent *src_hp, *dst_hp; struct sockaddr_in src, dst; struct timeval t; int on; int num = 10; int failed_count = 0; int bytes_sent, bytes_recv; int dst_addr_len; int result; fd_set socks; /* Initialize variables */ on = 1; memset(send_buf, 0, sizeof(send_buf)); /* Check for valid args */ if(argc < 2) { printf("\nUsage: %s \n", argv[0]); printf("- dst_server is the target\n"); exit(EXIT_FAILURE); } /* Check for root access */ if (getuid() != 0) { fprintf(stderr, "%s: This program requires root privileges!\n", argv[0]); exit(EXIT_FAILURE); } /* Get source IP address */ if(gethostname(src_name, sizeof(src_name)) ip_src = (*(struct in_addr *)src_hp->h_addr); } /* Get destination IP address */ if((dst_hp = gethostbyname(argv[1])) == NULL) { if((ip->ip_dst.s_addr = inet_addr(argv[1])) == -1) { fprintf(stderr, "%s: Can't resolve, unknown destination.\n", argv[1]); exit(EXIT_FAILURE); } } else { ip->ip_dst = (*(struct in_addr *)dst_hp->h_addr); dst.sin_addr = (*(struct in_addr *)dst_hp->h_addr); } sprintf(src_ip, "%s", inet_ntoa(ip->ip_src)); sprintf(dst_ip, "%s", inet_ntoa(ip->ip_dst)); printf("Source IP: '%s' -- Destination IP: '%s'\n", src_ip, dst_ip); /* Create RAW socket */ if((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) { perror("socket() error"); /* If something wrong, just exit */ exit(EXIT_FAILURE); } /* Socket options, tell the kernel we provide the IP structure */ if(setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) ip_v = 4; ip->ip_hl = 5; ip->ip_tos = 0; ip->ip_len = htons(sizeof(send_buf)); ip->ip_id = htons(321); ip->ip_off = htons(0); ip->ip_ttl = 255; ip->ip_p = IPPROTO_ICMP; ip->ip_sum = 0; /* ICMP structure, check ip_icmp.h */ icmp->icmp_type = ICMP_ECHO; icmp->icmp_code = 0; icmp->icmp_id = 123; icmp->icmp_seq = 0; /* Set up destination address family */ dst.sin_family = AF_INET; /* Loop based on the packet number */ for(int i = 1; i icmp_cksum = 0; ip->ip_sum = cksum((unsigned short *)send_buf, ip->ip_hl); icmp->icmp_cksum = cksum((unsigned short *)icmp, sizeof(send_buf) - sizeof(struct icmp)); /* Get destination address length */ dst_addr_len = sizeof(dst); /* Set listening timeout */ t.tv_sec = 5; t.tv_usec = 0; /* Set socket listening descriptors */ FD_ZERO(&socks); FD_SET(sock, &socks); /* Send packet */ if((bytes_sent = sendto(sock, send_buf, sizeof(send_buf), 0, (struct sockaddr *)&dst, dst_addr_len)) < 0) { perror("sendto() error"); failed_count++; printf("Failed to send packet.\n"); fflush(stdout); } else { printf("Sent %d byte packet... ", bytes_sent); fflush(stdout); /* Listen for the response or timeout */ if((result = select(sock + 1, &socks, NULL, NULL, &t))  0) { printf("Waiting for packet... "); fflush(stdout); if((bytes_recv = recvfrom(sock, recv_buf, sizeof(ip) + sizeof(icmp) + sizeof(recv_buf), 0, (struct sockaddr *)&dst, (socklen_t *)&dst_addr_len)) icmp_seq++; } } /* Display success rate */ printf("Ping test completed with %d%% success rate.\n", (((num - failed_count) / num) * 100)); /* close socket */ close(sock); return 0; } /* One's Complement checksum algorithm */ unsigned short cksum(unsigned short *addr, int len) { int nleft = len; int sum = 0; unsigned short *w = addr; unsigned short answer = 0; while (nleft > 1) { sum += *w++; nleft -= 2; } if (nleft == 1) { *(unsigned char *)(&answer) = *(unsigned char *)w; sum += answer; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); answer = ~sum; return (answer); } 

对我做错了什么的想法? 校验和还好吗? 两者都使用0可以吗?

编辑:好的,所以我设法得到正确发送的数据包,感谢下面的人! 目标计算机将发回回应答复。 但是,现在,我的程序没有收到答复。 select()函数总是超时。 我已经更新了上面的代码。

编辑2:好吧,我已经开始工作了! 我需要将套接字协议设置为IPPROTO_ICMP而不是IPPROTO_RAW ,它工作得很好! 再次感谢你们的评论! 真帮了! 看起来我只能将一个答案标记为正确,但你们都帮助解决这个问题。 🙂

只有一点我注意到了……

你有这个:

  struct ip *ip = (struct ip *)send_buf; 

然后,您要分配目标字段:

 ip->ip_dst = (*(struct in_addr *)dst_hp->h_addr) 

然后你用memset擦除它(因为send_buff指向同一个东西):

 memset(send_buf, 0, sizeof(send_buf)); 

所以,当你想在这里获得ip_dst:

 dst.sin_addr = ip->ip_dst; 

你得到0而不是你之前设置的。

乍一看:在select()之后你不能依赖时间结构。 你还应该设置usec。

所以在代码中,在for循环中包含t值的规范:

 for (i = 1; i <= num; i++) { t.tv_sec = 5; t.tv_usec = 0; ... 

另外,当你进行第二次迭代时(可以)改变了。

在您的sprintf(src_ip,...)和dst_ip中,您省略了格式。

除了ebutusov的回复:

 ip->ip_sum = 0; icmp->icmp_cksum = htons(~(ICMP_ECHO << 8)); 

都不正确。
您需要正确计算校验和(两者的算法相同,但涵盖不同的字段)。