简体   繁体   中英

Calculating checksum of ICMPv6 Packet in C

I am trying to calculate the checksum of an ICMPv6 message (to be precise, a Neighbor Advertisement).

RFC 4443 describes it as the "16-bit one's complement of the one's complement sum of the entire ICMPv6 message"

There is also some example code on how to do that (although I think it's from IPv4, but the only difference is what is included in the sum, not how to calculate it): RFC 1071

I have taken a packet from wireshark and entered the shorts in host byte order. Then I print the correct checksum, zero it out and calculate mine. But they do not match. According to RFC 1071, endianess shouldn't be the problem (the result isn't just byte swapped, but completely off).

According to RFC 2460 #8.1 I need to include the "pseudo-header" into the calculation, containing only Src+Dst address, length as 32 Bit wide field and next header type.

The Calling code:

uint32_t payloadlen = htonl(32);
struct ip6_hdr *ip6;
struct nd_neighbor_advert *na;
size_t len, offset, tmplen;
uint8_t *tmppacket, icmp = 58;

uint8_t packet[] = {
            0x60, 0x00, 0x00, 0x00, 0x00, 0x20, 0x3A, 0xFF, 0x20, 0x01, 0x0D, 0xB8,
            0xDD, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
            0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x54, 0xFF,
            0xFE, 0x00, 0x22, 0x00, 0x88, 0x00, 0x54, 0xB9, 0x60, 0x00, 0x00, 0x00,
            0x20, 0x01, 0x0D, 0xB8, 0xDD, 0xDD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x00, 0x03, 0x54, 0x00, 0x00, 0x13
        };

na->nd_na_hdr.icmp6_cksum = 0;

tmplen = 40+sizeof(struct nd_neighbor_advert)+ICMP_OPT_LEN;
tmppacket = malloc(tmplen);
memset(tmppacket, 0, 40);
offset = sizeof(struct in6_addr);
memcpy(tmppacket, &ip6->ip6_src, offset);
memcpy(tmppacket+offset, &ip6->ip6_dst, offset);
memcpy(tmppacket+offset*2, &payloadlen, 4);
memcpy(tmppacket+39, &icmp, 1);
memcpy(tmppacket+40, packet+sizeof(struct ip6_hdr),
        sizeof(struct nd_neighbor_advert)+ICMP_OPT_LEN);

na = (struct nd_neighbor_advert *) (tmppacket+40);
na->nd_na_hdr.icmp6_cksum = checksum((uint16_t *) tmppacket, tmplen);
printf("Checksum calc: %hX\n", na->nd_na_hdr.icmp6_cksum);

dump((unsigned char *) tmppacket, tmplen);

The checksum function:

uint16_t checksum (uint16_t *addr, size_t bytes) {
    unsigned int i;
    uint16_t *p = addr;
    uint32_t sum = 0;

    /* Sum */
    for (i=bytes; i > 1; i -= 2)
        sum += *p++;

    /* If uneven length */
    if (i > 0)
        sum += (uint16_t) *((unsigned char *) (p));

    /* Carry */
    while ((sum & 0xFFFF0000) != 0)
        sum = (sum >> 16) + (sum & 0xFFFF);

    return ~((uint16_t) sum);
}

This is just quick and dirty to get it working at first. The code for "main" omits some stuff. Endianess in the checksum function shouldn't be a problem, also this packet has an even length.

The result should be B954, but I get DB32. The output of dump is:

Packet size: 72
2001 0DB8 DDDD 0000 0000 0000 0000 0100 FE80 0000 0000 0000 0203 54FF FE00 2200 0000 0020 0000 003A 8800 32DB 6000 0000 2001 0DB8 DDDD 0000 0000 0000 0000 0100 0201 0003 5400 0013

Thanks for the tips so far. If you have an idea what could be still wrong, I'd appreciate your input.

I think there are three problems with your code:

  1. You checksum the IPv6 header, which you shouldn't. The checksum should cover both IPv6 addresses and the length, but not other header fields.
  2. Endianity matters if the length is uneven. Before adding the extra character, you need to ntohs it (or actually, on a little-endian platform, shift it right 8 bits). EDIT: Ignoring it is OK on little-endian platforms. The shift is needed on big-endian.
  3. When reducing to 16 bits, the sum may overflow a short. In this case, you need to feed this carry back to the calculation (ie add 1).

Try this version of the checksum calculation function (it worked for me)

uint16_t
checksum (void * buffer, int bytes) {
   uint32_t   total;
   uint16_t * ptr;
   int        words;

   total = 0;
   ptr   = (uint16_t *) buffer;
   words = (bytes + 1) / 2; // +1 & truncation on / handles any odd byte at end

   /*
    *   As we're using a 32 bit int to calculate 16 bit checksum
    *   we can accumulate carries in top half of DWORD and fold them in later
    */
   while (words--) total += *ptr++;

   /*
    *   Fold in any carries
    *   - the addition may cause another carry so we loop
    */
   while (total & 0xffff0000) total = (total >> 16) + (total & 0xffff);

   return (uint16_t) total;
}

then assign it to the checksum field like this

yourpkt->checksum = ~(checksum (buff, length));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM