I've been trying to calculate the checksum of a TCP header in C. For my purposes, the TCP header is 20 bytes long and has no extra data or options. Also, according to my knowledge, the TCP checksum is calculated as the one's complement of the one's complement sum of the 16 bit words in the header. So, I wrote a function to calculate the TCP checksum, considering the conditions I stated above. However, when I examine the packet being sent out after I calculate the checksum, I get this:
1 0.000000 10.0.2.15 -> 127.0.0.1 TCP 74 24131->8000 [SYN] Seq=0 Win=13273 [TCP CHECKSUM INCORRECT] Len=20
I examined the packet using tshark
. However, I also examined the packets with Wireshark, and it also said the same thing. Except, that in parenthesis, it said, "maybe caused by TCP checksum offload?" Which, I don't fully understand.
Here's the code I used to calculate the checksum:
unsigned short tcp_checksum(struct tcphdr* tcph,struct iphdr* iph) {
/* Calculate TCP checksum */
struct pseudo_hdr* pseudo_hdr;
u_char* buffer;
u_char* segment;
u_char* pseudo_segment;
unsigned int count = 32;
unsigned long sum = 0;
unsigned short mask = 0xffff;
unsigned long long* hdr;
pseudo_hdr = malloc(sizeof(struct pseudo_hdr)); // allocate memory
buffer = malloc(32); // allocate for 32 bytes of information
if (pseudo_hdr == NULL || buffer == NULL) { // if memory wasn't allocated properly
err();
if (pseudo_hdr != NULL) free(pseudo_hdr);
if (buffer != NULL) free(buffer);
return 0;
}
pseudo_hdr->saddr = (unsigned long)iph->saddr; // we add the cast because the fields if of type u_int_32
pseudo_hdr->daddr = (unsigned long)iph->daddr; // same reason for adding the cast as above
bzero(&pseudo_hdr->reserved,sizeof(pseudo_hdr->reserved)); // zero header
pseudo_hdr->proto = IPPROTO_TCP; // this will always be 6
pseudo_hdr->len = htons(tcph->doff*4); // length of tcp header
/* Place both headers into a buffer */
segment = (u_char*)tcph;
pseudo_segment = (u_char*)pseudo_hdr;
/* Concactenate */
memcpy((char*)buffer,(char*)pseudo_segment,sizeof(struct pseudo_hdr)); // first the pseudo header
memcpy((char*)buffer+12,(char*)segment,sizeof(struct tcphdr)); // then the TCP segment
/* Calculate checksum just like IP checksum (RFC C implementation) */
hdr = (unsigned long long*)buffer;
while (count > 1) {
sum += *(unsigned short*)hdr++;
count -= 2;
}
// Add left over bytes, if any
if (count > 0) sum += * (u_char*) hdr;
// Fold 32-bit sum to 16 bits
while (sum>>16) sum = (sum & mask) + (sum >> 16);
sum = ~sum; // bitwise NOT operation
/* Discard headers and buffer */
free(buffer);
free(pseudo_hdr);
return sum;
}
struct iphdr
and struct tcphdr
are defined in <netinet/ip.h>
and <netinet/tcp.h>
. Here's my definition of struct pseudo_hdr
:
struct pseudo_hdr {
unsigned long saddr; // 4 bytes
unsigned long daddr; // 4 bytes
unsigned char reserved; // 1 byte
unsigned char proto; // 1 byte
unsigned short len; // 2 bytes
};
/* Total = 4+4+1+1+2 = 12 bytes */
As I said, this function, for my purposes, calculates the checksum for a TCP header that is 20 bytes long.
Thank you for any help or recommendations.