C套接字发送UDP并处理来自路由器的ICMP应答

我正在尝试将UDP数据包发送到路由器,其生存时间为1,然后接收ICMP超时回复。 到目前为止,我能够发送数据包,但是当我的程序进入执行的recv部分时,它就会挂起。 我有一个错误检查recvfrom,但它甚至没有达到。 我的电脑正在接收请求。 我知道这是因为我在运行程序时运行Wireshark并且我过滤了ICMP请求。 每次我运行程序时,都会收到回复。 我在recvfrom上做错了什么?

#include  #include  #include  #include  #include  #include  #include  #include  #define UNSPEC_PROTO 0 int main(int argc, const char *argv[]) { if (argc != 2) { printf("usage: routetracer \n"); return -1; } struct addrinfo hints; //params for ret val of getaddrinfo struct addrinfo* ret; //return value of getaddrinfo struct sockaddr* reply_addr; char ipv4[INET_ADDRSTRLEN]; char* msg = "THE PORT IS OVER 9000!!!!"; int status = 0; int ttl = 0; int src_sock = 0; int recv_sock = 0; socklen_t reply_addr_len = sizeof(struct sockaddr); const char* dest_port = "9001"; int icmp_msg_len = 100; char icmp_msg[icmp_msg_len]; //define what we want from getaddrinfo memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; //IPv4 hints.ai_socktype = SOCK_DGRAM; //UDP packets //call getaddrinfo to fill ret, w/ error chk if ((status = getaddrinfo(argv[1], dest_port, &hints, &ret)) != 0) { printf("getaddrinfo: %s\n", gai_strerror(status)); return -1; } //extract IPv4 address from ret struct sockaddr_in* ip = (struct sockaddr_in *)ret->ai_addr; //convert address from pure numbers to something easier to read inet_ntop(ret->ai_family, &(ip->sin_addr), ipv4, INET_ADDRSTRLEN); //kindly inform the user of which hostname they are connecting to printf("Route for: %s\n", ipv4); //create a socket for our machine if ((src_sock = socket(ret->ai_family, ret->ai_socktype, ret->ai_protocol)) < 0) { fprintf(stderr, "Error creating host socket: %s\n", strerror(errno)); return -1; } //create a socket to recv icmp packet from hops if ((recv_sock = socket(AF_INET, SOCK_DGRAM, UNSPEC_PROTO)) ai_addr, ret->ai_addrlen)) > 0) { printf("msg sent successfully\n"); } else { fprintf(stderr, "Error sending msg: %s\n", strerror(errno)); } if ((recvfrom(recv_sock, icmp_msg, icmp_msg_len, 0, reply_addr, &reply_addr_len)) != -1) { /* PROCESS THE INFORMATION */ printf("Packet received\n"); } else { fprintf(stderr, "Error receiving packet: %s\n", strerror(errno)); } return 0; } 

通常,UDP几乎忽略了ICMP错误,因此如果要查看它们,则需要打开原始套接字以接收所有 ICMP数据包并查找与套接字相关的数据包。

至少在Linux上,另一种方法是设置IP_RECVERR套接字选项。 如果这样做,您可以执行recvmsg并设置MSG_ERRQUEUE标志以获取与套接字关联的任何ICMP(或其他)错误。 这样做的好处是不需要提升权限或第二个套接字。

打开sockets时检查选项。

请参见如何使用RAW套接字嗅探所有ICMP数据包 。

请参见如何使用原始套接字在C中接收ICMP请求 。

您可能还希望将套接字选项更改为非阻塞,并使用select()函数来确定是否有要读取的内容。

有关使用select()函数的示例,请参阅以下内容。

使用select系统调用阻止recvfrom 。

select和recvfrom的结果不正确 。

在套接字的一些实现中,必须连接UDP套接字以接收错误。

因此,您需要添加connect调用,然后使用send / recv函数。

我在FreeBSD上证实了这一点。 至少有一个消息来源明确指出:

http://www.softlab.ntua.gr/facilities/documentation/unix/unix-socket-faq/unix-socket-faq-5.html (参见5.3。执行connect()调用会影响套接字的接收行为吗? )

PS请注意,您不会以这种方式收到确切的ICMP错误消息。 你只会得到一些错误代码,没有很多细节(如果有的话)。

首先,您的代码具有未定义的行为,因为reply_addr未初始化。 你应该先解决这个问题:

 struct sockaddr_in reply_addr; 

…然后:

 recvfrom(recv_sock, icmp_msg, icmp_msg_len, 0, (struct sockaddr*)&reply_addr, &reply_addr_len); 

最后,您需要使用原始套接字而不是数据报套接字来接收ICMP数据包:

 recv_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);