1

我正在尝试计算 ICMPv6 消息的校验和(准确地说,是邻居广告)。

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

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

我从 Wireshark 拿了一个数据包,并按主机字节顺序输入了短裤。然后我打印正确的校验和,将其归零并计算我的。但它们不匹配。根据 RFC 1071,字节序不应该是问题(结果不仅仅是字节交换,而是完全关闭)。

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

校验和函数:

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”的代码省略了一些东西。校验和函数中的字节序应该不是问题,而且这个数据包的长度是偶数。

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


大小

感谢到目前为止的提示。如果您知道还有什么问题,我会很感激您的意见。

4

2 回答 2

3

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

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

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

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));
于 2013-02-18T13:11:26.673 回答