TCP校验和计算与Wireshark计算不匹配 [英] TCP Checksum calculation doesn't match with the wireshark calculation

查看:221
本文介绍了TCP校验和计算与Wireshark计算不匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到一个问题,示例程序生成的tcp校验和(下面复制)与Wireshark计算的校验和不匹配。有人可以指出我要去哪里了吗?
在这里,我尝试了两种方法

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。

同时使用这两种方法,得到两个不同的值,并且都与wireshark值不匹配。

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

我在这里复制IP和TCP标头详细信息。

I am copying here the IP and TCP Header details.

IP标头:

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

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

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 24

0020 00 00 00 00 00 00 00 24

TCP标头:

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

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

0010 eb 10 00 00

0010 eb 10 00 00

我的理解是,添加伪标头和TCP标头值将得出校验和。手动添加值将提供完全不同的值。当我尝试编程时,它是(38 eb)。 wirehark显示正确的值应该是 0xb348

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);
  }
}


推荐答案

手册校验和计算



TCP / UDP / IP的校验和计算非常简单。所谓的 16位一个人的补码算法只是一个概念,即在两个16位数字相加的过程中,从16位0开始又加上了16位的余数。例如:

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.

此算法的一个特性是,负值是通过简单的二进制求逆产生的。并且此算法中的0具有2个二进制值:0x0000和0xffff

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

16位补码的另一个好处是您不需要在进行16位加法时,必须担心字节序,您只需要正确转换最终结果即可。发生这种情况是因为进位总是从一个字节传输到另一个字节,并且永不丢失。就像在小端机器中读取数据一样,这是一个示例:

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

这就是为什么所有校验和计算算法都不会麻烦地转换每个从网络到主机字节顺序的16位值。

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

考虑所有这些,您只需将数据块分解为16位作品,并按常规方式将它们全部加在一起,然后将高16位加到低16位上,并在将结果写回到数据包之前求反。

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.

在您的示例中,TCP头校验和的计算公式为:

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


的地方

TCP校验和计算中所述,您需要添加一个伪TCP数据包的标头,以便源IP和目标IP地址和端口也参与了校验和计算。对于IPv4和IPv6,此伪头是不同的。在您的IPv6示例中,它将是:

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

现在,TCP和IP伪头校验和的总和为:

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

注意:此校验和仍然与您声称的Wireshark计算不同,主要是因为您提供的数据包根据IP报头和TCP pa包含20字节的TCP有效负载数据yload也用于校验和计算。在我的示例中,我只使用了TCP头,没有任何其他有效载荷。

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.

有一个数字


  1. 此功能计算IPv4校验和。要针对IPv6对其进行修改,您需要将计算中使用的IP地址大小从4个字节扩展到16个。

  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.

ip_src ip_dst 初始化是错误的,应该是:

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 不从网络字节顺序转换。应该是:

get_ipv6_udptcp_checksum()

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

l4_len = ntohs(ipv6_hdr->ip6_plen);



main()



计算出的校验和为

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));

这篇关于TCP校验和计算与Wireshark计算不匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆