Monday, September 7, 2009

Implementing Traceroute with System.Net: Part-II

In this part, we will implement traceroute according to the principles we discussed in the last post:

http://ferozedaud.blogspot.com/2009/09/implementing-traceroute-with-systemnet.html

First, start with the code for Ping, which is at http://blogs.msdn.com/feroze_daud/archive/2005/10/26/485372.aspx and modify the Main() method as follows:

    static void Main(string[] args)
    {

        if (args.Length != 1)
        {
            Usage();
        }



        if (0 == String.Compare(args[0], "/?", true, CultureInfo.InvariantCulture)

        || 0 == String.Compare(args[0], "-h", true, CultureInfo.InvariantCulture)

        || 0 == String.Compare(args[0], "-?", true, CultureInfo.InvariantCulture))
        {
            Usage();
        }

        string target = args[0];

        IPAddress[] heTarget = Dns.GetHostAddresses(target);

        IPAddress ipTarget = null;

        foreach (IPAddress ip in heTarget)
        {

            if (ip.AddressFamily == AddressFamily.InterNetwork)
            {
                ipTarget = ip;
                break;
            }
        }



        IPAddress[] heSource = Dns.GetHostAddresses(Dns.GetHostName());

        IPAddress ipSource = null;

        foreach (IPAddress ip in heSource)
        {
            byte[] addressBytes = ip.GetAddressBytes();
            if (addressBytes[0] == 169 && addressBytes[1] == 254)
            {
                continue;
            }
            if (ip.AddressFamily == AddressFamily.InterNetwork)
            {
                ipSource = ip;
                break;
            }
        }

        IPEndPoint epLocal = new IPEndPoint(ipSource, 0);

        Socket pingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);

        pingSocket.Bind(epLocal);

        byte[] data = Encoding.ASCII.GetBytes("1234567890abcdef");

        Console.WriteLine("Ping {0}({1}) with {2} bytes of data...", target, ipTarget.ToString(), data.Length);

        Console.WriteLine();

        int hop = 1;
        int maxHops = 30;

        pingSocket.ReceiveTimeout = 30;
        System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
        EndPoint epResponse = (EndPoint)new IPEndPoint(0, 0);

        while (true)
        {
            Console.Write("{0}", hop);
            bool allTimedOut = true;
            for (int i = 0; i < 3; i++)
            {
                ICMP_PACKET packet = ICMP_PACKET.CreateRequestPacket(111, 222, data);

                IPEndPoint epRemote = new IPEndPoint(ipTarget, 0);

                pingSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.IpTimeToLive, hop);


                stopWatch.Start();
                pingSocket.SendTo(packet.Serialize(), epRemote);

                byte[] receiveData = new byte[1024];
                int read = 0;
                try
                {
                    epResponse = new IPEndPoint(0, 0);
                    read = pingSocket.ReceiveFrom(receiveData, ref epResponse);
                    stopWatch.Stop();
                    ICMP_PACKET recvPacket = new ICMP_PACKET(receiveData, 20, read);

                    Console.Write("\<<{0}ms", stopWatch.ElapsedMilliseconds);
                    allTimedOut = false;
                }
                catch (SocketException e)
                {
                    Console.Write("\t*");
                }
            }

            if (allTimedOut)
            {
                Console.WriteLine("\tRequest timed out");
            }
            else
            {
                Console.WriteLine("\t{0}", ((IPEndPoint)epResponse).Address.ToString());
            }
            ++hop;

            IPEndPoint ipepResponse = epResponse as IPEndPoint;
            if (hop > maxHops || ipepResponse.Address.Equals(ipTarget))
            {
                break;
            }
        }

        pingSocket.Close();
    }

As discussed earlier, we have now put a loop around the actual Socket.SendTo() call. This loop will run three times. We will continue sending the packet until we reach 30 hops max, or we have reached the destination. As you have noticed, since we did all the heavy lifting in the Ping program, all we had to do here, was to set the TimeToLive Socket option so that the routers/gateways on the path to the destination respond with a "Time Exceeded" message. When I run it on my machine, here is the output:
C:\>traceroute.exe www.yahoo.com
Ping www.yahoo.com(209.131.36.158) with 16 bytes of data...

1       <0ms    <1ms    <1ms    192.168.1.1
2       <5ms    <10ms   <14ms   <Address removed>
3       <18ms   <23ms   <27ms   <Address removed>
4       <33ms   <38ms   <43ms   <Address removed>
5       <68ms   <92ms   <116ms  <Address removed>
6       <143ms  <171ms  <197ms  <Address removed>
7       <223ms  <250ms  <276ms  <Address removed>
8       <304ms  <331ms  <365ms  <Address removed>
9       <391ms  <417ms  <445ms  <Address removed>
10      <473ms  <510ms  <538ms  <Address removed>


Note, that I have removed the actual IPAddresses from the output, for privacy reasons.

In the next part, we will discuss how we can make this utility more robust.