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

标签 c unix tcp checksum

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

  1. tcp_checksum
  2. get_ipv6_udptcp_checksum。

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

我正在此处复制 IP 和 TCP header 详细信息。

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

我的理解是,添加伪 header 和 TCP header 值将给出校验和。手动添加值会给出完全不同的值。当我尝试时以编程方式,它是 (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 位值从网络字节顺序转换为主机字节顺序。

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

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

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

TCP checksum calculation 中所述您需要在 TCP 数据包中添加一个伪 header ,以便源和目标 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

在将校验和写回 header 之前取反校验和:

~0x4ca3 = 0xb35c

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

代码中的问题

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

tcp_checksum()

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

  2. 围绕ip_srcip_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);

主要()

计算出的校验和没有转换成网络字节序,它应该是这样的:

tcphdr.th_sum = htons(get_ipv6_udptcp_checksum(&iphdr, (uint16_t *)&tcphdr));

关于c - TCP 校验和计算与 wireshark 计算不匹配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22727881/

相关文章:

c - 5叉后只有1个 child (C)

linux - 为 for 循环设置 IFS 然后在 for 循环内取消设置是否安全?

unix - 在 shell 脚本中引用命令行参数

tcp - Spring 集成。 TCP 服务器工厂

c - typedef 一个位域变量

c - Bison:纯推送解析器中存储的最后一个 $$ 值在哪里?

linux - linux 上的脚本 shell 和 solaris 上的脚本 shell 之间的区别

kubernetes - netstat 将外部端口显示为 kubernetes :port. 这是什么意思?

java - 当使用 Smack 4.1.0 API 作为 Google 的 GCM CCS 的 XMPP 客户端时,SecurityMode.required 不工作

c - 在 C 中迭代相同类型的结构成员