Introduction
In this article, I will discuss the working of a simple network sniffer which can parse IP, TCP, UDP, and DNS packets.
Capturing the Packets
mainSocket = newSocket(AddressFamily.InterNetwork, SocketType.Raw,
ProtocolType.IP);
mainSocket.Bind(newIPEndPoint(IPAddress.Parse(cmbInterfaces.Text),0));
mainSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, true);
byte[] byTrue = newbyte[4]{1, 0, 0, 0};
byte[] byOut = newbyte[4];
mainSocket.IOControl(IOControlCode.ReceiveAll, byTrue, byOut);
mainSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None,
newAsyncCallback(OnReceive), null);
For capturing the packets, we use a raw socket and bind it to the IP address. After setting the proper options for the socket, we then call the IOControl method on it. Notice that IOControl is analogous to the Winsock2WSAIoctl method. The IOControlCode.ReceiveAll implies that all incoming and outgoing packets on the particular interface be captured.
The second parameter passed to IOControl with IOControlCode.ReceiveAll should be TRUE so an array byTrue is created and passed to it (thanks to Leonid Molochniy for this). Next we start receiving all packets asynchronously.
Analysing the Packets
The IP datagram encapsulates the TCP and UDP packets. This further contains the data sent by the application layer protocols such as DNS, HTTP, FTP, SMTP, SIP, etc. Thus a TCP packet is received inside the IP datagram, like this:
+-----------+------------+--------------------+
| IP header | TCP header | Data |
+-----------+------------+--------------------+
So the first thing that we need to do is to parse the IP header. The stripped version of the IP header class is shown below, the comments describe the things as they happen.
public classIPHeader
{
private byte byVersionAndHeaderLength; private byte byDifferentiatedServices; private ushort usTotalLength; private ushort usIdentification; private ushort usFlagsAndOffset; private byte byTTL; private byte byProtocol; private short sChecksum; private uint uiSourceIPAddress; private uint uiDestinationIPAddress;
private byte byHeaderLength; private byte[] byIPData = new byte[4096]; public IPHeader(byte[] byBuffer, int nReceived)
{
try
{
MemoryStream memoryStream = newMemoryStream(byBuffer, 0, nReceived);
BinaryReader binaryReader = newBinaryReader(memoryStream);
byVersionAndHeaderLength = binaryReader.ReadByte();
byDifferentiatedServices = binaryReader.ReadByte();
usTotalLength =
(ushort) IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());
usIdentification =
(ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());
usFlagsAndOffset =
(ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());
byTTL = binaryReader.ReadByte();
byProtocol = binaryReader.ReadByte();
sChecksum = IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());
uiSourceIPAddress = (uint)(binaryReader.ReadInt32());
uiDestinationIPAddress = (uint)(binaryReader.ReadInt32());
byHeaderLength = byVersionAndHeaderLength;
byHeaderLength <<= 4;
byHeaderLength >>= 4;
byHeaderLength *= 4;
Array.Copy(byBuffer,
byHeaderLength, byIPData, 0, usTotalLength - byHeaderLength);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "MJsniff", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
}
Firstly, the class contains the data members corresponding to the fields of the IP header. Kindly see RFC 791 for a detailed explanation of the IP header and its fields. The constructor of the class takes the bytes received and creates a MemoryStream on the received bytes and then creates a BinaryReader to read the data from the MemoryStream byte-by-byte. Also note that the data received from the network is in big-endian form so we use the IPAddress.NetworkToHostOrder to correct the byte ordering. This has to be done for all non-byte data members.
The TCP, UDP headers are also parsed in an identical fashion, the only difference is that they are read from the point where the IP header ends. As an example of parsing the application layer protocols, the attached project can detect DNS packets as well.
References
Updates
- 9th February, 2008: Fixed the code to capture both incoming and outgoing packets. Thanks to Darren_vms for pointing this in the comments.