|
Creating Protocols Header Definition Class (C++) 1
Create a new class library project and you might use ProtocolHeaderDefinitionCP as the name. This class will be used in the next related TCP/IP program examples. It contains the header definition of several protocols such as TCP, UDP and IP (for both IPv4 and IPv6). This definition class is quite long, so be careful.
|
Add/edit the following using directives at the top of the file.
using namespace System; using namespace System::Net; using namespace System::Net::Sockets; using namespace System::Collections; |
Rename the default given class to WinsockIoctl.
Add the following code for the WinsockIoctl class.
public ref class WinsockIoctl { /// <summary> /// An interface query takes the socket address of a remote destination and /// returns the local interface that destination is reachable on. /// </summary> public: static const int SIO_ROUTING_INTERFACE_QUERY = -939524076; // otherwise equal to 0xc8000014 /// <summary> /// The address list query returns a list of all local interface addresses. /// /// </summary> public: static const int SIO_ADDRESS_LIST_QUERY = 0x48000016; }; |
Add another class, SocketaddrConvert and its code.
public ref class SockaddrConvert { /// <summary> /// This routine converts an IPEndPoint into a byte array that represents the /// underlying sockaddr structure of the correct type. Currently this routine /// supports only IPv4 and IPv6 socket address structures. /// </summary> /// <param name="endPoint">IPEndPoint to convert to a binary form</param> /// <returns>Binary array of the serialized socket address structure</returns> public: static array<Byte>^ GetSockaddrBytes(IPEndPoint^ endPoint) { SocketAddress^ socketAddress = endPoint->Serialize(); array<Byte>^ sockaddrBytes;
sockaddrBytes = gcnew array<Byte>(socketAddress->Size);
for (int i = 0; i < socketAddress->Size; i++) { sockaddrBytes[i] = socketAddress[i]; } return sockaddrBytes; }
/// <summary> /// This routine converts the binary representation of a sockaddr structure back /// into an IPEndPoint object. This is done by looking at the first 2 bytes of the /// serialized byte array which always indicate the address family of the underlying /// structure. From this we can construct the appropriate IPEndPoint object. /// </summary> /// <param name="sockaddrBytes"></param> /// <returns></returns> public: static IPEndPoint^ GetEndPoint(array<Byte>^ sockaddrBytes) { IPEndPoint^ unpackedEndpoint = nullptr; IPAddress^ unpackedAddress; unsigned short addressFamily, unpackedPort;
// Reconstruct the 16-bit (short) value representing the address family addressFamily = BitConverter::ToUInt16(sockaddrBytes, 0);
if (addressFamily == 2) // AF_INET { array<Byte>^ addressBytes = gcnew array<Byte>(4);
unpackedPort = BitConverter::ToUInt16(sockaddrBytes, 2); unpackedAddress = gcnew IPAddress(BitConverter::ToUInt32(sockaddrBytes, 4)); unpackedEndpoint = gcnew IPEndPoint(unpackedAddress, unpackedPort); } else if (addressFamily == 23) // AF_INET6 { array<Byte>^ addressBytes = gcnew array<Byte>(16); unpackedPort = BitConverter::ToUInt16(sockaddrBytes, 2); Array::Copy(sockaddrBytes, 8, addressBytes, 0, 16); unpackedAddress = gcnew IPAddress(addressBytes); unpackedEndpoint = gcnew IPEndPoint(unpackedAddress, unpackedPort); } else { Console::WriteLine("GetEndPoint: Unknown address family: {0}", addressFamily); } return unpackedEndpoint; } }; |
Next, add the abstract class, ProtocolHeader.
public ref class ProtocolHeader abstract { /// <summary> /// This abstracted method returns a byte array that is the protocol /// header and the payload. This is used by the BuildPacket method /// to build the entire packet which may consist of multiple headers /// and data payload. /// </summary> /// <param name="payLoad">The byte array of the data encapsulated in this header</param> /// <returns>A byte array of the serialized header and payload</returns> public: virtual array<Byte>^ GetProtocolPacketBytes(array<Byte>^ payLoad) abstract;
/// <summary> /// This method builds the entire packet to be sent on the socket. It takes /// an ArrayList of all encapsulated headers as well as the payload. The /// ArrayList of headers starts with the outermost header towards the /// innermost. For example when sending an IPv4/UDP packet, the first entry /// would be the IPv4 header followed by the UDP header. The byte payload of /// the UDP packet is passed as the second parameter. /// </summary> /// <param name="headerList">An array list of all headers to build the packet from</param> /// <param name="payLoad">Data payload appearing after all the headers</param> /// <returns>Returns a byte array representing the entire packet</returns> public: array<Byte>^ BuildPacket(ArrayList^ headerList, array<Byte>^ payLoad) { ProtocolHeader^ protocolHeader; array<Byte>^ newPayload = nullptr;
// Traverse the array in reverse order since the outer headers may need // the inner headers and payload to compute checksums on. for (int i = headerList->Count - 1; i >= 0; i--) { protocolHeader = (ProtocolHeader^)headerList[i]; newPayload = protocolHeader->GetProtocolPacketBytes(payLoad); // The payLoad for the next iteration of the loop is now any // encapsulated headers plus the original payload data. payLoad = newPayload; }
return payLoad; }
/// <summary> /// This is a simple method for computing the 16-bit one's complement /// checksum of a byte buffer. The byte buffer will be padded with /// a zero byte if an uneven number. /// </summary> /// <param name="payLoad">Byte array to compute checksum over</param> /// <returns></returns> public: static unsigned short ComputeChecksum(array<Byte>^ payLoad) { unsigned int xsum = 0; unsigned short shortval = 0, hiword = 0, loword = 0;
// Sum up the 16-bits for (int i = 0; i < payLoad->Length / 2; i++) { hiword = (unsigned short)(((unsigned short)payLoad[i * 2]) << 8); loword = (unsigned short)payLoad[(i * 2) + 1]; shortval = (unsigned short)(hiword | loword); xsum = xsum + (unsigned int)shortval; } // Pad if necessary if ((payLoad->Length % 2) != 0) { xsum += (unsigned int)payLoad[payLoad->Length - 1]; }
xsum = ((xsum >> 16) + (xsum & 0xFFFF)); xsum = (xsum + (xsum >> 16)); shortval = (unsigned short)(~xsum);
return shortval; }
/// <summary> /// Utility function for printing a byte array into a series of 4 byte hex digits with /// four such hex digits displayed per line. /// </summary> /// <param name="printBytes">Byte array to display</param> public: static void PrintByteArray(array<Byte>^ printBytes) { int index = 0;
while (index < printBytes->Length) { for (int i = 0; i < 4; i++) { if (index >= printBytes->Length) break;
for (int j = 0; j < 4; j++) { if (index >= printBytes->Length) break; Console::Write("{0}", printBytes[index++].ToString("x2")); } Console::Write(" "); } Console::WriteLine(""); } } }; |
Then add a derived class, Ipv4Header. This is IPv4 definition.
/// <summary> /// This is the IPv4 protocol header. /// </summary> public ref class Ipv4Header : ProtocolHeader { private: Byte ipVersion; // actually only 4 bits Byte ipLength; // actually only 4 bits Byte ipTypeOfService; unsigned short ipTotalLength; unsigned short ipId; unsigned short ipOffset; Byte ipTtl; Byte ipProtocol; unsigned short ipChecksum; IPAddress^ ipSourceAddress; IPAddress^ ipDestinationAddress; public: static int Ipv4HeaderLength = 20;
/// <summary> /// Simple constructor that initializes the members to zero. /// </summary> public: Ipv4Header() { ipVersion = 4; ipLength = (Byte)Ipv4HeaderLength; // Set the property so it will convert properly ipTypeOfService = 0; ipId = 0; ipOffset = 0; ipTtl = 1; ipProtocol = 0; ipChecksum = 0; ipSourceAddress = IPAddress::Any; ipDestinationAddress = IPAddress::Any; }
/// <summary> /// Gets and sets the IP version. This should be 4 to indicate the IPv4 header. /// </summary> public: property Byte Version { Byte get() { return ipVersion; } void set(Byte value) { ipVersion = value; } }
/// <summary> /// Gets and sets the length of the IPv4 header. This property takes and returns /// the number of bytes, but the actual field is the number of 32-bit DWORDs /// (the IPv4 header is a multiple of 4-bytes). /// </summary> public: property Byte Length { Byte get() { return (Byte)(ipLength * 4); } void set(Byte value) { ipLength = (Byte)(value / 4); } }
/// <summary> /// Gets and sets the type of service field of the IPv4 header. Since it /// is a byte, no byte order conversion is required. /// </summary> public: property Byte TypeOfService { Byte get() { return ipTypeOfService; } void set(Byte value) { ipTypeOfService = value; } }
/// <summary> /// Gets and sets the total length of the IPv4 header and its encapsulated /// payload. Byte order conversion is required. /// </summary> public: property unsigned short TotalLength { unsigned short get() { return (unsigned short)IPAddress::NetworkToHostOrder((short)ipTotalLength); } void set(unsigned short value) { ipTotalLength = (unsigned short)IPAddress::HostToNetworkOrder((short)value); } }
/// <summary> /// Gets and sets the ID field of the IPv4 header. Byte order conversion is required. /// </summary> public: property unsigned short Id { unsigned short get() { return (unsigned short)IPAddress::NetworkToHostOrder((short)ipId); } void set(unsigned short value) { ipId = (unsigned short)IPAddress::HostToNetworkOrder((short)value); } }
/// <summary> /// Gets and sets the offset field of the IPv4 header which indicates if /// IP fragmentation has occurred. /// </summary> public: property unsigned short Offset { unsigned short get() { return (unsigned short)IPAddress::NetworkToHostOrder((short)ipOffset); } void set(unsigned short value) { ipOffset = (unsigned short)IPAddress::HostToNetworkOrder((short)value); } }
/// <summary> /// Gets and sets the time-to-live (TTL) value of the IP header. This field /// determines how many router hops the packet is valid for. /// </summary> public: property Byte Ttl { Byte get() { return ipTtl; } void set(Byte value) { ipTtl = value; } }
/// <summary> /// Gets and sets the protocol field of the IPv4 header. This field indicates /// what the encapsulated protocol is. /// </summary> public: property Byte Protocol { Byte get() { return ipProtocol; } void set(Byte value) { ipProtocol = value; } }
/// <summary> /// Gets and sets the checksum field of the IPv4 header. For the IPv4 header, the /// checksum is calculated over the header and payload. Note that this field isn't /// meant to be set by the user as the GetProtocolPacketBytes method computes the /// checksum when the packet is built. /// </summary> public: property unsigned short Checksum { unsigned short get() { return (unsigned short)IPAddress::NetworkToHostOrder((short)ipChecksum); } void set(unsigned short value) { ipChecksum = (unsigned short)IPAddress::HostToNetworkOrder((short)value); } }
/// <summary> /// Gets and sets the source IP address of the IPv4 packet. This is stored /// as an IPAddress object which will be serialized to the appropriate /// byte representation in the GetProtocolPacketBytes method. /// </summary> public: property IPAddress^ SourceAddress { IPAddress^ get() { return ipSourceAddress; } void set(IPAddress^ value) { ipSourceAddress = value; } }
/// <summary> /// Gets and sets the destination IP address of the IPv4 packet. This is stored /// as an IPAddress object which will be serialized to the appropriate byte /// representation in the GetProtocolPacketBytes method. /// </summary> public: property IPAddress^ DestinationAddress { IPAddress^ get() { return ipDestinationAddress; } void set(IPAddress^ value) { ipDestinationAddress = value; } }
/// <summary> /// This routine creates an instance of the Ipv4Header class from a byte /// array that is a received IGMP packet. This is useful when a packet /// is received from the network and the header object needs to be /// constructed from those values. /// </summary> /// <param name="ipv4Packet">Byte array containing the binary IPv4 header</param> /// <param name="bytesCopied">Number of bytes used in header</param> /// <returns>Returns the Ipv4Header object created from the byte array</returns> public: static Ipv4Header^ Create(array<Byte>^ ipv4Packet, int bytesCopied) { Ipv4Header^ ipv4Header = gcnew Ipv4Header(); // Make sure byte array is large enough to contain an IPv4 header if (ipv4Packet->Length < Ipv4Header::Ipv4HeaderLength) return nullptr;
// Decode the data in the array back into the class properties ipv4Header->ipVersion = (Byte)((ipv4Packet[0] >> 4) & 0xF); ipv4Header->ipLength = (Byte)(ipv4Packet[0] & 0xF); ipv4Header->ipTypeOfService = ipv4Packet[1]; ipv4Header->ipTotalLength = BitConverter::ToUInt16(ipv4Packet, 2); ipv4Header->ipId = BitConverter::ToUInt16(ipv4Packet, 4); ipv4Header->ipOffset = BitConverter::ToUInt16(ipv4Packet, 6); ipv4Header->ipTtl = ipv4Packet[8]; ipv4Header->ipProtocol = ipv4Packet[9]; ipv4Header->ipChecksum = BitConverter::ToUInt16(ipv4Packet, 10);
ipv4Header->ipSourceAddress = gcnew IPAddress(BitConverter::ToUInt32(ipv4Packet, 12)); ipv4Header->ipDestinationAddress = gcnew IPAddress(BitConverter::ToUInt32(ipv4Packet, 16)); bytesCopied = ipv4Header->Length; return ipv4Header; }
/// <summary> /// This routine takes the properties of the IPv4 header and marshalls them into /// a byte array representing the IPv4 header that is to be sent on the wire. /// </summary> /// <param name="payLoad">The encapsulated headers and data</param> /// <returns>A byte array of the IPv4 header and payload</returns> public: virtual array<Byte>^ GetProtocolPacketBytes(array<Byte>^ payLoad) override { array<Byte>^ ipv4Packet; array<Byte>^ byteValue; int index = 0;
// Allocate space for the IPv4 header plus payload ipv4Packet = gcnew array<Byte>(Ipv4HeaderLength + payLoad->Length); ipv4Packet[index++] = (Byte)((ipVersion << 4) | ipLength); ipv4Packet[index++] = ipTypeOfService;
byteValue = BitConverter::GetBytes(ipTotalLength); Array::Copy(byteValue, 0, ipv4Packet, index, byteValue->Length); index += byteValue->Length;
byteValue = BitConverter::GetBytes(ipId); Array::Copy(byteValue, 0, ipv4Packet, index, byteValue->Length); index += byteValue->Length;
byteValue = BitConverter::GetBytes(ipOffset); Array::Copy(byteValue, 0, ipv4Packet, index, byteValue->Length); index += byteValue->Length;
ipv4Packet[index++] = ipTtl; ipv4Packet[index++] = ipProtocol; ipv4Packet[index++] = 0; // Zero the checksum for now since we will ipv4Packet[index++] = 0; // calculate it later
// Copy the source address byteValue = ipSourceAddress->GetAddressBytes(); Array::Copy(byteValue, 0, ipv4Packet, index, byteValue->Length); index += byteValue->Length;
// Copy the destination address byteValue = ipDestinationAddress->GetAddressBytes(); Array::Copy(byteValue, 0, ipv4Packet, index, byteValue->Length); index += byteValue->Length;
// Copy the payload Array::Copy(payLoad, 0, ipv4Packet, index, payLoad->Length); index += payLoad->Length;
// Compute the checksum over the entire packet (IPv4 header + payload) Checksum = ComputeChecksum(ipv4Packet);
// Set the checksum into the built packet byteValue = BitConverter::GetBytes(ipChecksum); Array::Copy(byteValue, 0, ipv4Packet, 10, byteValue->Length);
return ipv4Packet; } }; |