I would like to ask if my solution of computing 16 bit checksum according to ICMPv6 protocol is correct. I try to follow Wikipedia, but I am not sure mainly about two things.
First is what the packet length means - is it the packet length of the whole ICMPv6 packet without checksum, or only the payload? Is it in octets as in IPv6? What is the length of this ICMPv6 echo request?
6000 # beginning of IPv6 packet
0000 0000 3a00 FE80 0000 0000 0000 0202
B3FF FE1E 8329 FE80 0000 0000 0000 0202
B3FF FE1E 8330
8000 xxxx # this is beginning of the ICMP packet - type and checksum
a088 0000 0001 # from here including this line I compute the length
0203 0405 0607 0809 0a0b 0c0d 0e0f 1011
1213 1415 1617 1819 1a1b 1c1d 1e1f 2021
2223 2425 2627 2829 2a2b 2c2d 2e2f 3031
3233
Does it mean that the length of the above is 56 octets as I state in the code below?
Then I have problem understanding this (again from wiki).
Following this pseudo header, the checksum is continued with the ICMPv6 message in which the checksum is initially set to zero. The checksum computation is performed according to Internet protocol standards using 16-bit ones' complement summation, followed by complementing the checksum itself and inserting it into the checksum field
Does it mean I should add the whole ICMPv6 frame with 0000 on the checksum field to the checksum too?
I tried to write a simple program for this in Python:
# START OF Pseudo header
# we are doing 16 bit checksum hence quadruplets
## source IP
sip = ['FE80', '0000', '0000', '0000', '0202', 'B3FF', 'FE1E', '8329']
## destination IP
dip = ['FE80', '0000', '0000', '0000', '0202', 'B3FF', 'FE1E', '8330']
## next header - 32 bits, permanently set to (58)_dec ~ (88)_hex
nh = ['0000', '0088']
## packet length -> see my question above: (56)_dec ~ (38)_hex
lng = ['0038']
png = "8000 0000 a088 0000 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f 3031 3233".split(" ")
# END OF PSEUDO HEADER
tot = sip + dip + lng + nh + png # from what the sum is going to be counted
stot = sum([int(x, 16) for x in tot]) % 65535 # we are in 16 bits world
rstot = 65535 - stot # wrap around
res = hex(rstot) # convert to hex
print(stot, rstot)
print(res)
check = bin(rstot + stot)
print(check) # all ones
that is for the following ICMPv6 ping requests (with IPv6 header):
d392 30fb 0001 d393 30fb 0001 86dd 6000
0000 0000 3a00 FE80 0000 0000 0000 0202
B3FF FE1E 8329 FE80 0000 0000 0000 0202
B3FF FE1E 8330 8000 xxxx a088 0000 0001
0203 0405 0607 0809 0a0b 0c0d 0e0f 1011
1213 1415 1617 1819 1a1b 1c1d 1e1f 2021
2223 2425 2627 2829 2a2b 2c2d 2e2f 3031
3233
and it gives output:
27741 37794
0xe672 # correct?
0b1111111111111111
So I should just replace xxxx with e672. Is it correct? When I try to compute this with wireshark, I get a different answer.
I'll try to address your questions with an example.
Let's take this sample capture from the Wireshark wiki so we have the same packet, open it in Wireshark and let's take the first ICMPv6 packet (frame 3).
Note at least one important thing for this packet: the payload length for the IPv6 layer is 32 (0x20).
Note: to extract a packet as a string on Wireshark, select the packet and the desired layer (e.g Ipv6) and then: right click > copy > bytes > hex stream
Building the pseudo header
To calculate the checksum, the 1st thing to do is to build the pseudo header according to RFC 2460 section 8.1.
The checksum is calculated on the pseudo-header and the ICMPv6 packet.
The IPv6 version of ICMP [ICMPv6] includes the above pseudo-header in its checksum computation
To build the pseudo header we need:
Source and Dest IPs are from the IPv6 layer.
Next Header field is fixed to 58:
The Next Header field in the pseudo-header for ICMP contains the value 58, which identifies the IPv6 version of ICMP.
Upper-Layer Packet Length :
The Upper-Layer Packet Length in the pseudo-header is the length of the upper-layer header and data (e.g., TCP header plus TCP data). Some upper-layer protocols carry their own length information (e.g., the Length field in the UDP header); for such protocols, that is the length used in the pseudo- header. Other protocols (such as TCP) do not carry their own length information, in which case the length used in the pseudo-header is the Payload Length from the IPv6 header, minus the length of any extension headers present between the IPv6 header and the upper-layer header.
In our case, the upper layer (ICMPv6) doesn't carry a length field, so in this case, we have to use the payload length field from the IPv6 layer, which is 32 (0x20) for this packet.
Let try some code:
def build_pseudo_header(src_ip, dest_ip, payload_len):
source_ip_bytes = bytearray.fromhex(src_ip)
dest_ip_bytes = bytearray.fromhex(dest_ip)
next_header = struct.pack(">I", 58)
upper_layer_len = struct.pack(">I", payload_len)
return source_ip_bytes + dest_ip_bytes + upper_layer_len + next_header
Code should be called like this :
SOURCE_IP = "fe80000000000000020086fffe0580da"
DEST_IP = "fe80000000000000026097fffe0769ea"
pseudo_header = build_pseudo_header(SOURCE_IP, DEST_IP, 32)
Building the ICMPV6 packet
As mentionned in the rfc 4443 section 2.3 the checksum field must be set to 0 prior any calculation.
For computing the checksum, the checksum field is first set to zero.
In this case I use the type and code fields from ICMPv6 as a signle 16-bit value. The checksum field is removed and the remainder of the packet is simply called "remainder":
TYPE_CODE = "8700"
REMAINDER = "00000000fe80000000000000026097fffe0769ea01010000860580da"
Building the ICMPv6 part of the packet for checksum calculation:
def build_icmpv6_chunk(type_and_code, other):
type_code_bytes = bytearray.fromhex(type_and_code)
checksum = struct.pack(">I", 0) # make sure checksum is set to 0 here
other_bytes = bytearray.fromhex(other)
return type_code_bytes + checksum + other_bytes
Called as follow:
TYPE_CODE = "8700"
REMAINDER = "00000000fe80000000000000026097fffe0769ea01010000860580da"
icmpv6_chunk = build_icmpv6_chunk(TYPE_CODE, REMAINDER)
Calculating the checksum
Calculating the checksum is done according to RFC 1701. The main difficulty in Python is to wrap the sum in a 16-bit quantity.
The input to the calc_checksum() function is the concatenation of the pseudo header and the ICMPv6 part of the packet (with the checksum set to 0):
Python example:
def calc_checksum(packet):
total = 0
# Add up 16-bit words
num_words = len(packet) // 2
for chunk in struct.unpack("!%sH" % num_words, packet[0:num_words*2]):
total += chunk
# Add any left over byte
if len(packet) % 2:
total += ord(packet[-1]) << 8
# Fold 32-bits into 16-bits
total = (total >> 16) + (total & 0xffff)
total += total >> 16
return (~total + 0x10000 & 0xffff)
Code example
The code is quite ugly but returns the correct checksum. In our example, this code returns 0x68db which is right according to wireshark.
#!/usr/local/bin/python3
# -*- coding: utf8 -*-
import struct
SOURCE_IP = "fe80000000000000020086fffe0580da"
DEST_IP = "fe80000000000000026097fffe0769ea"
TYPE_CODE = "8700"
REMAINDER = "00000000fe80000000000000026097fffe0769ea01010000860580da"
def calc_checksum(packet):
total = 0
# Add up 16-bit words
num_words = len(packet) // 2
for chunk in struct.unpack("!%sH" % num_words, packet[0:num_words*2]):
total += chunk
# Add any left over byte
if len(packet) % 2:
total += ord(packet[-1]) << 8
# Fold 32-bits into 16-bits
total = (total >> 16) + (total & 0xffff)
total += total >> 16
return (~total + 0x10000 & 0xffff)
def build_pseudo_header(src_ip, dest_ip, payload_len):
source_ip_bytes = bytearray.fromhex(src_ip)
dest_ip_bytes = bytearray.fromhex(dest_ip)
next_header = struct.pack(">I", 58)
upper_layer_len = struct.pack(">I", payload_len)
return source_ip_bytes + dest_ip_bytes + upper_layer_len + next_header
def build_icmpv6_chunk(type_and_code, other):
type_code_bytes = bytearray.fromhex(type_and_code)
checksum = struct.pack(">I", 0)
other_bytes = bytearray.fromhex(other)
return type_code_bytes + checksum + other_bytes
def main():
icmpv6_chunk = build_icmpv6_chunk(TYPE_CODE, REMAINDER)
pseudo_header = build_pseudo_header(SOURCE_IP, DEST_IP, 32)
icmpv6_packet = pseudo_header + icmpv6_chunk
checksum = calc_checksum(icmpv6_packet)
print("checksum: {:#x}".format(checksum))
if __name__ == '__main__':
main()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With