Search code examples
ctcpnetwork-programmingipchecksum

IP and TCP header checksum calculate in C


I'm writing a simple program to send/receive TCP packets and incorporate it into a larger project. I got stuck at the checksum part where the number I calculated doesn't match the checksum number shown in Wireshark.

For the checksum function, I re-used the code from Mike Muss as below:

static int
in_cksum(u_short *addr, int len)
{
    register int nleft = len;
    register u_short *w = addr;
    register int sum = 0;
    u_short answer = 0;

    /*
     * Our algorithm is simple, using a 32 bit accumulator (sum), we add
     * sequential 16 bit words to it, and at the end, fold back all the
     * carry bits from the top 16 bits into the lower 16 bits.
     */
    while (nleft > 1)  {
        sum += *w++;
        nleft -= 2;
    }

    /* mop up an odd byte, if necessary */
    if (nleft == 1) {
        *(u_char *)(&answer) = *(u_char *)w ;
        sum += answer;
    }

    /* add back carry outs from top 16 bits to low 16 bits */
    sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
    sum += (sum >> 16);         /* add carry */
    answer = ~sum;              /* truncate to 16 bits */
    return(answer);
}

I received a packet and store it in a char buffer[2048]. In order to get IP header, I do:

struct iphdr* ip;
ip = (struct iphdr*) buffer;

From here, I can read information correctly as ip->protocol, ip->saddr, etc., and even print out the correct checksum as displayed in Wireshark

printf("Print checksum = 0x%x\n",ntohs(ip->check));

Then I try to calculate the checksum using the function above and print it out:

printf("My calculated checksum =0x%x\n",in_cksum ((unsigned short*) ip, sizeof(struct iphdr)));

What I got is "My calculated checksym = 0x0" and it seems that there is nothing in the IP header. I guess that I might not pass in the in_cksum function parameter correctly, but I'm not sure how to fix that or if there is another problem in the way that I'm doing.

The IP checksum problem is solved below. However, I met a similar problem when trying to calculate the TCP checksum. Below is how I get the TCP header:

tcp=(struct tcphdr*) (buffer+sizeof(struct iphdr);

After this, again I can read correct information about the TCP header such as tcp->source, tcp->dest, and even tcp->check.

Then I tried to re-compute the checksum as below:

tcp->check=0;
tcp->check=in_cksum((unsigned short*)tcp, ntohs(ip->tot_length)-(4*ip->ihl));

The result I got here is different from what I printed out before. I think my problem could be that the length that I passed to the cksum function, but I am not quite sure how to fix it.


Solution

  • The calculation of the IP checksum must not include the field that contains that checksum - zero should be used for that field instead. From RFC 791:

    The checksum field is the 16 bit one's complement of the one's complement sum of all 16 bit words in the header. For purposes of computing the checksum, the value of the checksum field is zero.

    I haven't done the math, but I suspect that getting zero is the expected consequence of recalculating the checksum over the entire header, i.e. it causes the calculated sum to cancel itself out.

    [I checked - Wikipedia agrees: "The result of summing the entire IP header, including checksum, should be zero if there is no corruption". Zero is therefore in fact the correct result when verifying the checksum]

    Note that an IP header can only be a multiple of 4 bytes long, so your padding code is unnecessary in this particular case, albeit potentially useful if wanting to calculate the generic ones-complement checksum of other data.

    Also, instead of passing sizeof(struct iphdr) you should be passing 4 * ip->ihl to account for any IP options that may be in the header, having first ensured that you actually have at least that many bytes in your buffer.