TCP Checksum calculation doesn't match with the wireshark calculation

3

I am experiencing a problem where the tcp checksum generated by the sample program (copied below) doesn't match with the checksum calculated by wireshark. Can some one please point me where I am going wrong. Here I tried two ways

  1. tcp_checksum
  2. get_ipv6_udptcp_checksum.

with both these, getting two different values and both are not matching with the wireshark value.

I am copying here the IP and TCP Header details.

IP Header:

0000 60 00 00 00 00 2a 06 80 10 80 a2 b1 00 00 00 00

0010 00 00 00 00 00 1e 00 00 ff 00 00 00 00 00 00 00

0020 00 00 00 00 00 00 00 24

TCP Header:

0000 04 22 00 50 00 01 e0 dd 00 01 42 74 50 14 22 38

0010 eb 10 00 00

My understanding is, adding the pseudo header and TCP header values will give the checksum. adding the values manually is giving entirely different value. programmatically when I tried, it was (38 eb). wireshark shows the correct value should be 0xb348

where I am doing wrong? can some one please suggest me how to do it manually?

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>           // close()
#include <string.h>           // strcpy, memset(), and memcpy()

#include <netdb.h>            // struct addrinfo
#include <sys/types.h>        // needed for socket(), uint8_t, uint16_t
#include <sys/socket.h>       // needed for socket()
#include <netinet/in.h>       // IPPROTO_TCP, INET6_ADDRSTRLEN
#include <netinet/ip.h>       // IP_MAXPACKET (which is 65535)
#include <netinet/ip6.h>      // struct ip6_hdr
#define __FAVOR_BSD           // Use BSD format of tcp header
#include <netinet/tcp.h>      // struct tcphdr
#include <arpa/inet.h>        // inet_pton() and inet_ntop()
#include <sys/ioctl.h>        // macro ioctl is defined
#include <bits/ioctls.h>      // defines values for argument "request" of ioctl.
#include <net/if.h>           // struct ifreq
#include <linux/if_ether.h>   // ETH_P_IP = 0x0800, ETH_P_IPV6 = 0x86DD
#include <linux/if_packet.h>  // struct sockaddr_ll (see man 7 packet)
#include <net/ethernet.h>

#include <errno.h>            // errno, perror()
void ipv6_to_str_unexpanded(char *str, const struct in6_addr * addr) {
   sprintf(str, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
                 (int)addr->s6_addr[0], (int)addr->s6_addr[1],
                 (int)addr->s6_addr[2], (int)addr->s6_addr[3],
                 (int)addr->s6_addr[4], (int)addr->s6_addr[5],
                 (int)addr->s6_addr[6], (int)addr->s6_addr[7],
                 (int)addr->s6_addr[8], (int)addr->s6_addr[9],
                 (int)addr->s6_addr[10], (int)addr->s6_addr[11],
                 (int)addr->s6_addr[12], (int)addr->s6_addr[13],
                 (int)addr->s6_addr[14], (int)addr->s6_addr[15]);
printf("addr:[%s]\n",str);
}


static inline uint16_t
get_16b_sum(uint16_t *ptr16, uint32_t nr)
{
        uint32_t sum = 0;
        while (nr > 1)
        {
                sum +=*ptr16;
                nr -= sizeof(uint16_t);
                ptr16++;
                if (sum > UINT16_MAX)
                        sum -= UINT16_MAX;
        }

        /* If length is in odd bytes */
        if (nr)
                sum += *((uint8_t*)ptr16);

        sum = ((sum & 0xffff0000) >> 16) + (sum & 0xffff);
        sum &= 0x0ffff;
        return (uint16_t)sum;
}

static inline uint16_t 
get_ipv6_psd_sum (struct ip6_hdr * ip_hdr)
{
        /* Pseudo Header for IPv6/UDP/TCP checksum */
        union ipv6_psd_header {
                struct {
                        uint8_t src_addr[16]; /* IP address of source host. */
                        uint8_t dst_addr[16]; /* IP address of destination host(s). */
                        uint32_t len;         /* L4 length. */
                        uint32_t proto;       /* L4 protocol - top 3 bytes must be zero */
                } __attribute__((__packed__));

                uint16_t u16_arr[0]; /* allow use as 16-bit values with safe aliasing */
        } psd_hdr;

        memcpy(&psd_hdr.src_addr, &ip_hdr->ip6_src,
                        (sizeof(ip_hdr->ip6_src) + sizeof(ip_hdr->ip6_dst)));
        //psd_hdr.len       = ip_hdr->payload_len;
        psd_hdr.len       = ip_hdr->ip6_plen;
        psd_hdr.proto     = IPPROTO_TCP;//(ip_hdr->proto << 24);

        return get_16b_sum(psd_hdr.u16_arr, sizeof(psd_hdr));
}


static inline uint16_t 
get_ipv6_udptcp_checksum(struct ip6_hdr *ipv6_hdr, uint16_t *l4_hdr)
{
        uint32_t cksum;
        uint32_t l4_len;

        l4_len = (ipv6_hdr->ip6_plen);

        cksum = get_16b_sum(l4_hdr, l4_len);
        cksum += get_ipv6_psd_sum(ipv6_hdr);

        cksum = ((cksum & 0xffff0000) >> 16) + (cksum & 0xffff);
        cksum = (~cksum) & 0xffff;
        if (cksum == 0)
                cksum = 0xffff;

        return (uint16_t)cksum;
}

//! \brief Calculate the TCP checksum.
//! \param buff The TCP packet.
//! \param len The size of the TCP packet.
//! \param src_addr The IP source address (in network format).
//! \param dest_addr The IP destination address (in network format).
//! \return The result of the checksum.
uint16_t tcp_checksum(const void *buff, size_t len, struct in6_addr src_addr, struct in6_addr dest_addr)
{
    const uint16_t *buf=buff;
    uint16_t *ip_src=(void *)&src_addr, *ip_dst=(void *)&dest_addr;
    uint32_t sum;
    size_t length=len;

    // Calculate the sum                                            //
    sum = 0;
    while (len > 1)
    {
        sum += *buf++;
        if (sum & 0x80000000)
            sum = (sum & 0xFFFF) + (sum >> 16);
        len -= 2;
    }

    if ( len & 1 )
    // Add the padding if the packet lenght is odd          //
    sum += *((uint8_t *)buf);

    // Add the pseudo-header                                        //
    sum += *(ip_src++);
    sum += *ip_src;
    sum += *(ip_dst++);
    sum += *ip_dst;
    sum += htons(IPPROTO_TCP);
    sum += htons(length);

    // Add the carries                                              //
    while (sum >> 16)
        sum = (sum & 0xFFFF) + (sum >> 16);

    // Return the one's complement of sum                           //
    return ( (uint16_t)(~sum)  );
}

// Define some constants.
#define ETH_HDRLEN 14  // Ethernet header length
#define IP6_HDRLEN 40  // IPv6 header length
#define TCP_HDRLEN 20  // TCP header length, excludes options data

// Function prototypes
uint16_t checksum (uint16_t *, int);
uint16_t tcp6_checksum (struct ip6_hdr, struct tcphdr, uint8_t *, int);
char *allocate_strmem (int);
uint8_t *allocate_ustrmem (int);
int *allocate_intmem (int);

int
main (int argc, char **argv)
{
  int i, status, frame_length, sd, bytes, *tcp_flags, opt_len;
  char *interface, *target, *src_ip, *dst_ip;
  struct ip6_hdr iphdr;
  struct tcphdr tcphdr;
  uint8_t *src_mac, *dst_mac, *ether_frame;
  uint8_t *options;
  struct addrinfo hints, *res;
  struct sockaddr_in6 *ipv6;
  struct sockaddr_ll device;
  struct ifreq ifr;
  void *tmp;

  // Allocate memory for various arrays.
  src_mac = allocate_ustrmem (6);
  dst_mac = allocate_ustrmem (6);
  ether_frame = allocate_ustrmem (IP_MAXPACKET);
  interface = allocate_strmem (40);
  target = allocate_strmem (INET6_ADDRSTRLEN);
  src_ip = allocate_strmem (INET6_ADDRSTRLEN);
  dst_ip = allocate_strmem (INET6_ADDRSTRLEN);
  tcp_flags = allocate_intmem (8);
  options = allocate_ustrmem (40);

  // Interface to send packet through.
  strcpy (interface, "eth0");

  // Source IPv6 address: you need to fill this out
  strcpy (src_ip,"1080:a2b1::1e:0");
  strcpy (dst_ip,"ff00::24");

  // IPv6 header

  // IPv6 version (4 bits), Traffic class (8 bits), Flow label (20 bits)
  iphdr.ip6_flow = htonl ((6 << 28) | (0 << 20) | 0);

  // Payload length (16 bits): TCP header + TCP options
  //iphdr.ip6_plen = htons (TCP_HDRLEN + opt_len);
  //iphdr.ip6_plen = htons (TCP_HDRLEN);
  iphdr.ip6_plen = htons(TCP_HDRLEN);

  // Next header (8 bits): 6 for TCP
  iphdr.ip6_nxt = IPPROTO_TCP;

  // Hop limit (8 bits): default to maximum value
  iphdr.ip6_hops = 128;

  // Source IPv6 address (128 bits)
  if ((status = inet_pton (AF_INET6, src_ip, &(iphdr.ip6_src))) != 1) {
    fprintf (stderr, "inet_pton() failed.\nError message: %s", strerror (status));
    exit (EXIT_FAILURE);
  }

char srcAddr[32];
memset(srcAddr,0,32);
printf("src ip addr:");
ipv6_to_str_unexpanded(srcAddr,&(iphdr.ip6_src));


  // Destination IPv6 address (128 bits)
  if ((status = inet_pton (AF_INET6, dst_ip, &(iphdr.ip6_dst))) != 1) {
    fprintf (stderr, "inet_pton() failed.\nError message: %s", strerror (status));
    exit (EXIT_FAILURE);
  }
char dstAddr[32];
memset(dstAddr,0,32);
printf("dst ip addr:");
ipv6_to_str_unexpanded(dstAddr,&(iphdr.ip6_dst));


  // TCP header

  // Source port number (16 bits)
  tcphdr.th_sport = htons (1058);
printf("src port:[%d]\n",tcphdr.th_sport);

  // Destination port number (16 bits)
  tcphdr.th_dport = htons (80);

  // Sequence number (32 bits)
  tcphdr.th_seq = htonl (1);
printf("seq :[%d]\n",tcphdr.th_seq);

  // Acknowledgement number (32 bits): 0 in first packet of SYN/ACK process
  tcphdr.th_ack = htonl (1);

  // Reserved (4 bits): should be 0
  tcphdr.th_x2 = 0;

  // Data offset (4 bits): size of TCP header + length of options, in 32-bit words
  //tcphdr.th_off = (TCP_HDRLEN + opt_len) / 4;
  tcphdr.th_off = TCP_HDRLEN/4;

  // Flags (8 bits)

  // FIN flag (1 bit)
  tcp_flags[0] = 0;

  // SYN flag (1 bit): set to 1
  tcp_flags[1] = 0;

  // RST flag (1 bit)
  tcp_flags[2] = 1;

  // PSH flag (1 bit)
  tcp_flags[3] = 0;

  // ACK flag (1 bit)
  tcp_flags[4] = 1;

  // URG flag (1 bit)
  tcp_flags[5] = 0;

  // ECE flag (1 bit)
  tcp_flags[6] = 0;

  // CWR flag (1 bit)
  tcp_flags[7] = 0;

  tcphdr.th_flags = 0;
  for (i=0; i<8; i++) {
    tcphdr.th_flags += (tcp_flags[i] << i);
  }

  // Window size (16 bits)
  tcphdr.th_win = htons (8760);

  // Urgent pointer (16 bits): 0 (only valid if URG flag is set)
  tcphdr.th_urp = htons (0);

  tcphdr.th_sum  = 0;
  //tcphdr.th_sum  = get_ipv6_udptcp_checksum(&iphdr, (uint16_t *)&tcphdr);
  tcphdr.th_sum = tcp_checksum((void *)&tcphdr, htons(20), iphdr.ip6_src, iphdr.ip6_dst);

printf("TCP Checksum:[%x]\n",tcphdr.th_sum);

return 0;
}

char *
allocate_strmem (int len)
{
  void *tmp;

  if (len <= 0) {
    fprintf (stderr, "ERROR: Cannot allocate memory because len = %i in allocate_strmem().\n", len);
    exit (EXIT_FAILURE);
  }

  tmp = (char *) malloc (len * sizeof (char));
  if (tmp != NULL) {
    memset (tmp, 0, len * sizeof (char));
    return (tmp);
  } else {
    fprintf (stderr, "ERROR: Cannot allocate memory for array allocate_strmem().\n");
    exit (EXIT_FAILURE);
  }
}
c
unix
tcp
checksum
asked on Stack Overflow Mar 29, 2014 by user3475103 • edited Feb 23, 2020 by Cœur

2 Answers

11

Manual Checksum Calculation

The checksum calculation for TCP/UDP/IP is rather trivial. What is called "16 bit one's complement" arithmetic is just a notion that during addition of two 16-bit numbers whatever was carried over 16 bit is added back from bit 0. E.g.

0x8000 + 0x8000 = 0x10000 => 0x1 + 0x0000 = 0x0001.

One of the properties of this arithmetic is that negative value is produced by simple binary inversion. And 0 in this arithmetic has 2 binary values: 0x0000 and 0xffff

-0x0001 = ~0x0001 = 0xfffe;
0xfffe + 0x8000 + 0x8000 = 0x1fffe => 0x1 + 0xfffe = 0xffff = 0x0000

Another nice thing about 16 bit one's complement is that you don't have to worry about endianness while doing 16-bit additions, you have to properly convert only the final result. This happens because carry bit always travels from one byte to another and is never lost. Here's the same example as if data was read in little endian machine:

0x0080 + 0x0080 = 0x0100 => htons(0x0100) = 0x0001

That's why all algorithms of checksum calculation doesn't bother converting each and every 16-bit value from network to host byte order.

Considering all these, you simply break your data block into 16-bit works, add them all together the regular way, and then add higher 16 bits to the lower 16 bits and invert the result before writing it back to the packet.

In your example, TCP header checksum would be calculated as:

0x0422 + 0x0050 + 0x0001 + 0xe0dd + 0x0001 + 0x4274 + 0x5014 + 0x2238 +
0x0000 + 0x0000 = 0x19a11 = 0x1 + 0x9a11 = 0x9a12
^^^^^^ // <- this is the place for the TCP checksum

As described in TCP checksum calculation you need to add a pseudo header to your TCP packet so that source and destination IP addresses and ports also took part in checksum calculation. This pseudo header is different for IPv4 and IPv6. In your example for IPv6 its going to be:

0x1080 + 0xa2b1 + 0x0000 + 0x0000 + // source IPv6 address
0x0000 + 0x0000 + 0x001e + 0x0000 +
0xff00 + 0x0000 + 0x0000 + 0x0000 + // destination IPv6 address
0x0000 + 0x0000 + 0x0000 + 0x0024 +
0x0016 +                            // IP payload (TCP packet) lenght
0x0006                              // Next Header value for TCP
= 0x1b28f = 0x1 + 0xb28f = 0xb290

Now TCP and IP-pseudo-header checksums combined would be:

0x9a12 + 0xb290 = 0x14ca2 = 0x1 + 0x4ca2 = 0x4ca3

Negating checksum before writing it back to the header:

~0x4ca3 = 0xb35c

Note: this checksum still differ from what you claim Wireshark calculates mostly because the packet you provided as example has 20 bytes of TCP payload data according to IP header, and TCP payload is also used in checksum calculation. In my example I used just TCP header without any other payload.

Problems in Your Code

There's a number of problems spotted in the code provided.

tcp_checksum()

  1. This function calculates IPv4 checksum. To modify it for IPv6 you need to extend IP addresses sizes used in calculation from 4 bytes to 16.

  2. The code around ip_src and ip_dst initialization is wrong and should be:


    uint16_t *ip_src=(uint16_t *)&src_addr->in_addr;
    uint16_t *ip_dst=(uint16_t *)&dest_addr->in_addr;

get_ipv6_udptcp_checksum()

l4_len is not converted from network byte order. It should be:

l4_len = ntohs(ipv6_hdr->ip6_plen);

main()

Calculated checksum is not converted into network byte order, as it should be:

tcphdr.th_sum = htons(get_ipv6_udptcp_checksum(&iphdr, (uint16_t *)&tcphdr));
answered on Stack Overflow Mar 29, 2014 by dkz • edited Mar 30, 2014 by dkz
0

Checksum calculation for UDP frame with Pseudo header and : SOLVED! (Including C-Code, example output and Wireshark dataframe for verification)

Hello, i have been looking into the same problem: Please find a C program that generates the correct UDP CHECK based on the pseudo header and the frame content. Included is the program output and the wireshark result for the same frame in real life.

unsigned long sum=0, sum2=0, term=0;
CString s;

#define DATABYTES 3

// pseudo header bytes:
unsigned char ip1[]={192,168,11,25};   // ip 1
unsigned char ip2[]={192,168,11,20};  // ip 2
unsigned char pr []= {0,17};         // udp protocol 
unsigned char len[]= {0,8+DATABYTES};         // UDP entire message length entire 

// acutal message bytes:
unsigned char port1[] ={0x22,0xb8};   // port 1
unsigned char port2[] ={0x22,0xb8};   // port 1
unsigned char msglen[]={0,8+DATABYTES};         // UDP entire message length entire 
unsigned char check[] = {0,0};        // check set to 0 
unsigned char msg[DATABYTES]= {0x11,0x22,0x33};         // 10 bytes message length

// add artificial pseudo header bytes to sum
term = ((unsigned short) ip1[0] << 8) + ip1 [1];
sum+=term;
s.Format("Add ip1 = 0x%04x Sum = 0x%04x\n",term,sum);  AfxTrace(s);
term = ((unsigned short) ip1[2] << 8) + ip1 [3];
sum+=term;
s.Format("Add ip1 = 0x%04x Sum = 0x%04x\n",term,sum);  AfxTrace(s);

term = ((unsigned short) ip2[0] << 8) + ip2 [1];
sum+=term;
s.Format("Add ip2 = 0x%04x Sum = 0x%04x\n",term,sum);  AfxTrace(s);
term = ((unsigned short) ip2[2] << 8) + ip2 [3];
sum+=term;
s.Format("Add ip2 = 0x%04x Sum = 0x%04x\n",term,sum);  AfxTrace(s);

term = ((unsigned short) pr[0] << 8) + pr [1];
sum+=term;
s.Format("Add pr = 0x%04x Sum = 0x%04x\n",term,sum);  AfxTrace(s);
term = ((unsigned short) len[0] << 8) + len [1];
sum+=term;
s.Format("Add len = 0x%04x Sum = 0x%04x\n",term,sum);  AfxTrace(s);


// ----------------------------------------------------------------
// add real udp header bytes to sum (ports ....)
term = ((unsigned short) port1[0] << 8) + port1[1];
sum+=term;
s.Format("Add port1 = 0x%04x Sum = 0x%04x\n",term,sum);  AfxTrace(s);
term = ((unsigned short) port2[0] << 8) + port2[1];
sum+=term;
s.Format("Add port2 = 0x%04x Sum = 0x%04x\n",term,sum);  AfxTrace(s);
term = ((unsigned short) msglen[0] << 8) + msglen [1];
sum+=term;
s.Format("Add msglen = 0x%04x Sum = 0x%04x\n",term,sum);  AfxTrace(s);

// ----------------------------------------------------------------
// add data bytes info:
for (int i=0;i<DATABYTES/2;i++)
{
    // add message data bytes to sum sum (ports ....)
    term = ((unsigned short) msg[i*2] << 8) + msg [1+i*2];
    sum+=term;
    s.Format("Add msg = 0x%04x sum = 0x%04x\n",term,sum);  AfxTrace(s);
}

if (DATABYTES % 2 != 0)
{
    term = ((unsigned short) msg[DATABYTES-1] << 8);
    sum+=term;
    s.Format("Add msg = 0x%04x sum = 0x%04x\n",term,sum);  AfxTrace(s);
}

sum= (sum >> 16) + (sum & 0xFFFF);
s.Format("Clean sum = 0x%04x \n",sum);  AfxTrace(s);

sum= (sum >> 16) + (sum & 0xFFFF);
s.Format("Clean sum = 0x%04x \n",sum);  AfxTrace(s);

sum= (~sum & 0xFFFF);
s.Format("not sum = 0x%04x \n",sum);  AfxTrace(s);

Output of the program:

Add ip1 = 0xc0a8 Sum = 0xc0a8
Add ip1 = 0x0b19 Sum = 0xcbc1
Add ip2 = 0xc0a8 Sum = 0x18c69
Add ip2 = 0x0b14 Sum = 0x1977d
Add pr = 0x0011 Sum = 0x1978e
Add len = 0x000b Sum = 0x19799
Add port1 = 0x22b8 Sum = 0x1ba51
Add port2 = 0x22b8 Sum = 0x1dd09
Add msglen = 0x000b Sum = 0x1dd14
Add msg = 0x1122 sum = 0x1ee36
Add msg = 0x3300 sum = 0x22136
Add msg = 0x0000 sum = 0x22136
Add msg = 0x0000 sum = 0x22136
Clean sum = 0x2138 
Clean sum = 0x2138 
not sum = 0xdec7  

And the wireshark frame:

Ethernet II, Src: Intel_af:ef:6f (00:03:47:af:ef:6f), Dst: aa:aa:bb:bb:cc:cd (aa:aa:bb:bb:cc:cd)
    Destination: aa:aa:bb:bb:cc:cd (aa:aa:bb:bb:cc:cd)
        Address: aa:aa:bb:bb:cc:cd (aa:aa:bb:bb:cc:cd)
        .... ..1. .... .... .... .... = LG bit: Locally administered address (this is NOT the factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Source: Intel_af:ef:6f (00:03:47:af:ef:6f)
        Address: Intel_af:ef:6f (00:03:47:af:ef:6f)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Type: IP (0x0800)
Internet Protocol Version 4, Src: 192.168.11.25 (192.168.11.25), Dst: 192.168.11.20 (192.168.11.20)
    Version: 4
    Header length: 20 bytes
    Differentiated Services Field: 0x00 (DSCP 0x00: Default; ECN: 0x00: Not-ECT (Not ECN-Capable Transport))
        0000 00.. = Differentiated Services Codepoint: Default (0x00)
        .... ..00 = Explicit Congestion Notification: Not-ECT (Not ECN-Capable Transport) (0x00)
    Total Length: 31
    Identification: 0xebe3 (60387)
    Flags: 0x00
        0... .... = Reserved bit: Not set
        .0.. .... = Don't fragment: Not set
        ..0. .... = More fragments: Not set
    Fragment offset: 0
    Time to live: 128
    Protocol: UDP (17)
    Header checksum: 0xb76c [correct]
        [Good: True]
        [Bad: False]
    Source: 192.168.11.25 (192.168.11.25)
    Destination: 192.168.11.20 (192.168.11.20)
    [Source GeoIP: Unknown]
    [Destination GeoIP: Unknown]
User Datagram Protocol, Src Port: ddi-udp-1 (8888), Dst Port: ddi-udp-1 (8888)
    Source port: ddi-udp-1 (8888)
    Destination port: ddi-udp-1 (8888)
    Length: 11
    Checksum: 0xdec7 [correct]
        [Good Checksum: True]
        [Bad Checksum: False]
Data (3 bytes)
    Data: 112233
    [Length: 3]

0000  aa aa bb bb cc cd 00 03 47 af ef 6f 08 00 45 00   ........G..o..E.
0010  00 1f eb e3 00 00 80 11 b7 6c c0 a8 0b 19 c0 a8   .........l......
0020  0b 14 22 b8 22 b8 00 0b de c7 11 22 33            .."."......"3

One thing is missing: if output is 0000, you need to invert it to FFFF to signal for a calculated check instead of a check not present. Good Luck, JOHI.

answered on Stack Overflow Nov 9, 2016 by JOHI

User contributions licensed under CC BY-SA 3.0