问题
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;
}
回答1:
The while loop is overkill. The body will only happen once.
while (checksum >> 16 != 0)
checksum = (checksum & 0xffff) + (checksum >> 16);
checksum = ~checksum;
return (uint16_t)checksum;
Instead
checksum += checksum >> 16;
return (uint16_t)~checksum;
This is unnecessary. len is always 16-bit
temp.dword = htonl(len);
checksum += temp.word[0];
checksum += temp.word[1];
This is unnecessary. The constant is always 00 00 00 58, so just add 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];
Your algorithm looks generally right otherwise, except for the way you handle the endianness of the integers and the last byte odd-numbered byte. From how I read the protocol, the bytes are to be summed in big-endian order, i.e., the bytes 0xAB 0xCD are to be interpreted as the 16-bit 0xABCD. Your code depends on the ordering of your machine.
The order that the integers are built will affect the number of carries, which you are adding correctly into the checksum. But if your code matches your target machine, then the last odd-numbered byte is wrong. 0xAB would result in 0xAB00, not 0x00AB as written.
回答2:
If this is running on a little-endian machine then I think you need (much) more byte swapping while accumulating the checksum.
For example on a little endian machine the s6_addr16[0]
element of a typical IPv6 address starting 2001:
will contain 0x0120
, and not 0x2001
. This will put your carry bits in the wrong place.
The length code appears OK since you are using htonl()
there, but the 0x00 0x00 0x00 0x58
and subsequent message accumulation logic does not. I think any left over bits should end up in the high byte too, not the low byte as happens in your code.
Also, using 0x0000
for the pseudo header checksum bytes is what you should do when generating the checksum. To validate the checksum use the actual checksum bytes received in the IPv6 RA, and then you should get 0xffff
as the eventual value.
回答3:
I found my bug: I had a 256 byte input buffer and assumed the iov_len
element of msg_iov
on recvmsg()
was modified to return length of data received. Since the length of my router advertisements where a constant 64 bytes, the difference between this lengths resulted in a constant error in the checksum. I did not need to change the byte order to verify the checksum (although I have not had an odd length ICMPv6 packet to verify my handling of the final byte in the odd length case.
Also, the final NOT of the checksum is only necessary to calculate the checksum, not to verify it. With the above code checksum()
will return 0 if the checksum is valid.
来源:https://stackoverflow.com/questions/7457863/how-do-i-validate-the-icmpv6-checksum-why-am-do-i-keep-getting-a-checksum-of-0