I'm working on a Linux userspace program that receives IPv6 router advertisement packets. As part of RFC4861 I need to verify the ICMPv6 checksum. Based on my research, most of which refers to the refers to the IP checksum in general if you compute the ones compliment checksum of the IPv6 pseudo header and the packet contents the result should be 0xffff. But I keep getting a checksum of 0x3fff.
Is there something wrong with my checksum implementation? does the Linux kernel verify the ICMPv6 checksum before passing the packets to userspace? is there a good reference source for known good ICMPv6 packets to test with?
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;
}