Source/Tx.Network/Udp/NetworkTransformExtensions.cs (83 lines of code) (raw):
namespace Tx.Network
{
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
public static class NetworkTransformExtentions
{
///// <summary>
///// Takes an IpPacket object and encodes the IP header for transmission on the network in a byte array. Includes computing checksums.
///// </summary>
///// <param name="Packet">Packet object for encoding.</param>
///// <returns>The IP header in network order byte array with checksums included.</returns>
//public static byte[] PacketHeaderToWireBytes(this IpPacket Packet)
//{
// using (var builderStream = new MemoryStream())
// {
// //ipVers bits 0 to 3
// //InternetHeaderLength bits 4 to 7
// var ipversion = Packet.IpVersion == NetworkInterfaceComponent.IPv4 ? 4 : 6;
// var byte0 = (byte)BitConverter.GetBytes((ipversion << 4) + Packet.InternetHeaderLength)[0];
// builderStream.WriteByte(byte0);
// //DscpValue 8 to 13
// //ExplicitCongestionNotice 14 to 15
// var byte1 = (byte)BitConverter.GetBytes(((int)Packet.DscpValue << 2) + Packet.ExplicitCongestionNotice)[0];
// builderStream.WriteByte(byte1);
// //IpPacketLength 16 to 31
// var byte23 = BitConverter.GetBytes(NetworkOrderUshort(Packet.IpPacketLength));
// builderStream.Write(byte23, 0, 2);
// //FragmentGroupId 32 to 47
// var byte45 = BitConverter.GetBytes(NetworkOrderUshort(Packet.FragmentGroupId));
// builderStream.Write(byte45, 0, 2);
// //IpHeaderFlags 48 to 50
// //FragmentOffset 51 to 63
// var byte67 = BitConverter.GetBytes(((int)Packet.IpHeaderFlags << 13) + Packet.FragmentOffset);
// builderStream.WriteByte(byte67[0]);
// builderStream.WriteByte(byte67[1]);
// //TimeToLive 64 to 71
// builderStream.WriteByte(Packet.TimeToLive);
// //ProtocolNumber 72 to 79
// builderStream.WriteByte(Packet.ProtocolNumber);
// //PacketHeaderChecksum 80 to 95
// builderStream.Write(BitConverter.GetBytes(0), 0, 2); //put all zeros in here and calculate it below.
// //SourceIpAddress 96 to 127
// builderStream.Write(Packet.SourceIpAddress.GetAddressBytes(), 0, 4);
// //DestinationIpAddress 128 to 160
// builderStream.Write(Packet.DestinationIpAddress.GetAddressBytes(), 0, 4);
// if (Packet.IpOptions != null)
// {
// builderStream.Write(Packet.IpOptions, 0, Packet.IpOptions.Length);
// }
// var headerBytes = builderStream.ToArray();
// var sum = GetInternetChecksum(headerBytes);
// Array.Copy(BitConverter.GetBytes(sum), 0, headerBytes, 10, 2);
// return headerBytes;
// }
//}
///// <summary>
///// Takes a UDP Datagram object and encodes the UDP header for transmission on the network. Assumes UDP checksum is computed.
///// </summary>
///// <param name="input">Datagram object to encode.</param>
///// <returns>UDP header encoded in network order.</returns>
internal static byte[] UdpHeaderToWireBytes(this IUdpDatagram input)
{
//udp header is 8 bytes
using (var outPacket = new MemoryStream())
{
outPacket.Write(BitConverter.GetBytes(NetworkOrderUshort(input.UdpDatagramHeader.SourcePort)), 0, 2);
outPacket.Write(BitConverter.GetBytes(NetworkOrderUshort(input.UdpDatagramHeader.DestinationPort)), 0, 2);
outPacket.Write(BitConverter.GetBytes(NetworkOrderUshort(input.UdpDatagramHeader.UdpLength)), 0, 2);
outPacket.Write(BitConverter.GetBytes(NetworkOrderUshort(input.UdpDatagramHeader.UdpCheckSum)), 0, 2); //should be zero if we haven't calculated it yet.
return outPacket.ToArray();
}
}
///// <summary>
///// Takes an IpPacket object and encodes it for transmission on the network in a byte array. Includes computing checksums.
///// </summary>
///// <param name="packet">Packet object for encoding.</param>
///// <returns>Network order byte array with IP checksum included ready to send on a raw socket.</returns>
//public static byte[] ToWireBytes(this IpPacket packet)
//{
// using (var builderStream = new MemoryStream())
// {
// var header = PacketHeaderToWireBytes(packet);
// builderStream.Write(header, 0, header.Length);
// builderStream.Write(packet.PacketData, 0, packet.PacketData.Length);
// return builderStream.ToArray();
// }
//}
///// <summary>
///// Takes an UDPDatagram object and encodes it for transmission on the network in a byte array. Includes computing checksums.
///// </summary>
///// <param name="input">UdpDatagram object for encoding.</param>
///// <returns>Network order byte array with IP and UDP checksums included ready to send on a raw socket.</returns>
//public static byte[] ToWirebytes(this IUdpDatagram input)
//{
// using (var builderStream = new MemoryStream())
// {
// var ipHeader = PacketHeaderToWireBytes(input);
// builderStream.Write(ipHeader, 0, ipHeader.Length);
// var udpCk = GetUdpCheckSum(input);
// input.UdpCheckSum = NetworkOrderUshort(udpCk);
// builderStream.Write(UdpHeaderToWireBytes(input), 0, 8); //bytes now in network order here
// builderStream.Write(input.UdpData, 0, input.UdpData.Length);
// return builderStream.ToArray();
// }
//}
///// <summary>
///// Per RFC 768, compute a ones complement sum over the sum of the 16bit words in the byte array. If the array is too short a zero-pad byte is added.
///// </summary>
///// <param name="input">Byte array to compute the checksum on</param>
///// <returns>16 bit integer sum</returns>
///// <remarks>This is not a CRC but is the checksum used for IP headers, UDP datagrams or TCP segments.
///// Note that if the 16 bit integers on input are in network order, the answer will also be in network order.</remarks>
internal static ushort GetInternetChecksum(byte[] input) //input should be in Host order
{
//make a copy because we may need to resize it; GC should get this on return
byte[] _input = new byte[input.Length];
Array.Copy(input, _input, input.Length);
//zero pad
if (_input.Length % 2 != 0)
{
Array.Resize(ref _input, _input.Length + 1);
_input[_input.Length] = 0;
}
uint firstSum = 0;
for (int i = 0; i < _input.Length; i += 2)
{
firstSum += (uint)_input.ReadUShort(i);
}
uint secondSum = 0;
byte[] firstSumBytes = BitConverter.GetBytes(firstSum);
for (int j = 0; j < 4; j += 2)
{
secondSum += (uint)firstSumBytes.ReadUShort(j);
}
if ((secondSum & 0xffff0000) > 0)
{
secondSum = secondSum & 0xffff;
secondSum += 1;
}
ushort sum = (ushort)secondSum; //numeric overflow is handled by the above if block.
return (ushort)(~sum & 0xFFFF);
}
/// <summary>
/// From a UDP datagram object, creates the UDP pseudoheader that is used to compute the UDP checksum per RFC 768
/// </summary>
/// <param name="input">The datagram object to use in encoding</param>
/// <returns>Byte array of the pseudo header in Network Order</returns>
internal static byte[] UdpPseudoHeader(this IUdpDatagram input)
{
byte zeroes = 0;
byte protocol = (byte)ProtocolType.Udp;
using (var builder = new MemoryStream())
{
builder.Write(input.PacketHeader.SourceIpAddress.GetAddressBytes(), 0, 4);
builder.Write(input.PacketHeader.DestinationIpAddress.GetAddressBytes(), 0, 4);
builder.WriteByte(zeroes);
builder.WriteByte(protocol);
builder.Write(BitConverter.GetBytes(NetworkOrderUshort(input.UdpDatagramHeader.UdpLength)), 0, 2);
return builder.ToArray();
}
}
/// <summary>
/// Uses the UDP Pseudoheader, UDP Header, and UDP payload to compute the checksum used in UDP transmission, per RFC 768
/// </summary>
/// <param name="input">The UdpDatagram object to check</param>
/// <returns>16bit integer sum in network order.</returns>
public static ushort GetUdpCheckSum(this IUdpDatagram input)
{
using (var udpCk = new MemoryStream())
{
var udpPh = UdpPseudoHeader(input);
udpCk.Write(udpPh, 0, udpPh.Length);
udpCk.Write(BitConverter.GetBytes(NetworkOrderUshort(input.UdpDatagramHeader.SourcePort)), 0, 2);
udpCk.Write(BitConverter.GetBytes(NetworkOrderUshort(input.UdpDatagramHeader.DestinationPort)), 0, 2);
udpCk.Write(BitConverter.GetBytes(NetworkOrderUshort(input.UdpDatagramHeader.UdpLength)), 0, 2);
udpCk.WriteByte(0);
udpCk.WriteByte(0);
var udpData = input.Data.AsByteArraySegment();
udpCk.Write(udpData.Array, udpData.Offset, udpData.Count);
return GetInternetChecksum(udpCk.ToArray());
}
}
internal static ushort NetworkOrderUshort(ushort input)
{
return (ushort)IPAddress.HostToNetworkOrder((short)input);
}
}
}