Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I validate the ICMPv6 checksum? (Why am do I keep getting a checksum of 0x3fff?)

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;
}
like image 963
dlundquist Avatar asked Oct 25 '22 00:10

dlundquist


2 Answers

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.

like image 139
uncleO Avatar answered Oct 27 '22 09:10

uncleO


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.

like image 26
Alnitak Avatar answered Oct 27 '22 09:10

Alnitak