Introduction
This article presents a checksum routine for UPD/IP packets using 32-bit groupings.
Background
See RFC 768 to read about the UDP protocol and the UDP checksum. In particular, you need to understand the pseudo header used for the UDP checksum. See RFC 1071 for a discourse on the theory of the internet checksum.
I use winpcap (http://winpcap.polito.it) to monitor a UDP data stream and I needed a checksum routine, but all the examples I found were based on inefficient 16-bit groupings of the bytes. However, in RFC 1071 I found three key principles that allow for a more efficient process:
First, the size of the groupings doesn't matter if you fold the result back into a 16 bit word at the end. In C it is not easy to check for integer overflow, so you need an accumulator that can hold all of the overflows from all the summing. For 32 bit groupings, the accumulator needs to be 64 bits. At the end of the process you can fold the 64 bits into 16 bits to get a valid checksum result.
Second, the byte order doesn't matter until the end if you are generating a checksum to insert into a packet. If you are only checking the checksum at the receiving end, the result should be 0xFFFF
, so in this case the byte order doesn't matter at all. Thus, you save a little overhead by not calling ntohs()
for each grouping of bytes.
Finally, zeroes don't affect the checksum result, so padding a leftover byte with zeroes to form a long integer is ok.
Using the code
To use the UDPchecksum()
routine you can simply paste it into your winpcap project (or any project that has a packet capture tool) and rename the global pointers to match the names you choose. Winpcap fills a structure with the winpcap header information along with a buffer containing the captured packet. In my version these pointers were members of a class, but I removed that detail for this example. They must point to the winpcap entities that contain a header and a packet, respectively, before the call to UDPchecksum()
. Of course, you could make the pointers private and pass them in the function call.
There are two cheap (efficient) tricks in this version:
First, the checksum pseudo header is created by mangling the IP header. The pseudo header consists of the following elements:
- IP Source Address = 4 bytes
- IP Destination Address = 4 bytes
- Protocol = 2 bytes
- UDP Length = 2 bytes
These must be added to the checksum to get the correct result. Since the IP Source and Destination addresses along with the Protocol field are contiguous and immediately precede the UDP section, we already have a good start. I simply copied the UPD Length field to the IP Checksum field of the IP header, and I zeroed out the IP Time to Live byte. Now the pseudo header is complete and contiguous with the UDP header. The checksum process starts at byte 22 of the packet, which is the beginning of the pseudo header I just assembled.
Second, I solved the problem of what to do when the data length is not a multiple of 4 bytes by replacing the four bytes immediately following the UDP packet with zeroes. These will either be the ethernet Frame Check Sequence or ethernet filler. Again, I don't mind mangling the leftover ethernet fields to simplify processing the UDP packet. Now, if a single byte remains to be read at the end of a UDP packet, three zeroes are read with it to form a 32 bit grouping, and there is no affect on the result.
The return from my version is 'true
' if the checksum result is correct.
#define UDP_LEN 38
#define IP_LIVE 22
#define IP_CHECKSUM 24
struct pcap_pkthdr *pHeader;
u_char *pPkt_data;
bool UDPchecksum()
{
_int64 qwSum = 0;
int nSize;
unsigned char *pB = (unsigned char *)(pPkt_data + pHeader->caplen);
ULONG *pDW = (ULONG *)(pB);
*pDW = 0;
*(pPkt_data + IP_LIVE) = 0;
*(pPkt_data + IP_CHECKSUM + 1) = *(pPkt_data + UDP_LEN + 1);
*(pPkt_data + IP_CHECKSUM) = *(pPkt_data + UDP_LEN);
pB = (pPkt_data + 22);
pDW = (ULONG *)(pB);
dwSize = pHeader->caplen - IP_LIVE;
while( nSize > 0 ) {
qwSum += (ULONG) *pDW++;
nSize -= 4;
}
while (qwSum >> 16)
qwSum = (qwSum & 0xffff) + (qwSum >> 16);
return (qwSum == 0xFFFF);
}
History
No changes yet!