计算C中ICMPv6包的校验和

我正在尝试计算ICMPv6消息的校验和(确切地说,是一个Neighbor Advertisement)。

RFC 4443将其描述为“整个ICMPv6消息的一个补码和的16位补码”

还有一些关于如何做到这一点的示例代码(虽然我认为它来自IPv4,但唯一的区别是总和中包含的内容,而不是如何计算它): RFC 1071

我从wireshark获取了一个数据包,并以主机字节顺序进入短路。 然后我打印正确的校验和,将其归零并计算我的。 但他们不匹配。 根据RFC 1071,endianess不应该是问题(结果不只是字节交换,而是完全关闭)。

根据RFC 2460#8.1,我需要在计算中包含“伪标头”,仅包含Src + Dst地址,长度为32位宽字段和下一个标头类型。

通话代码:

uint32_t payloadlen = htonl(32); struct ip6_hdr *ip6; struct nd_neighbor_advert *na; size_t len, offset, tmplen; uint8_t *tmppacket, icmp = 58; uint8_t packet[] = { 0x60, 0x00, 0x00, 0x00, 0x00, 0x20, 0x3A, 0xFF, 0x20, 0x01, 0x0D, 0xB8, 0xDD, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x54, 0xFF, 0xFE, 0x00, 0x22, 0x00, 0x88, 0x00, 0x54, 0xB9, 0x60, 0x00, 0x00, 0x00, 0x20, 0x01, 0x0D, 0xB8, 0xDD, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x00, 0x03, 0x54, 0x00, 0x00, 0x13 }; na->nd_na_hdr.icmp6_cksum = 0; tmplen = 40+sizeof(struct nd_neighbor_advert)+ICMP_OPT_LEN; tmppacket = malloc(tmplen); memset(tmppacket, 0, 40); offset = sizeof(struct in6_addr); memcpy(tmppacket, &ip6->ip6_src, offset); memcpy(tmppacket+offset, &ip6->ip6_dst, offset); memcpy(tmppacket+offset*2, &payloadlen, 4); memcpy(tmppacket+39, &icmp, 1); memcpy(tmppacket+40, packet+sizeof(struct ip6_hdr), sizeof(struct nd_neighbor_advert)+ICMP_OPT_LEN); na = (struct nd_neighbor_advert *) (tmppacket+40); na->nd_na_hdr.icmp6_cksum = checksum((uint16_t *) tmppacket, tmplen); printf("Checksum calc: %hX\n", na->nd_na_hdr.icmp6_cksum); dump((unsigned char *) tmppacket, tmplen); 

校验和function:

 uint16_t checksum (uint16_t *addr, size_t bytes) { unsigned int i; uint16_t *p = addr; uint32_t sum = 0; /* Sum */ for (i=bytes; i > 1; i -= 2) sum += *p++; /* If uneven length */ if (i > 0) sum += (uint16_t) *((unsigned char *) (p)); /* Carry */ while ((sum & 0xFFFF0000) != 0) sum = (sum >> 16) + (sum & 0xFFFF); return ~((uint16_t) sum); } 

这首先让它快速而肮脏。 “main”的代码省略了一些东西。 校验和函数中的Endianess应该不是问题,此数据包的长度也是均匀的。

结果应该是B954,但我得到了DB32。 转储的输出是:

包大小:72
2001 0DB8 DDDD 0000 0000 0000 0000 0100 FE80 0000 0000 0000 0203 54FF FE00 2200 0000 0020 0000 003A 8800 32DB 6000 0000 2001 0DB8 DDDD 0000 0000 0000 0000 0100 0201 0003 5400 0013

感谢您的提示到目前为止。 如果您对可能仍然存在的问题有所了解,我将非常感谢您的意见。

我认为您的代码有三个问题:

  1. 你校验IPv6标头,你不应该。 校验和应涵盖IPv6地址和长度,但不包括其他标头字段。
  2. 如果长度不均匀,则字节顺序很重要。 在添加额外字符之前,您需要对其进行操作(或者实际上,在小端平台上,将其右移8位)。 编辑:忽略它在小端平台上是可以的。 大端需要转变。
  3. 当减少到16位时,总和可能会短路溢出。 在这种情况下,您需要将此结转送回计算(即添加1)。

试试这个版本的校验和计算function(它对我有用)

 uint16_t checksum (void * buffer, int bytes) { uint32_t total; uint16_t * ptr; int words; total = 0; ptr = (uint16_t *) buffer; words = (bytes + 1) / 2; // +1 & truncation on / handles any odd byte at end /* * As we're using a 32 bit int to calculate 16 bit checksum * we can accumulate carries in top half of DWORD and fold them in later */ while (words--) total += *ptr++; /* * Fold in any carries * - the addition may cause another carry so we loop */ while (total & 0xffff0000) total = (total >> 16) + (total & 0xffff); return (uint16_t) total; } 

然后像这样将它分配给校验和字段

 yourpkt->checksum = ~(checksum (buff, length));