Using IP_HDRINCL

The one limitation of raw sockets is that you can work only with certain protocols that are already defined, such as ICMP and IGMP. You cannot create a raw socket with IPPROTO_UDP and manipulate the UDP header; likewise with TCP. To manipulate the IP header as well as either the TCP or UDP header (or any other protocol encapsulated in IP), you must use the IP_HDRINCL socket option with a raw socket. This option allows you to build your own IP header as well as the headers of other protocols.

Additionally, if you want to implement your own protocol scheme that is encapsulated in IP, you can create a raw socket and use the IPPROTO_RAW value as the protocol. This allows you to set the protocol field in the IP header manually and build your own custom protocol header. In this section, we'll take a look at how to build your own UDP packets so that you can gain a good understanding of the steps involved. Once you understand how to manipulate the UDP header, creating your own protocol header or manipulating other protocols encapsulated in IP is fairly trivial.

When you use the IP_HDRINCL option, you are required to fill in the IP header yourself for every send call, as well as the headers of any other protocols wrapped within. The IP header is described in Chapter 9, Figure 9-3, in the section on the IP_HDRINCL option. The UDP header is quite a bit simpler than IP. It is only 8 bytes long and contains only four fields, as shown in Figure 13-5. The first two fields are the source and destination port numbers. They are 16 bits each. The third field is the UDP length, which is the length, in bytes, of the UDP header and data. The fourth field is the checksum, which we will discuss shortly. The last part of the UDP packet is the data.

Figure 13-5. UDP header format

Because UDP is an unreliable protocol, calculating the checksum is optional; however, we will cover it for the sake of completeness. Unlike the IP checksum, which covers only the IP header, the UDP checksum covers the data and also includes part of the IP header. The additional fields required to calculate the UDP checksum are known as a pseudo-header. A pseudo-header is composed of the following items:

  • 32-bit source IP address (IP header)
  • 32-bit destination IP address (IP header)
  • 8-bit field zeroed out
  • 8-bit protocol
  • 16-bit UDP length

Added to these items are the UDP header and data. The method of calculating the checksum is the same as for IP and ICMP: the 16-bit one's complement sum. Because the data can be an odd number, it might be necessary to pad a zero byte to the end of the data in order to calculate the checksum. This pad field is not transmitted as part of the data. Figure 13-6 illustrates all the fields required for the checksum calculation. The first three 32-bit words make up the UDP pseudo-header. Following this is the UDP header and its data. Notice that because the checksum is calculated on 16-bit values, the data might need to be padded with a zero byte.

Figure 13-6. UDP pseudo-header

Our example program in Figure 13-7 simply sends a UDP packet to any given destination IP and port from any source IP and port of your choice. The first step is to create a raw socket and set the IP_HDRINCL flag:

 SOCKET s; BOOL bOpt; s = WSASocket(AF_INET, SOCK_RAW, IPPROTO_UDP, NULL, 0, WSA_FLAG_OVERLAPPED); ret = setsockopt(s, IPPROTO_IP, IP_HDRINCL, (char *)&bOpt, sizeof(bOpt)); 

Note that we created a raw socket of protocol IPPROTO_UDP. This works only on Windows 2000 because it also requires the IP_HDRINCL option to be set. The example, Iphdrinc.c, illustrates how to use raw sockets and the IP_HDRINCL option to manipulate the IP and UDP headers on outgoing packets.

Figure 13-7 Raw UDP example

 // Module Name: Iphdrinc.c // #pragma pack(1) #define WIN32_LEAN_AND_MEAN #include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> #include <stdlib.h> #define MAX_MESSAGE 4068 #define MAX_PACKET 4096 // // Set up some default values // #define DEFAULT_PORT 5150 #define DEFAULT_IP "10.0.0.1" #define DEFAULT_COUNT 5 #define DEFAULT_MESSAGE "This is a test" // // Define the IP header. Make the version and length fields one // character since we can't declare two 4-bit fields without // the compiler aligning them on at least a 1-byte boundary. // typedef struct ip_hdr { unsigned char ip_verlen; // IP version & length unsigned char ip_tos; // IP type of service unsigned short ip_totallength; // Total length unsigned short ip_id; // Unique identifier unsigned short ip_offset; // Fragment offset field unsigned char ip_ttl; // Time to live unsigned char ip_protocol; // Protocol(TCP, UDP, etc.) unsigned short ip_checksum; // IP checksum unsigned int ip_srcaddr; // Source address unsigned int ip_destaddr; // Destination address } IP_HDR, *PIP_HDR, FAR* LPIP_HDR; // // Define the UDP header // typedef struct udp_hdr { unsigned short src_portno; // Source port number unsigned short dst_portno; // Destination port number unsigned short udp_length; // UDP packet length unsigned short udp_checksum; // UDP checksum (optional) } UDP_HDR, *PUDP_HDR; // // Global variables // unsigned long dwToIP, // IP to send to dwFromIP; // IP to send from (spoof) unsigned short iToPort, // Port to send to iFromPort; // Port to send from (spoof) DWORD dwCount; // Number of times to send char strMessage[MAX_MESSAGE]; // Message to send // // Description: // Print usage information and exit // void usage(char *progname) { printf("usage: %s [-fp:int] [-fi:str] [-tp:int] [-ti:str]\ [-n:int] [-m:str]\n", progname); printf(" -fp:int From (sender) port number\n"); printf(" -fi:IP From (sender) IP address\n"); printf(" -fp:int To (recipient) port number\n"); printf(" -fi:IP To (recipient) IP address\n"); printf(" -n:int Number of times to read message\n"); printf(" -m:str Size of buffer to read\n\n"); ExitProcess(1); } // // Function: ValidateArgs // // Description: // Parse the command line arguments, and set some global flags to // indicate the actions to perform // void ValidateArgs(int argc, char **argv) { int i; iToPort = DEFAULT_PORT; iFromPort = DEFAULT_PORT; dwToIP = inet_addr(DEFAULT_IP); dwFromIP = inet_addr(DEFAULT_IP); dwCount = DEFAULT_COUNT; strcpy(strMessage, DEFAULT_MESSAGE); for(i = 1; i < argc; i++) { if ((argv[i][0] == '-') || (argv[i][0] == '/')) { switch (tolower(argv[i][1])) { case 'f': // From address switch (tolower(argv[i][2])) { case 'p': if (strlen(argv[i]) > 4) iFromPort = atoi(&argv[i][4]); break; case 'i': if (strlen(argv[i]) > 4) dwFromIP = inet_addr(&argv[i][4]); break; default: usage(argv[0]); break; } break; case 't': // To address switch (tolower(argv[i][2])) { case 'p': if (strlen(argv[i]) > 4) iToPort = atoi(&argv[i][4]); break; case 'i': if (strlen(argv[i]) > 4) dwToIP = inet_addr(&argv[i][4]); break; default: usage(argv[0]); break; } break; case 'n': // Number of times to send message if (strlen(argv[i]) > 3) dwCount = atol(&argv[i][3]); break; case 'm': if (strlen(argv[i]) > 3) strcpy(strMessage, &argv[i][3]); break; default: usage(argv[0]); break; } } } return; } // // Function: checksum // // Description: // This function calculates the 16-bit one's complement sum // for the supplied buffer // USHORT checksum(USHORT *buffer, int size) { unsigned long cksum=0; while (size > 1) { cksum += *buffer++; size -= sizeof(USHORT); } if (size) { cksum += *(UCHAR*)buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >>16); return (USHORT)(~cksum); } // // Function: main // // Description: // First parse command line arguments and load Winsock. Then // create the raw socket and set the IP_HDRINCL option. // Following this, assemble the IP and UDP packet headers by // assigning the correct values and calculating the checksums. // Then fill in the data and send to its destination. // int main(int argc, char **argv) { WSADATA wsd; SOCKET s; BOOL bOpt; struct sockaddr_in remote; // IP addressing structures IP_HDR ipHdr; UDP_HDR udpHdr; int ret; DWORD i; unsigned short iTotalSize, // Lots of sizes needed to fill iUdpSize, // the various headers with iUdpChecksumSize, iIPVersion, iIPSize, cksum = 0; char buf[MAX_PACKET], *ptr = NULL; IN_ADDR addr; // Parse command line arguments, and print them out // ValidateArgs(argc, argv); addr.S_un.S_addr = dwFromIP; printf("From IP: <%s>\n Port: %d\n", inet_ntoa(addr), iFromPort); addr.S_un.S_addr = dwToIP; printf("To IP: <%s>\n Port: %d\n", inet_ntoa(addr), iToPort); printf("Message: [%s]\n", strMessage); printf("Count: %d\n", dwCount); if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { printf("WSAStartup() failed: %d\n", GetLastError()); return -1; } // Creating a raw socket // s = WSASocket(AF_INET, SOCK_RAW, IPPROTO_UDP, NULL, 0,0); if (s == INVALID_SOCKET) { printf("WSASocket() failed: %d\n", WSAGetLastError()); return -1; } // Enable the IP header include option // bOpt = TRUE; ret = setsockopt(s, IPPROTO_IP, IP_HDRINCL, (char *)&bOpt, sizeof(bOpt)); if (ret == SOCKET_ERROR) { printf("setsockopt(IP_HDRINCL) failed: %d\n", WSAGetLastError()); return -1; } // Initalize the IP header // iTotalSize = sizeof(ipHdr) + sizeof(udpHdr) + strlen(strMessage); iIPVersion = 4; iIPSize = sizeof(ipHdr) / sizeof(unsigned long); // // IP version goes in the high-order 4 bits of ip_verlen. The // IP header length (in 32-bit words) goes in the lower 4 bits. // ipHdr.ip_verlen = (iIPVersion << 4) | iIPSize; ipHdr.ip_tos = 0; // IP type of service ipHdr.ip_totallength = htons(iTotalSize); // Total packet len ipHdr.ip_id = 0; // Unique identifier: set to 0 ipHdr.ip_offset = 0; // Fragment offset field ipHdr.ip_ttl = 128; // Time to live ipHdr.ip_protocol = 0x11; // Protocol(UDP) ipHdr.ip_checksum = 0 ; // IP checksum ipHdr.ip_srcaddr = dwFromIP; // Source address ipHdr.ip_destaddr = dwToIP; // Destination address // // Initalize the UDP header // iUdpSize = sizeof(udpHdr) + strlen(strMessage); udpHdr.src_portno = htons(iFromPort) ; udpHdr.dst_portno = htons(iToPort) ; udpHdr.udp_length = htons(iUdpSize) ; udpHdr.udp_checksum = 0 ; // // Build the UDP pseudo-header for calculating the UDP checksum. // The pseudo-header consists of the 32-bit source IP address, // the 32-bit destination IP address, a zero byte, the 8-bit // IP protocol field, the 16-bit UDP length, and the UDP // header itself along with its data (padded with a 0 if // the data is odd length). // iUdpChecksumSize = 0; ptr = buf; ZeroMemory(buf, MAX_PACKET); memcpy(ptr, &ipHdr.ip_srcaddr, sizeof(ipHdr.ip_srcaddr)); ptr += sizeof(ipHdr.ip_srcaddr); iUdpChecksumSize += sizeof(ipHdr.ip_srcaddr); memcpy(ptr, &ipHdr.ip_destaddr, sizeof(ipHdr.ip_destaddr)); ptr += sizeof(ipHdr.ip_destaddr); iUdpChecksumSize += sizeof(ipHdr.ip_destaddr); ptr++; iUdpChecksumSize += 1; memcpy(ptr, &ipHdr.ip_protocol, sizeof(ipHdr.ip_protocol)); ptr += sizeof(ipHdr.ip_protocol); iUdpChecksumSize += sizeof(ipHdr.ip_protocol); memcpy(ptr, &udpHdr.udp_length, sizeof(udpHdr.udp_length)); ptr += sizeof(udpHdr.udp_length); iUdpChecksumSize += sizeof(udpHdr.udp_length); memcpy(ptr, &udpHdr, sizeof(udpHdr)); ptr += sizeof(udpHdr); iUdpChecksumSize += sizeof(udpHdr); for(i = 0; i < strlen(strMessage); i++, ptr++) *ptr = strMessage[i]; iUdpChecksumSize += strlen(strMessage); cksum = checksum((USHORT *)buf, iUdpChecksumSize); udpHdr.udp_checksum = cksum; // // Now assemble the IP and UDP headers along with the data // so we can send it // ZeroMemory(buf, MAX_PACKET); ptr = buf; memcpy(ptr, &ipHdr, sizeof(ipHdr)); ptr += sizeof(ipHdr); memcpy(ptr, &udpHdr, sizeof(udpHdr)); ptr += sizeof(udpHdr); memcpy(ptr, strMessage, strlen(strMessage)); // Apparently, this SOCKADDR_IN structure makes no difference. // Whatever we put as the destination IP addr in the IP header // is what goes. Specifying a different destination in remote // will be ignored. // remote.sin_family = AF_INET; remote.sin_port = htons(iToPort); remote.sin_addr.s_addr = dwToIP; for(i = 0; i < dwCount; i++) { ret = sendto(s, buf, iTotalSize, 0, (SOCKADDR *)&remote, sizeof(remote)); if (ret == SOCKET_ERROR) { printf("sendto() failed: %d\n", WSAGetLastError()); break; } else printf("sent %d bytes\n", ret); } closesocket(s) ; WSACleanup() ; return 0; } 

After creating the socket and setting the IP_HDRINCL option, the code begins filling in the IP header. You'll notice that the code declared the IP header as a structure: IP_HDR. Notice that the first two 4-bit fields have been combined into a single field because the compiler can align the fields only on a minimum of a 1-byte boundary. Because of this, the code has to manipulate the IP version into the high 4 bits. The ip_protocol field is set to 0x11, which corresponds to UDP. The code also sets the ip_srcaddr field to the source IP address (or whatever address you want the recipient to think it came from) and the ip_destaddr field to the recipient's IP address. The network stack calculates the IP checksum, so the code doesn't have to set it.

The next step is to initialize the UDP header. This is simple because there aren't as many fields. The source and destination port numbers are set along with the UDP header size. The checksum field is initialized to 0. Although your code is not required to calculate the UDP checksum, this example does in order to illustrate how the calculation is done with the UDP pseudo-header. To make the calculation easier, we copy all the necessary fields into a temporary character buffer, buf. Then we pass this buffer to our checksum calculating function along with the length of the buffer to perform the calculation over.

The last thing to do before sending the datagram is to assemble the various pieces of the message in one contiguous buffer. Simply use the memcpy function to copy the IP and UDP headers followed by the data into a contiguous buffer. Next call the sendto function to send the data. Note that when you use the IP_HDRINCL option, the to parameter in sendto is ignored. The data is always sent to the host that you specify in the IP header.

To see Iphdrinc.c in action, you can use the UDP receiver program from Chapter 7, Receiver.c. For example, start the UDP receiver application with the following parameters:

 Receiver.exe -p:5150 -i:xxx.xxx.xxx.xxx -n:5 -b:1000 

You can leave off the -i parameter if the machine has only one network interface; otherwise, specify the IP address of one of the interfaces. On the Windows 2000 machine, start the Iphdrinc.exe example as

 Iphdrinc.exe -fi:1.2.3.4 -fp:10 -ti:xxx.xxx.xxx.xxx -tp:5150 -n:5150 

The IP address given for the -ti parameter should be the same IP address that the receiver listens on. The receiver application should report the following output:

 [1.2.3.4:10] sent me: 'This is a test' [1.2.3.4:10] sent me: 'This is a test' [1.2.3.4:10] sent me: 'This is a test' [1.2.3.4:10] sent me: 'This is a test' [1.2.3.4:10] sent me: 'This is a test' 

After the call to recvfrom completes, Receiver.c prints the message along with the address information returned from the SOCKADDR_IN structure passed into recvfrom. You can further verify that the IP header for the packet transmitted on the wire is the same as the one you created by using the Microsoft Network Monitor application to capture the packets on the network. Table 13-4 lists the possible command line parameters for the Iphdrinc example. You can specify the source and destination IP address and port number.

Table 13-4. Iphdrinc.c parameters

ParameterDescription
-fi:xxx.xxx.xxx.xxxSource IP address of the IP packet
-fp:intSource port number of the IP packet
-ti:xxx.xxx.xxx.xxxDestination IP address of the IP packet
-tp:intDestination port of the IP packet
-n:intNumber of UDP datagrams to send
-m:stringMessage to send


Network Programming for Microsoft Windows
Linux Server Hacks, Volume Two: Tips & Tools for Connecting, Monitoring, and Troubleshooting
ISBN: 735615799
EAN: 2147483647
Year: 1998
Pages: 159

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net