motoku
motoku

Reputation: 1581

A few related questions regarding traceroutes in c:

According to Wikipedia, a traceroute program

Traceroute, by default, sends a sequence of User Datagram Protocol (UDP) packets addressed to a destination host[...] The time-to-live (TTL) value, also known as hop limit, is used in determining the intermediate routers being traversed towards the destination. Routers decrement packets' TTL value by 1 when routing and discard packets whose TTL value has reached zero, returning the ICMP error message ICMP Time Exceeded.[..]

I started writing a program (using an example UDP program as a guide) to adhere to this specification,

#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;
}

... however at this point I have a few questions.

Upvotes: 2

Views: 529

Answers (2)

gabhijit
gabhijit

Reputation: 3585

Here are some of my suggestions (based on assumption it's a Linux machine).

  1. read packets You might want to read whole 1500 byte packets (entire Ethernet frame). Don't worry - smaller frames would still be read completely with read returning the length of data read.

  2. Best way to add marker is to have some UDP payload (a simple unsigned int) should be good enough. Increase it on every packet sent. (I just did a tcpdump on traceroute - the ICMP error - does return an entire IP frame back - so you can look at the returned IP frame, parse the UDP payload and so on. Note your DATAGRAM_LEN would change accordingly. ) Of course you can use ID - but be careful that ID is mainly used by fragmentation. You should be okay with that - 'cos you'd not be approaching fragmentation limit on any intermediate routers with these packet sizes. Generally, not a good idea to 'steal' protocol fields that are meant for something else for our custom purpose.

  3. A cleaner way could be to actually use IPPROTO_ICMP on raw sockets (if manuals are installed on your machine man 7 raw and man 7 icmp). You would not want to receive copy of all packets on your device and ignore those that are not ICMP.

  4. If you are using type SOCKET_RAW on AF_PACKET, you will have to manually attach a link layer header or you can do SOCKET_DGRAM and check. Also man 7 packet for lot of subtleties.

Hope that helps or are you looking at some actual code?

Upvotes: 1

A common pitfall is that programming at this level needs very careful use of the proper include files. For instance, your program as-is won't compile on NetBSD, which is typically quite strict in following relevant standards. Even when I add some includes, there is no struct iphdr but there is a struct udpiphdr instead.

So for now the rest of my answer is not based on trying your program in practice.

  • read(2) can be used to read single packets at a time. For packet-oriented protocols, such as UDP, you'll never get more data from it than a single packet. However you can also use recvfrom(2), recv(2) or recvmsg(2) to receive the packets.

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

  • To identify the packets, I believe using the id field is typically done, as you have already. I am not sure what you mean with "mark my packets as they return to my box as expired", since your packets don't return to you. What you may get back are ICMP Time Exceeded messages. These usually arrive within a few seconds, if they arrive at all. Sometimes they are not sent, sometimes they may be blocked by misconfigured routers between you and their sender. Note that this assumes that the IP ID you set up in your packet is respected by the network stack you're using. It is possible that it doesn't, and replaces your chosen ID with a different one. Van Jacobson, the original author of the traceroute command as found in NetBSD therefore use a different method:
 * 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.
  • Using a IPPROTO_ICMP socket for receiving the replies is more likely to be efficient than trying to receive all packets. It would also require fewer privileges to do so. Of course sending raw packets normally already requires root, but it could make a difference if a more fine-grained permission system is in use.

Upvotes: 1

Related Questions