【问题标题】:C sockets send UDP and process ICMP reply from routerC 套接字发送 UDP 并处理来自路由器的 ICMP 回复
【发布时间】:2014-04-16 19:09:41
【问题描述】:

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

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>

#define UNSPEC_PROTO 0

int main(int argc, const char *argv[])
{ 
if (argc != 2) {
    printf("usage: routetracer <ip address or hostname>\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)) < 0){
    fprintf(stderr, "Error creating recv socket: %s\n", strerror(errno));
}

/*
 * We go from hop to hop by incrementing the time to live in the IP header
 * for each hop we visit until we reach the destination IP address (which we
 * already have). Time to live decrements for every hop, so once it reaches
 * zero we report the IP address of the node we are connected to.
 */

//while(test_ip != dest_ip)
//time_to_live++
//send_to(dest_addr)
//receive icmp error message
//get src addr of error msg from ip header of icmp
//test_ip = src addr
/*
while (last_hop == 0) {
    ttl++;
    setsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
    sendto(sock, msg, strlen(msg), 0, (struct sockaddr *)ip, sizeof(ip));
}
*/

ttl = 1;
if (!(setsockopt(src_sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)))) {
    printf("TTL set successfully\n");
} else {
    printf("Error setting TTL: %s\n", strerror(errno));
}

if ((sendto(src_sock, msg, strlen(msg), 0, ret->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;
}

【问题讨论】:

    标签: c sockets icmp ttl recvfrom


    【解决方案1】:

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

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

    【讨论】:

    • 该解决方案仅适用于 Linux 吗?我有一台 Linux 机器,我可以在它上面运行,但我也希望它在我的 Mac 上运行。
    • 您可以检查是否支持 IP_RECVERR,方法是检查该宏是否由标头定义 -- #include &lt;netinet/in.h&gt;..#ifdef IP_RECVERR。如果未定义,则不受支持。
    【解决方案2】:

    在某些套接字实现中,必须连接 UDP 套接字才能接收错误。

    所以,你需要添加connect调用,然后使用send/recv函数。

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

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

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

    【讨论】:

      【解决方案3】:

      在打开套接字时检查选项。

      How to sniff all ICMP packets using RAW sockets

      How to receive ICMP request in C with raw sockets

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

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

      Blocking recvfrom with select system call.

      Unexepcted results with select and recvfrom.

      【讨论】:

      • 感谢您提供的链接让我找到了答案。
      【解决方案4】:

      首先,您的代码具有未定义的行为,因为 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);
      

      【讨论】:

      • 为什么不首先将reply_addr 设为struct sockaddr*?这样我就不需要将它转换为指针类型。将其声明为struct sockaddr_in reply_addr; 不仅意味着它也未初始化吗?另外,我有什么办法可以绕过需要 root 才能打开原始套接字?
      • 1) sockaddr 是通用套接字地址结构,而sockaddr_in 是互联网地址 (AF_INET) 结构。由于这就是您正在使用的,因此在这里使用它是有意义的,尽管不是强制性的。 2) 在 Linux 中,您可以将程序的功能设置为 root,使用:setcap cap_net_raw=+ep &lt;program-name&gt;,以便您以后可以以非 root 身份运行它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多