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校验和计算中所述,您需要向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
注意:此校验和仍然与您声称Wireshark计算的大不相同,因为您提供的数据包根据IP标头具有20个字节的TCP有效负载数据,并且TCP有效负载也用于校验和计算.在我的例子中,我只使用TCP头而没有任何其他有效负载.
提供的代码中发现了许多问题.
此函数计算IPv4校验和.要为IPv6修改它,您需要将计算中使用的IP地址大小从4个字节扩展到16个.
代码ip_src
和ip_dst
初始化是错误的,应该是:
uint16_t *ip_src=(uint16_t *)&src_addr->in_addr; uint16_t *ip_dst=(uint16_t *)&dest_addr->in_addr;
l4_len
未从网络字节顺序转换.它应该是:
l4_len = ntohs(ipv6_hdr->ip6_plen);
计算的校验和不会转换为网络字节顺序,因为它应该是:
tcphdr.th_sum = htons(get_ipv6_udptcp_checksum(&iphdr, (uint16_t *)&tcphdr));