Source/Tx.Network/Ip/PacketParser.cs (100 lines of code) (raw):

namespace Tx.Network { using System; using System.Net.Sockets; /// <summary> /// Class that provides methods for parsing of IP packets. /// </summary> /// <remarks> /// For many of the operations in this class a set of bits less than one (1) byte /// is needed. Bitwise operators and masks are needed to extract those bit values (0 or 1) /// from a byte. /// /// All data from the network is received as Big-Endian.Windows is Little-Endian. /// You can validate this with BitConverter.IsLittleEndian /// /// Any sequence of bytes greater than 1 from the network has the most-significant /// byte at the LAST (aka End) position in the sequence. Network bytes are ALWAYS Big-Endian. /// /// To do the "right thing" in Windows'-little-endian-world: /// - A SINGULAR byte does not require any changes /// /// - A CHARACTER array (aka string) or other Byte-Array requires no changes /// -- ASCII works this way. /// -- Other encodings may vary so look up the rules. /// /// - Any NUMBER comprised of more than 1 byte from the Network is Big-Endian /// -- Therefore to make it work in Windows Network-to-Host-Order must be performed. /// -- All numbers are even multiples of a byte ( greater than 1) /// -- For example: /// | Network bytes will appear as [Ox80, 0x56] == 32854 (if cast to Int16 this will be a negative number) /// | NTHO in Windows will provide[0x56, 0x80] == 22144 /// /// Please refer to the usage of /// -System.Net.IPAddress.NetworkToHostOrder(). /// -- NOTE this operates on SIGNED Integers(16,32,64) only. /// -- All Internet header numbers are UNSIGNED Integers(16,32,64) so this makes no sense to me. /// - System.BitConverter.ToInt16() or .ToInt32() /// /// Typically the implementation would look like this: /// - (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(buffer, 2)); /// - At zero-based-index 2 for (byte[])buffer of arbitrary size > 5 bytes /// - Return type is SHORT so a cast to USHORT is required while inside /// Internet Headers. /// !!! /// !WARNING: the Bytes in an IP-Address (v4 or v6) are READ as a Byte-Array and not a Number! /// !NToH Should not be used on Addresses! /// !!! /// </remarks> public static class PacketParser { /// <summary> /// Parses the specified binary data. /// </summary> /// <param name="data">The data.</param> /// <returns>A new IP packet instance.</returns> /// <exception cref="System.ArgumentException">if data is empty.</exception> public static IpPacket Parse(ArraySegment<byte> data) { if (data.Count == 0) { throw new ArgumentException("Value cannot be an empty collection.", "data"); } return Parse(DateTimeOffset.UtcNow, true, data.Array, data.Offset, data.Count); } /// <summary> /// Parses the specified binary data. /// </summary> /// <param name="receivedTime">The received time.</param> /// <param name="reuseOriginalBuffer">if set to <c>true</c> then reuse original buffer.</param> /// <param name="packetBytes">The binary data.</param> /// <param name="offset">The offset in the binary data.</param> /// <param name="packetBytesLength">Length of the packet bytes.</param> /// <returns>A new IP packet instance.</returns> /// <exception cref="System.ArgumentNullException">packetBytes is empty.</exception> /// <exception cref="System.NotSupportedException">IPv4 only currently supported.</exception> public static IpPacket Parse( DateTimeOffset receivedTime, bool reuseOriginalBuffer, byte[] packetBytes, int offset, int packetBytesLength) { if (packetBytes == null) { throw new ArgumentNullException("packetBytes"); } var ipVers = packetBytes.ReadBits(offset, 0, 4); //bits 0 to 3 if (ipVers != 4) throw new NotSupportedException("IPv4 only currently supported"); //ensure this is v4 var internetHeaderLength = packetBytes.ReadBits(offset++, 4, 4); //bits 4 to 7 var dscpValue = packetBytes.ReadBits(offset, 0, 6); //8 to 13 var explicitCongestionNotice = packetBytes.ReadBits(offset++, 6, 2); //14 to 15 var ipPacketLength = packetBytes.ReadNetOrderUShort(offset); //16 to 31 offset += 2; var fragmentGroupId = packetBytes.ReadNetOrderUShort(offset); //32 to 47 offset += 2; var ipHeaderFlags = packetBytes.ReadBits(offset, 0, 3); //48 to 50 var fragmentOffset = packetBytes.ReadNetOrderUShort(offset, 3, 13); //51 to 63 offset += 2; var timeToLive = packetBytes[offset++]; //64 to 71 var protocolNumber = packetBytes[offset++]; //72 to 79 var protocol = (ProtocolType)protocolNumber; //Enum var packetHeaderChecksum = packetBytes.ReadNetOrderUShort(offset); //80 to 95 offset += 2; var sourceIpAddress = packetBytes.ReadIpAddress(offset); //96 to 127 offset += 4; var destinationIpAddress = packetBytes.ReadIpAddress(offset); //128 to 160 offset += 4; var ipOptions = default(ArraySegment<byte>); if (internetHeaderLength > 5) //161 and up { int length = (internetHeaderLength - 5) * 4; if (reuseOriginalBuffer) { ipOptions = new ArraySegment<byte>(packetBytes, offset, length); } else { var ipOptionsDataArray = new byte[length]; Array.Copy(packetBytes, offset, ipOptionsDataArray, 0, length); ipOptions = new ArraySegment<byte>(ipOptionsDataArray); } offset += length; } var packetData = default(ArraySegment<byte>); //IpHeader in bytes is 4*IHL bytes long if (ipPacketLength > 4 * internetHeaderLength) { int length = ipPacketLength - (internetHeaderLength * 4); if (reuseOriginalBuffer) { packetData = new ArraySegment<byte>(packetBytes, offset, length); } else { var packetDataArray = new byte[length]; Array.Copy(packetBytes, offset, packetDataArray, 0, length); packetData = new ArraySegment<byte>(packetDataArray); } } return new IpPacket { PacketHeader = new IpPacketHeader( sourceIpAddress, destinationIpAddress, false, internetHeaderLength, dscpValue, explicitCongestionNotice, ipPacketLength, fragmentGroupId, ipHeaderFlags, fragmentOffset, timeToLive, packetHeaderChecksum), IpOptions = ipOptions, ReceivedTime = receivedTime, ProtocolType = protocol, PacketData = packetData }; } } }