如何验证ICMPv6校验和?(为什么我一直得到0x3fff的校验和?)

dlu*_*ist 5 c sockets linux ipv6 icmp

我正在开发一个接收IPv6路由器通告数据包的Linux用户空间程序.作为RFC4861的一部分,我需要验证ICMPv6校验和.基于我的研究,其中大部分指的是一般来说,如果你计算IPv6伪报头的补码校验和以及数据包内容,结果应该是0xffff,那么就是指IP校验和.但我一直得到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;
}
Run Code Online (Sandbox Code Playgroud)

Unc*_*leO 1

while 循环太过分了。身体只会发生一次。

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

checksum = ~checksum;

return (uint16_t)checksum;
Run Code Online (Sandbox Code Playgroud)

反而

checksum += checksum >> 16;

return (uint16_t)~checksum;
Run Code Online (Sandbox Code Playgroud)

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

temp.dword = htonl(len);
checksum += temp.word[0];
checksum += temp.word[1];
Run Code Online (Sandbox Code Playgroud)

这是不必要的。该常数始终为 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];
Run Code Online (Sandbox Code Playgroud)

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

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