计算C中ICMPv6包的校验和

时间:2013-02-18 12:38:18

标签: c ipv6 checksum icmp

我正在尝试计算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);

校验和功能:

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。 转储的输出是:

  

数据包大小: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

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

2 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分: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));