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

我正在开发一个接收IPv6路由器通告包的Linux用户空间程序。 作为RFC4861的一部分,我需要validationICMPv6校验和。 根据我的研究,如果计算IPv6伪头的恭维校验和,结果应该是0xffff,那么大部分是指通常的IP校验和。 但是我一直得到0x3fff的校验和。

我的校验和执行有问题吗? 在将数据包传递给用户空间之前,Linux内核是否validation了ICMPv6校验和? 是否有一个好的参考资料来源,用于testing已知的好的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; } 

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

除非你处理整数的字节顺序和最后一个字节的奇数字节,否则你的算法看起来一般是正确的。 从我如何读取协议,字节将按big-endian顺序求和,即字节0xAB 0xCD将被解释为16位0xABCD。 您的代码取决于您的机器的顺序。

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

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

例如,在一个小端机上,从2001:开始的典型IPv6地址的s6_addr16[0]元素将包含0x0120 ,而不是0x2001 。 这将把你的携带位置错误。

由于您使用的是htonl()所以长度代码显示为OK,但是0x00 0x00 0x00 0x58和后续的消息积累逻辑不会。 我认为任何剩下的位都应该以高字节结束,而不是在代码中发生的低字节。

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

我发现我的错误:我有一个256字节的输入缓冲区,并认为recvmsg()iov_len元素被修改为返回接收到的数据的长度。 由于我的路由器广告的长度在一个常量64字节,这个长度之间的差异导致校验和中的常量错误。 我不需要改变字节顺序来验证校验和(虽然我没有一个奇数长度的ICMPv6数据包来验证我处理奇数长度情况下的最后一个字节。

另外,校验和的最后一个NOT也只是计算校验和,而不是验证校验和。 使用上面的代码checksum()将返回0,如果校验和是有效的。