关于c中traceroutes的一些相关问题:

时间:2015-05-30 01:20:24

标签: c sockets networking traceroute

根据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标志的第二个套接字,还是更容易编写过滤器;接受一切?
  • 是否存在其他常见错误;或者是可以预见的任何常见障碍?

2 个答案:

答案 0 :(得分:1)

一个常见的缺陷是,此级别的编程需要非常小心地使用正确的包含文件。例如,您的程序将无法在NetBSD上编译,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的原作者范雅各布森使用了另一种方法:
 * The udp port usage may appear bizarre (well, ok, it is bizarre).
 * The problem is that an icmp message only contains 8 bytes of
 * data from the original datagram.  8 bytes is the size of a udp
 * header so, if we want to associate replies with the original
 * datagram, the necessary information must be encoded into the
 * udp header (the ip id could be used but there's no way to
 * interlock with the kernel's assignment of ip id's and, anyway,
 * it would have taken a lot more kernel hacking to allow this
 * code to set the ip id).  So, to allow two or more users to
 * use traceroute simultaneously, we use this task's pid as the
 * source port (the high bit is set to move the port number out
 * of the "likely" range).  To keep track of which probe is being
 * replied to (so times and/or hop counts don't get confused by a
 * reply that was delayed in transit), we increment the destination
 * port number before each probe.
  • 使用IPPROTO_ICMP套接字接收回复比尝试接收所有数据包更有效。它还需要更少的权限。当然,发送原始数据包通常已经需要root,但如果使用更细粒度的权限系统,它可能会有所不同。

答案 1 :(得分:1)

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

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

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

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

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

  5. 希望有帮助或者您正在查看一些实际代码?