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:
ntohs
it (or actually, on a little-endian platform, shift it right 8 bits).
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.