5

我正在开发一个接收 IPv6 路由器广告数据包的 Linux 用户空间程序。作为 RFC4861 的一部分,我需要验证 ICMPv6 校验和。根据我的研究,如果您计算 IPv6 伪标头和数据包内容的互补校验和,则其中大部分是指一般的 IP 校验和,结果应该是 0xffff。但我不断收到 0x3fff 的校验和。

我的校验和实现有问题吗?Linux内核在将数据包传递给用户空间之前会验证ICMPv6校验和吗?是否有用于测试已知良好 ICMPv6 数据包的良好参考源?

uint16_t
checksum(const struct in6_addr *src, const struct in6_addr *dst, const void *data, size_t len) {
    uint32_t checksum = 0;
    union {
        uint32_t dword;
        uint16_t word[2];
        uint8_t byte[4];
    } temp;

    // IPv6 Pseudo header source address, destination address, length, zeros, next header
    checksum += src->s6_addr16[0];
    checksum += src->s6_addr16[1];
    checksum += src->s6_addr16[2];
    checksum += src->s6_addr16[3];
    checksum += src->s6_addr16[4];
    checksum += src->s6_addr16[5];
    checksum += src->s6_addr16[6];
    checksum += src->s6_addr16[7];

    checksum += dst->s6_addr16[0];
    checksum += dst->s6_addr16[1];
    checksum += dst->s6_addr16[2];
    checksum += dst->s6_addr16[3];
    checksum += dst->s6_addr16[4];
    checksum += dst->s6_addr16[5];
    checksum += dst->s6_addr16[6];
    checksum += dst->s6_addr16[7];

    temp.dword = htonl(len);
    checksum += temp.word[0];
    checksum += temp.word[1];

    temp.byte[0] = 0;
    temp.byte[1] = 0;
    temp.byte[2] = 0;
    temp.byte[3] = 58; // ICMPv6
    checksum += temp.word[0];
    checksum += temp.word[1];

    while (len > 1) {
        checksum += *((const uint16_t *)data);
        data = (const uint16_t *)data + 1;
        len -= 2;
    }

    if (len > 0)
        checksum += *((const uint8_t *)data);

    printf("Checksum %x\n", checksum);

    while (checksum >> 16 != 0)
        checksum = (checksum & 0xffff) + (checksum >> 16);

    checksum = ~checksum;

    return (uint16_t)checksum;
}
4

3 回答 3

1

while 循环是多余的。身体只会发生一次。

while (checksum >> 16 != 0)
    checksum = (checksum & 0xffff) + (checksum >> 16);

checksum = ~checksum;

return (uint16_t)checksum;

反而

checksum += checksum >> 16;

return (uint16_t)~checksum;

这是不必要的。len 始终为 16 位

temp.dword = htonl(len);
checksum += temp.word[0];
checksum += temp.word[1];

这是不必要的。常数总是 00 00 00 58,所以只需加上 58。

temp.byte[0] = 0;
temp.byte[1] = 0;
temp.byte[2] = 0;
temp.byte[3] = 58; // ICMPv6
checksum += temp.word[0];
checksum += temp.word[1];

除了处理整数的字节顺序和最后一个字节奇数字节的方式之外,您的算法看起来通常是正确的。从我阅读协议的方式来看,字节将按大端顺序求和,即字节 0xAB 0xCD 将被解释为 16 位 0xABCD。您的代码取决于您的机器的顺序。

构建整数的顺序将影响进位数,您将其正确添加到校验和中。但是如果您的代码与您的目标机器匹配,那么最后一个奇数字节是错误的。0xAB 将导致 0xAB00,而不是写入的 0x00AB。

于 2011-09-18T02:32:38.050 回答
1

如果这是在小端机器上运行,那么我认为您在累积校验和时需要(更多)字节交换。

例如,在小端机器上s6_addr16[0],典型的 IPv6 地址开始的元素2001:将包含0x0120,而不是0x2001。这会将您的进位位放在错误的位置。

长度代码看起来不错,因为您在htonl()那里使用,但0x00 0x00 0x00 0x58随后的消息累积逻辑却没有。我认为任何剩余的位也应该以高字节结束,而不是代码中发生的低字节。

此外,生成0x0000校验和时应该使用伪标头校验和字节。要验证校验和,请使用 IPv6 RA 中收到的实际校验和字节,然后您应该得到最终值。0xffff

于 2011-09-17T21:44:29.927 回答
1

我发现了我的错误:我有一个 256 字节的输入缓冲区,并假设on的iov_len元素被修改为返回接收到的数据长度。由于我的路由器通告的长度是恒定的 64 字节,因此该长度之间的差异会导致校验和出现恒定错误。我不需要更改字节顺序来验证校验和(尽管我没有奇数长度的 ICMPv6 数据包来验证我在奇数长度情况下对最终字节的处理。msg_iovrecvmsg()

此外,校验和的最终 NOT 仅用于计算校验和,而不是验证它。checksum()如果校验和有效,上述代码将返回 0。

于 2011-09-18T07:19:29.927 回答