【问题标题】:A few related questions regarding traceroutes in c:关于c中的traceroutes的一些相关问题:
【发布时间】:2015-08-13 00:11:41
【问题描述】:

根据Wikipedia,一个traceroute程序

Traceroute 默认发送一系列用户数据报协议 (UDP)数据包寻址到目标主机[...]生存时间 (TTL) 值,也称为跳数限制,用于确定 中间路由器被遍历到目的地。路由器 路由和丢弃数据包时将数据包的 TTL 值减 1 其 TTL 值已达到零,返回 ICMP 错误消息 超出 ICMP 时间。[..]

我开始编写一个程序(使用示例 UDP 程序作为指南)来遵守这个规范,

#include <sys/socket.h>
#include <assert.h>
#include <netinet/udp.h>     //Provides declarations for udp header
#include <netinet/ip.h>      //Provides declarations for ip header
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>

#define DATAGRAM_LEN sizeof(struct iphdr) + sizeof(struct iphdr)

unsigned short csum(unsigned short *ptr,int nbytes) {
    register long sum;
    unsigned short oddbyte;
    register short answer;

    sum=0;
    while(nbytes>1) {
        sum+=*ptr++;
        nbytes-=2;
    }
    if(nbytes==1) {
        oddbyte=0;
        *((u_char*)&oddbyte)=*(u_char*)ptr;
        sum+=oddbyte;
    }

    sum = (sum>>16)+(sum & 0xffff);
    sum = sum + (sum>>16);
    answer=(short)~sum;

    return(answer);
}

char *new_packet(int ttl, struct sockaddr_in sin) {
    static int id = 0;
    char *datagram = malloc(DATAGRAM_LEN);
    struct iphdr *iph = (struct iphdr*) datagram;
    struct udphdr *udph = (struct udphdr*)(datagram + sizeof (struct iphdr));

    iph->ihl = 5;
    iph->version = 4;
    iph->tos = 0;
    iph->tot_len = DATAGRAM_LEN;
    iph->id = htonl(++id); //Id of this packet
    iph->frag_off = 0;
    iph->ttl = ttl;
    iph->protocol = IPPROTO_UDP;
    iph->saddr = inet_addr("127.0.0.1");//Spoof the source ip address
    iph->daddr = sin.sin_addr.s_addr;
    iph->check = csum((unsigned short*)datagram, iph->tot_len);

    udph->source = htons(6666);
    udph->dest = htons(8622);
    udph->len = htons(8); //udp header size
    udph->check = csum((unsigned short*)datagram, DATAGRAM_LEN);

    return datagram;
}

int main(int argc, char **argv) {
    int s, ttl, repeat;
    struct sockaddr_in sin;
    char *data;

    printf("\n");

    if (argc != 3) {
        printf("usage: %s <host> <port>", argv[0]);
        return __LINE__;
    }

    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = inet_addr(argv[1]);
    sin.sin_port = htons(atoi(argv[2]));

    if ((s = socket(AF_PACKET, SOCK_RAW, 0)) < 0) {
        printf("Failed to create socket.\n");
        return __LINE__;
    }

    ttl = 1, repeat = 0;
    while (ttl < 2) {
        data = new_packet(ttl);
        if (write(s, data, DATAGRAM_LEN) != DATAGRAM_LEN) {
            printf("Socket failed to send packet.\n");
            return __LINE__;
        }
        read(s, data, DATAGRAM_LEN);
        free(data);
        if (++repeat > 2) {
            repeat = 0;
            ttl++;
        }
    }
    return 0;
}

...不过现在我有几个问题。

  • read(s, data, ...一次读取整个数据包,还是我需要解析从套接字读取的数据;寻找特定于 IP 数据包的标记?
  • 在我的数据包返回我的盒子时将它们唯一标记为过期的最佳方法是什么?
  • 我应该使用IPPROTO_ICMP 标志设置第二个套接字,还是更容易编写过滤器?接受一切?
  • 是否存在其他常见错误;还是可以预见任何常见的障碍?

【问题讨论】:

    标签: c sockets networking traceroute


    【解决方案1】:

    一个常见的缺陷是在这个级别的编程需要非常小心地使用正确的包含文件。例如,您的程序原样不会在 NetBSD 上编译,这在遵循相关标准方面通常非常严格。 即使我添加了一些包含,也没有struct iphdr,而是有一个struct udpiphdr

    所以现在我的其余答案不是基于在实践中尝试你的程序。

    • read(2) 可用于一次读取单个数据包。对于面向数据包的协议,例如 UDP,您从中获得的数据永远不会超过单个数据包。 不过您也可以使用recvfrom(2)recv(2)recvmsg(2) 来接收数据包。

    If fildes refers to a socket, read() shall be equivalent to recv() with no flags set.

    • 为了识别数据包,我相信使用 id 字段通常已经完成,就像您已经完成的那样。我不确定您所说的“将我的数据包返回到我的盒子时标记为过期”是什么意思,因为您的数据包不会返回给您。您可能会收到 ICMP Time Exceeded 消息。这些通常会在几秒钟内到达,如果它们到达的话。有时它们不会被发送,有时它们可​​能会被您和它们的发件人之间配置错误的路由器阻止。 请注意,这假设您在数据包中设置的 IP ID 受到您正在使用的网络堆栈的尊重。它可能没有,并用不同的 ID 替换您选择的 ID。因此,traceroute command as found in NetBSD 的原作者 Van Jacobson 使用了不同的方法:
    * udp 端口​​的使用可能看起来很奇怪(好吧,这很奇怪)。 * 问题是一个icmp消息只包含8个字节的 * 来自原始数据报的数据。 8 个字节是 udp 的大小 * header 所以,如果我们想将回复与原始相关联 * 数据报,必要的信息必须被编码到 * udp 标头(可以使用 ip id 但无法使用 * 与内核分配的 ip id 互锁,无论如何, * 需要更多的内核黑客才能允许这样做 * 设置 ip id 的代码)。因此,要允许两个或更多用户 * 同时使用 traceroute,我们使用这个任务的 pid 作为 * 源端口(设置高位将端口号移出 * 的“可能”范围)。跟踪正在执行的探测 * 回复(因此时间和/或跳数不会被 * 在传输过程中延迟的回复),我们增加目的地 * 每次探测前的端口号。
    • 使用IPPROTO_ICMP 套接字接收回复比尝试接收所有 数据包更有效率。这样做也需要更少的特权。当然,发送原始数据包通常已经需要 root,但如果使用更细粒度的权限系统,它可能会有所不同。

    【讨论】:

    • 不要对未引用的文本使用引号格式。
    • 哪些文字没有被引用?两个引号都被引用了。不幸的是,第二个引号的每一行的前导 * 弄乱了布局,所以我删除了它。然而,这当然不是正文的一部分。
    【解决方案2】:

    以下是我的一些建议(假设它是一台 Linux 机器)。

    1. 读取数据包 您可能想要读取整个 1500 字节的数据包(整个以太网帧)。不用担心 - 较小的帧仍将被完全读取,read 返回读取的数据长度。

    2. 添加标记的最佳方法是使用一些 UDP 有效负载(一个简单的无符号整数)就足够了。在发送的每个数据包上增加它。 (我刚刚在 traceroute 上做了一个 tcpdump - ICMP 错误 - 确实返回了整个 IP 帧 - 所以你可以查看返回的 IP 帧,解析 UDP 有效负载等等。注意你的DATAGRAM_LEN 会相应地改变。)当然你可以使用 ID - 但要注意 ID 主要用于分片。你应该没问题 - 因为你不会接近任何具有这些数据包大小的中间路由器的碎片限制。一般来说,为了我们的自定义目的,“窃取”用于其他用途的协议字段并不是一个好主意。

    3. 一种更简洁的方法是在原始套接字上实际使用 IPPROTO_ICMP(如果您的机器上安装了手册 man 7 rawman 7 icmp)。您不希望在您的设备上接收 所有 数据包的副本并忽略那些不是 ICMP 的数据包。

    4. 如果您在AF_PACKET 上使用SOCKET_RAW 类型,则必须手动附加链接层标头,或者您可以执行SOCKET_DGRAM 并检查。还有man 7 packet 有很多微妙之处。

    希望对您有所帮助还是您正在查看一些实际代码?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-09-09
      • 1970-01-01
      • 2012-05-14
      • 1970-01-01
      • 1970-01-01
      • 2014-10-15
      • 2018-05-24
      相关资源
      最近更新 更多