【问题标题】:Calculating checksum of ICMPv6 Packet in C在 C 中计算 ICMPv6 数据包的校验和
【发布时间】:2013-02-02 21:30:50
【问题描述】:

我正在尝试计算 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。 转储的输出是:

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

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

【问题讨论】:

  • 没有必要将数据包本身表示为uint16_t[],我认为这只会引起混淆。如果您需要添加字节,请执行此操作,即将数据包表示为 uint8_t 的数组,当然要保留确切的字节顺序。
  • @unwind 是的,我不需要这样做。但在最终程序中,无论如何我都不会手动输入数据包。在校验和中,我不需要添加字节,而是 16 位值。无论我将 uint8_t[] 还是 uint16_t[] 传递给函数,它都不会改变结果。
  • 你试过setsockopt(2) opname = IPV6_CHECKSUM 吗?

标签: c ipv6 checksum icmp


【解决方案1】:

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

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

【讨论】:

  • 更准确地说,ICMPv6 校验和考虑了一个 40 字节的伪报头,它是真实 IPv6 报头的派生,其组成如下(按顺序): - 16 字节源地址 - 目标地址的 16 个字节 - 4 个字节的高位有效载荷长度(与 IPv6 标头中的值相同) - 3 个字节零 - 1 个字节的 nextheader(因此,十进制 58)
【解决方案2】:

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

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

【讨论】:

  • 奇数字节处理似乎仅在缓冲区后面的字节为零时才有效。你不能假设它(它甚至可能是无效的内存)。
  • 你说得对……但在最后一次编辑之前,数组是由 uint16_t 元素组成的……不可能有无效的内存访问
  • 原来的原型其实是不正确的。校验和函数把数据看成 16 位字很方便,但并不代表实际上就是这样。
  • 如何考虑源IP地址和目的IP地址?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-30
  • 2017-03-21
  • 2015-08-31
  • 1970-01-01
相关资源
最近更新 更多