Creating an ICMP Class



Creating an ICMP Class

As just mentioned, the raw socket does not automatically format your ICMP packet, so you must do this yourself. C# is an object-oriented language. It makes sense to create a C# ICMP class that you can use to format an ICMP packet and manipulate the packet contents as necessary. This allows you to use the ICMP class in any of your network applications that use ICMP packets.

The ICMP Class Constructors

The ICMP class should define a data variable for each element in the ICMP packet. Figure shows the data variables to be defined to represent a generic ICMP packet.

Figure: The ICMP Class Generic Data Elements

Data Variable

Size

Type

Type

1 byte

Byte

Code

1 byte

Byte

Checksum

2 bytes

Unsigned 16-bit integer

Message

multibyte

Byte array

The Default Constructor

The default ICMP constructor creates an instance of the ICMP class but does not assign any values to the data variables. These can be assigned within the ICMP application program when you are ready to create the ICMP packet.

Here is the format for the ICMP class default constructor:

class ICMP
{
  public byte Type;
  public byte Code;
  public UInt16 Checksum;
  public int MessageSize;
  public byte[] Message = new byte[1024];
  public ICMP()
  {
  }
}

Although it is not part of the ICMP packet, the MessageSize variable is added to portray the actual size of the variable-length Message variable. You will understand why this is necessary when you see it in action, coming up.

To construct a new ICMP packet, the application need only create a new ICMP object and assign the appropriate values to the data elements, like this:

ICMP packet = new ICMP();
packet.Type = 0x08;
packet.Code = 0x00;
packet.Checksum = 0;

This code snippet shows an example of creating the first part of an Echo Request ICMP packet. The Echo Request packet demonstrates a common scenario that you will run into when creating an ICMP packet. Since the Echo Request packet defines fields within the ICMP Message element (the Identifier and the Sequence fields), you must decide how you are going to build the Message element. You have two options for accomplishing this:

  • Create another class specifically for the unique Echo Request fields, and retrieve the byte array of the class.

  • Convert the Message fields separately into byte arrays and place them in the data element.

If you are planning to create a lot of Echo Request packets, creating a Ping class that defines the individual Echo Request fields may be the way to go. Otherwise, for just a few Echo Request packets, it won’t be too big a task to convert the values into individual byte arrays and add them to the Message element:

Buffer.BlockCopy(BitConverter.GetBytes((short)1), 0, packet.Message, 0, 2);
Buffer.BlockCopy(BitConverter.GetBytes((short)1), 0, packet.Message, 2, 2);
byte[] data = Encoding.ASCII.GetBytes("test packet");
Buffer.BlockCopy(data, 0, packet.Message, 4, data.Length);
packet.MessageSize = data.Length + 4;

Here the Identifier and Sequence fields for the Echo Reply packet are each converted into byte arrays and placed in the proper place in the Message element.

Rebuilding an ICMP Object

After sending an ICMP packet, most likely you will get an ICMP packet returned from the remote device. To make it easier to decipher the contents of the packet, you should create another ICMP class constructor that can take a raw ICMP byte array and place the values into the appropriate data elements in the class:

public ICMP(byte[] data, int size)
{
  Type = data[20];
  Code = data[21];
  Checksum = BitConverter.ToUInt16(data, 22);
  MessageSize = size - 24;
  Buffer.BlockCopy(data, 24, Message, 0, MessageSize);
}

Remember that raw socket returns the entire IP packet. This means you must skip the IP header information before you can extract the ICMP packet information. So the start of the ICMP information, the Type element, is in the 20th position in the byte array. The individual data elements within the ICMP packet are then extracted byte by byte into the appropriate ICMP element.

After creating the new ICMP object with the received packet data, you can reference the data elements individually:

int recv = ReceiveFrom(data, ref ep);
ICMP response = new ICMP(data, recv);
Console.WriteLine("Received ICMP packet:");
Console.WriteLine(" Type {0}", response.Type);
Console.WriteLine(" Code: {0}", response.Code);
Int16 Identifier = BitConverter.ToInt16(response.Message, 0);
Int16 Sequence = BitConverter.ToInt16(response.Message, 2);
Console.WriteLine(" Identifier: {0}", Identifier);
Console.WriteLine(" Sequence: {0}", Sequence);
stringData = Encoding.ASCII.GetString(response.Message, 4,
      response.MessageSize - 4);
Console.WriteLine(" data: {0}", stringData);

Obtaining the first two data elements of the received ICMP packet is easy; all you have to do is read their values from the ICMP class elements. Extracting the data fields from the Message element is a little trickier. Because you know that the first 2 bytes constitute the unsigned integer Identifier field, and the second 2 bytes are the unsigned integer Sequence field, you can use the BitConverter class to assign those values from the appropriate bytes. The remainder of the Message element is assigned to the Message field of the Echo Reply packet.

From this code snippet, it is now easy to see why the MessageSize data element was added to the ICMP class. Without it, it would be difficult to determine how to reconstruct the Message element from the received packet.

The ICMP Packet Creator

After a new ICMP object is created and the packet data elements have been defined, you will want to send the packet to a remote network device. Unfortunately, you cannot directly send the ICMP object in a SendTo() method; it must be turned into a byte array.

As discussed in Chapter 7, “Using the C# Sockets Helper Classes,” the easiest way to send a complex class object across the network is to create a method that converts each data element into a byte array and concatenate the byte arrays into a single large byte array. This was done using the Buffer.BlockCopy() method:

public byte[] getBytes()
{
  byte[] data = new byte[MessageSize + 9];
  Buffer.BlockCopy(BitConverter.GetBytes(Type), 0, data, 0, 1);
  Buffer.BlockCopy(BitConverter.GetBytes(Code), 0, data, 1, 1);
  Buffer.BlockCopy(BitConverter.GetBytes(Checksum), 0, data, 2, 2);
  Buffer.BlockCopy(Message, 0, data, 4, MessageSize);
  return data;
}

Let’s see what is happening here. A data byte array is created to hold the newly converted data elements of the ICMP object. Each data element is converted to a byte array using the appropriate BitConverter class method and placed in the data byte array for the getBytes() method. When all of the data elements have been converted to the byte array, it contains a properly formatted ICMP packet, which can be sent to a remote network device:

IPEndPoint iep = new IPEndPoint(IPAddress.Parse("192.168.1.2"), 0);
sock.SendTo(packet.getBytes(), iep);

Remember that because ICMP does not use ports, you can use a zero value for the port parameter when creating the IPEndPoint object for the destination address.

Note 

You may have noticed that the Identifier and Sequence values were not converted to network byte order before being placed in the byte array. This is because the remote device will return the exact same packet for you to decode, you know that they will be in the same format when you get them back.

The ICMP Checksum Method

Perhaps the most dreaded part of creating an ICMP packet is calculating the checksum value of the packet (unless, of course, you enjoy binary math). The easiest way to do this task is to create a self-contained method for calculating the checksum and place it in the ICMP class to be used by the ICMP application program.

The ICMP RFC defines the Checksum element as “the 16-bit one’s complement of the one’s complement sum of the ICMP message, starting with the ICMP type. For computing the checksum, the Checksum element should be set to zero.”

Fortunately for us non–math majors, there are quite a few examples of checksum utilities available in the public domain. Listing 11.1 shows the checksum method I chose to implement in the ICMP class.

Listing 11.1: The getChecksum() ICMP method
Start example
public UInt16 getChecksum()
  {
   UInt32 chcksm = 0;
   byte[] data = getBytes();
   int packetsize = MessageSize + 8;
   int index = 0;
   while ( index < packetsize)
   {
     chcksm += Convert.ToUInt32(BitConverter.ToUInt16(data, index));
     index += 2;
   }
   chcksm = (chcksm >> 16) + (chcksm & 0xffff);
   chcksm += (chcksm >> 16);
   return (UInt16)(~chcksm);
  }
End example

Because the ICMP checksum value uses 16-bit arithmetic, this algorithm reads 2-byte chunks of the ICMP packet at a time (using the ToUInt16() method of the BitConverter class) and performs the necessary arithmetic operations on the bytes. The return value is a 16-bit unsigned integer value.

To use the checksum value in an ICMP application program, first fill in all the data elements, setting the Checksum element to zero. Next, call the getChecksum() method to calculate the checksum of the ICMP packet, and then place the result in the Checksum element of the packet:

packet.Checksum = 0;
packet.Checksum = packet.getChecksum();

After the Checksum element is calculated, the packet is ready to be sent out to the destination host using the SendTo() method.

Note 

In a production application, when an ICMP packet is received from a remote host, you should extract the Checksum element value and compare it to the calculated value for the packet. If the two values do not match, an error has occurred and the packet should be retransmitted.

Putting It All Together

Now that you have seen all of the individual elements of the ICMP class, we can look at the entire class as defined in the C# code. Listing 11.2 is the ICMP.cs program, which implements the ICMP class to assist you in creating and reading ICMP packets.

Listing 11.2: The ICMP.cs program
Start example
using System;
using System.Net;
using System.Text;
class ICMP
{
  public byte Type;
  public byte Code;
  public UInt16 Checksum;
  public int MessageSize;
  public byte[] Message = new byte[1024];
  public ICMP()
  {
  }
  public ICMP(byte[] data, int size)
  {
   Type = data[20];
   Code = data[21];
   Checksum = BitConverter.ToUInt16(data, 22);
   MessageSize = size - 24;
   Buffer.BlockCopy(data, 24, Message, 0, MessageSize);
  }
  public byte[] getBytes()
  {
   byte[] data = new byte[MessageSize + 9];
   Buffer.BlockCopy(BitConverter.GetBytes(Type), 0, data, 0, 1);
   Buffer.BlockCopy(BitConverter.GetBytes(Code), 0, data, 1, 1);
   Buffer.BlockCopy(BitConverter.GetBytes(Checksum), 0, data, 2, 2);
   Buffer.BlockCopy(Message, 0, data, 4, MessageSize);
   return data;
  }
  public UInt16 getChecksum()
  {
   UInt32 chcksm = 0;
   byte[] data = getBytes();
   int packetsize = MessageSize + 8;
   int index = 0;
   while ( index < packetsize)
   {
     chcksm += Convert.ToUInt32(BitConverter.ToUInt16(data, index));
     index += 2;
   }
   chcksm = (chcksm >> 16) + (chcksm & 0xffff);
   chcksm += (chcksm >> 16);
   return (UInt16)(~chcksm);
  }
}
End example

Something to keep in mind: do not try and compile the ICMP.cs class by itself. It does not include a Main() method and so cannot create an executable program on its own. Instead, the class supports ICMP network applications, allowing you to easily create, send, and interpret ICMP packets. The rest of the programs in this chapter use this class to build ICMP packets.

 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows