In Part II of this article, we saw how we are going to use the ICMP protocol to implement a simple Ping client. We also saw a skeleton of this program which showed how to translate the ICMP packet specification into a C# structure.
In this part, we will write a routine to calculate the checksum of the packet, and a routine to serialize the packet into a byte array. Recall from PartII that the request and reply packets have a particular encoding on the wire. We will have to write a routine that will encode the packet into a byte array, so that the array can be sent on the wire.
Take the skeleton that we created in Part II and add the following (lines in blue):
using System;
using System.Text;
using System.Globalization;
namespace ping
{
public class ICMP_PACKET
{
...
public ICMP_PACKET(Byte kind, Byte code, UInt16 id, UInt16 seq, byte[] data)
{
this.i_type = kind;
this.i_code = code;
this.i_id = ToNetworkOrder(id);
this.i_seq = ToNetworkOrder(seq);
this.data = data;
this.i_cksum = Checksum();
}
public static ICMP_PACKET CreateRequestPacket(UInt16 id, UInt16 seq, byte[] data)
{
return new ICMP_PACKET(8, 0, id, seq, data);
}
public static byte[] GetBytesInNetworkOrder(UInt16 number)
{
byte[] bytes = BitConverter.GetBytes(number);
if (BitConverter.IsLittleEndian)
return new byte[] { bytes[1], bytes[0] };
else
return bytes;
}
public static UInt16 ToNetworkOrder(UInt16 number)
{
byte[] networkBytes = GetBytesInNetworkOrder(number);
return BitConverter.ToUInt16(networkBytes, 0);
}
public UInt16 Checksum()
{
int cksum_buffer_length = (int)(Length / 2);
byte [] packetBytes = Serialize(this);
//
// first, convert the serialized bytes into a UInt16 array
// we will use the UInt16 array to do the checksum
//
UInt16[] checksumBuffer = new UInt16[cksum_buffer_length];
int index = 0;
int i = 0;
while (index < Length)
{
checksumBuffer[i] = BitConverter.ToUInt16(packetBytes,index);
index += 2;
++i;
}
int checksum = 0;
for (i = 0; i < checksumBuffer.Length; i++)
{
checksum += Convert.ToInt32(checksumBuffer[i]);
}
checksum = (checksum >> 16) + (checksum & 0xffff);
checksum += (checksum >> 16);
return (UInt16)(~checksum);
}
public byte[] Serialize()
{
return Serialize(this);
}
public static byte[] Serialize(ICMP_PACKET packet)
{
// first, find out how many bytes to allocate for the serialized packet
int packet_size = packet.Length;
bool isLittleEndian = BitConverter.IsLittleEndian;
UInt16 cksum = packet.i_cksum;
UInt16 id = packet.i_id;
UInt16 seq = packet.i_seq;
//allocate the byte array
byte[] packetArray = new byte[packet_size];
// now serialize the packet into the array.
int index = 0;
packetArray[index++] = packet.i_type;
packetArray[index++] = packet.i_code;
// the checksum is 16 bits.
byte[] temp = BitConverter.GetBytes(cksum);
// copy it into the packetArray at the required offset
Array.Copy(temp, 0, packetArray, index, temp.Length);
index += 2;
// similarly, copy the rest.
temp = BitConverter.GetBytes(id);
Array.Copy(temp, 0, packetArray, index, temp.Length);
index += 2;
// copy seq#
temp = BitConverter.GetBytes(seq);
Array.Copy(temp, 0, packetArray, index, temp.Length);
index += 2;
// copy payload
if (packet.PayloadLength > 0)
{
Array.Copy(packet.data, 0, packetArray, index, packet.PayloadLength);
}
return packetArray;
}
};
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();
//}
byte [] data = new byte[32];
int j = 0;
for (int i = 0; i < 32; i++)
{
data[i] = (byte)((int)'a' + j);
j = (j + 1) % 23;
}
ICMP_PACKET packet = ICMP_PACKET.CreateRequestPacket(0x200, 0x9603, data);
byte[] serialized = packet.Serialize();
for (int i = 0; i < serialized.Length; i++)
{