TCP校验和计算与wireshark计算不匹配

2024-01-07

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

  1. tcp_校验和
  2. get_ipv6_udptcp_checksum。

有了这两个值,就会得到两个不同的值,并且都与wireshark值不匹配。

我在此处复制 IP 和 TCP 标头详细信息。

IP 标头:

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 00 00 00 00 00 00 00

0020 00 00 00 00 00 00 00 24

TCP 标头:

0000 04 22 00 50 00 01 e0 日 00 01 42 74 50 14 2238

0010 eb10 00 00

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

我哪里做错了?有人可以建议我如何手动完成吗?

#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 开始加回来。

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

该算术的特性之一是通过简单的二进制反转产生负值。这个算术中的 0 有 2 个二进制值:0x0000 和 0xffff

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

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

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

这就是为什么所有校验和计算算法都不需要将每个 16 位值从网络字节顺序转换为主机字节顺序。

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

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

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

如中所述TCP校验和计算 http://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_checksum_for_IPv6您需要在 TCP 数据包中添加伪标头,以便源和目标 IP 地址以及端口也参与校验和计算。对于 IPv4 和 IPv6,此伪标头是不同的。在您的 IPv6 示例中,它将是:

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 伪标头校验和组合起来将是:

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

在将校验和写回标头之前对其取反:

~0x4ca3 = 0xb35c

Note:这个校验和仍然与您声称的 Wireshark 计算的不同,主要是因为您作为示例提供的数据包根据 IP 标头有 20 个字节的 TCP 有效负载数据,并且 TCP 有效负载也用于校验和计算。在我的示例中,我仅使用 TCP 标头,没有任何其他有效负载。

代码中的问题

提供的代码中发现了许多问题。

tcp_checksum()

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

  2. 周围的代码ip_src and ip_dst初始化错误,应该是:


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

l4_len = ntohs(ipv6_hdr->ip6_plen);

main()

计算出的校验和不会转换为网络字节顺序,因为它应该是:

tcphdr.th_sum = htons(get_ipv6_udptcp_checksum(&iphdr, (uint16_t *)&tcphdr));
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

TCP校验和计算与wireshark计算不匹配 的相关文章

随机推荐