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