Ping Part II: Adventures in Socket programming using System.Net

If you see the ping utility that comes with your OS, you will notice that it has many options. However, the one we are going to develop will just take one argument:

 

C:\ping>ping <hostname> | <ipaddress>

        Example: ping www.contoso.com

        Example: ping 127.0.0.1

 

 

Let us start out by looking at requirements of the Ping client. Basically, the task of the tool is to find out if a specified server is alive, and on the network. The way it achieves this is by sending an “echo” packet to the server. The server responds with an “echo” response. If the server responds within a certain time interval, then we can assume that there is network connectivity from the client to the server. If it doesn’t respond, then it could indicate a variety of things that could be wrong, for eg:

 

  • The destination server is not up.
  • The destination server is up but does not have networking enabled.
  • The destination server is up and on the network, but is configured to drop Ping requests
  • Etc.

 

We will not concern ourselves with “why” a server is not responding to the Ping request, as it could be another topic in itself.

 

Where was I? Oh yeah, the Ping client. It so happens that we have a protocol (ICMP) that has the echo request and reply operations that we need to implement the Ping client. Read the ICMP RFC to get information about all the features offered by the ICMP protocol. As we know, the ICMP protocol is at the same layer as the IP protocol in the OSI layering scheme.

 

For the ping utility, we will be using the ICMP Echo Request and response messages. The client will send the ICMP echo request message, and the server will reply with an Echo response message. If you look at the Rfc, it describes how an RFC message looks like:

 

Offset

Field Name

Size (octets)

Description

0

Type

1

Specified type of the operation. It should be set to 8 for request message and 0 for reply messages

8

Code

1

This field is Zero.

16

Checksum

2

One’s complement checksum of the ICMP message

32

Identifier

2

Used to correlate request and reply

48

Sequence Number

2

Used to correlate request and reply

64

Data

Variable

Optional data to be sent with the request. This must be sent back by the server.

 

 

Looking at the table above, we can come up with a C# class that corresponds to what the packet will look like:

 

public class ICMP_PACKET

{

      public Byte i_type; // type of message

      public Byte i_code; // sub code

      public UInt16 i_cksum; // ones complement checksum of header

      public UInt16 i_id; // identifier

      public UInt16 i_seq; // sequence number

      public Byte[] data;

}

I used Ethereal to sniff the network while doing a Ping, to see what a Ping request/response looks like on the wire. The request packet looks like this:

 

0020 59 99 08 00 b5 58 02 00 96 03 61 62 63 64 65 66 Y....X.. ..abcdef

0030 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 ghijklmn opqrstuv

0040 77 61 62 63 64 65 66 67 68 69 wabcdefghi     

Let us dissect the packet:

 

i_type = 0x08 ( Echo Request)

i_code = 0x00

i_cksum = 0xb558

i_id  = 0x0200

i_seq = 0x9603

data = 32 bytes ( abcedf…. )

 

The ICMP echo reply looks as follows:

 

0020 59 9a 00 00 bd 58 02 00 96 03 61 62 63 64 65 66 Y....X.. ..abcdef

0030 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 ghijklmn opqrstuv

0040 77 61 62 63 64 65 66 67 68 69 wabcdefghi     

Let us dissect the packet:

 

i_type = 0x00 ( Echo Reply)

i_code = 0x00

i_cksum = 0xbd58

i_id  = 0x0200

i_seq = 0x9603

data = 32 bytes ( abcedf…. )

Given what we have learned so far, we can now come up with a skeleton for the program that we will develop:

 

using System;

using System.Text;

using System.Globalization;

namespace ping

{

public class ICMP_PACKET

      {

            Byte i_type; // type of message

            Byte i_code; // sub code

            UInt16 i_cksum; // ones complement checksum of header

            UInt16 i_id; // identifier

            UInt16 i_seq; // sequence number

            Byte[] data;

            private const int headerLength = 8;

            // Total size of the packet (header + payload)

            public int Length

            {

                  get { return HeaderLength + PayloadLength; }

            }

            // Size of the packet header (excluding data)

            public int HeaderLength

            {

                  get { return headerLength; }

            }

            // Size of the payload

      public int PayloadLength

            {

                  get { return (data == null) ? 0 : data.Length; }

            }

            public byte PacketType

            {

                  get { return i_type; }

            }

            public byte Code

            {

                  get { return i_code; }

            }

            public UInt16 Identifier

            {

                  get { return i_id; }

            }

            public UInt16 SequenceNumber

            {

                  get { return i_seq; }

            }

            public ICMP_PACKET(Byte kind, Byte code, UInt16 id, UInt16 seq, byte[] data)

            {

                  this.i_type = kind;

                  this.i_code = code;

                  this.i_id = id;

                  this.i_seq = seq;

                  this.data = data;

                  this.i_cksum = 0;

            }

            public static ICMP_PACKET CreateRequestPacket(UInt16 id, UInt16 seq, byte[] data)

            {

                  return new ICMP_PACKET(8, 0, id, seq, data);

            }

      };

      class Program

      {

            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();

                  }

            }

            static void Usage()

            {

                  Console.WriteLine("ping <hostname> | <ipaddress>");

                  Console.WriteLine("\tExample: ping www.contoso.com");

                  Console.WriteLine("\tExample: ping 127.0.0.1");

                  Environment.Exit(0);

            }

      }

}