关于套接字:C – recvfrom意外的结果

C - result of recvfrom unexpected

我有一个简单的代码来发送和接收像ping这样的ICMP数据包,一切正常:我的数据包被发送,我收到了一个结果。
我在recvfrom结果中遇到问题,我的缓冲区中的ip dest / src不是服务器dest ip。

结果

1
2
$> ./my_ping qwant.com
IPv4: hdr-size=20 pkt-size=56 protocol=1 TTL=254 src=10.0.2.2 dest=172.17.0.2

但真正的ping:

1
2
3
$> ping qwant.com
PING qwant.com (194.187.168.99): 48 data bytes
56 bytes from 194.187.168.99: icmp_seq=0 ttl=61 time=101.742 ms

这不是相同的TTL和IP

Init struct addrinfo:

1
2
3
4
5
6
7
8
9
10
struct addrinfo hints;
struct addrinfo *a_info;

bzero(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = 0;
hints.ai_flags = AI_ADDRCONFIG;

getaddrinfo(host, NULL, &hints, &a_info)

Init套接字:

1
2
sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
setsockopt(sock, SOL_IP, IP_TTL, (void *)&val, sizeof(val);

发送& 接收时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
size_t                  i;
t_ping                  pack;
int                     count = 1;
struct iphdr            buff;

while (TRUE)
{
    bzero(&pack, sizeof(pack));
    pack.head.type = 8;
    pack.head.code = getpid();
    pack.id = count++;

    while (i < 15)
    {
        pack.seq[i] = i + '0';
        i++;
    }
    pack.seq[i] = 0;
    pack.head.chk = checksum(&pack, sizeof(pack));
    ft_bzero((void *)&buff, sizeof(buff));

    if (sendto(sock, &pack, sizeof(pack), 0, a_info->ai_addr, a_info->ai_addrlen) < 0)
        perror("sendto");
    if (recvfrom(sock, (void *)&buff, sizeof(buff), 0, a_info->ai_addr, &a_info->ai_addrlen) < 0)
        perror("recvfrom");

    display((void *)&buff);
    sleep(1);
}

最后我的显示功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct iphdr    *ip = buff;
char src[INET6_ADDRSTRLEN];
char dest[INET6_ADDRSTRLEN];

inet_ntop( AF_INET, (void *)&ip->saddr, src, sizeof(src) );
inet_ntop( AF_INET, (void *)&ip->daddr, dest, sizeof(dest) );


printf("IPv%d: hdr-size=%d pkt-size=%d protocol=%d TTL=%d src=%s dest=%s
"
,
    ip->version,
    ip->ihl*4,
    ntohs(ip->tot_len),
    ip->protocol,
    ip->ttl,
    src,
    dest);

在我看来,我的缓冲区包含有关最后一个数据包步骤(路由器 - >我的家)的信息,它解释了为什么TTL值为254以及为什么我发现与traceroute相同的IP:

$> traceroute qwant.com
traceroute to qwant.com (194.187.168.99), 30 hops max, 60 byte packets
172.17.0.1 (172.17.0.1) 0.026 ms 0.011 ms 0.010 ms
10.0.2.2 (10.0.2.2) 0.149 ms 0.160 ms 0.156 ms
[...]
194.187.168.99 (194.187.168.99) 147.634 ms 147.506 ms 147.540 ms

为什么收到的信息与我的服务器目标无关? 我怎样才能收到这些信息?


方案:

我更改了我的getaddrinfo调用和我的套接字init:

1
2
3
getaddrinfo(stats.host, NULL, NULL, &addrinfo);
sd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
setsockopt(sd, SOL_IP, IP_TTL, &val, sizeof(val));

接下来我创建了2 struct sockaddr_in:一个用于sendto,第二个用于recvfrom

1
2
struct sockaddr_in send;
struct sockaddr_in recv;

现在初始化send结构以指定sendto处的目标,但没有更改recv:它是在第一次recvfrom调用期间自动分配的。

1
2
3
4
bzero(&send, sizeof(send));
send.sin_family = addrinfo->ai_family;
send.sin_port = 0;
send.sin_addr.s_addr = ((struct sockaddr_in *)addrinfo->ai_addr)->sin_addr.s_addr;

最后我改变了我的数据包结构

1
2
3
4
5
struct packet
{
    struct icmphdr hdr;
    char msg[PACKETSIZE-sizeof(struct icmphdr)];
};

现在,只需调用sendtorecvfrom

1
2
3
socklen_t len = sizeof(recv);
sendto(sd, &pckt, sizeof(pckt), 0, (struct sockaddr*)&send, sizeof(send));
recvfrom(sd, &pckt, sizeof(pckt), 0, (struct sockaddr*)&recv, &len);

The Internet Assigned Numbers Authority (IANA) has reserved the
following three blocks of the IP address space for private networks:

1
2
3
          10.0.0.0        -   10.255.255.255
          172.16.0.0      -   172.31.255.255
          192.168.0.0     -   192.168.255.255

172.17.0.2是专用网络中的地址。 我想你的机器在这个网络里面,还有服务器。

getaddrinfo()可以为您返回多个地址。 尝试运行所有响应并检查是否还有Internet地址。

来自man getaddrinfo:

/* getaddrinfo() returns a list of address structures.
Try each address until we successfully bind(2).
If socket(2) (or bind(2)) fails, we (close the socket
and) try the next address. */

1
2
3
4
5
6
7
8
9
10
11
for (rp = result; rp != NULL; rp = rp->ai_next) {
    sfd = socket(rp->ai_family, rp->ai_socktype,
            rp->ai_protocol);
    if (sfd == -1)
        continue;

   if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
        break;                  /* Success */

   close(sfd);
}