计算 ICMPv6 的 16 位校验和 header
Computing 16-bit checksum of ICMPv6 header
请问我根据ICMPv6协议计算16位校验和的方案是否正确。我尝试遵循 Wikipedia,但我主要不确定两件事。
首先是the packet length
的意思——它是没有校验和的整个ICMPv6数据包的数据包长度,还是只有有效载荷?它是像 IPv6 一样的八位字节吗?此 ICMPv6 回显请求的长度是多少?
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
上面的长度是不是像我在下面的代码中所说的那样是56个八位字节?
那么我在理解这个问题时遇到了问题(再次来自 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
这是否意味着我也应该将校验和字段上带有 0000 的整个 ICMPv6 帧也添加到校验和中?
我试图在 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
用于以下 ICMPv6 ping 请求(使用 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
它给出输出:
27741 37794
0xe672 # correct?
0b1111111111111111
所以我应该将 xxxx
替换为 e672
。这是正确的吗?当我尝试用 wireshark 计算这个时,我得到了不同的答案。
我将尝试通过示例来解决您的问题。
让我们从 Wireshark wiki 获取 this sample capture 所以我们有相同的数据包,在 Wireshark 中打开它,让我们获取第一个 ICMPv6 数据包(第 3 帧)。
请注意此数据包的至少一件重要事项:IPv6 层的有效负载长度 是 32 (0x20)。
注意:要在 Wireshark 上将数据包提取为字符串,select 数据包和所需的层(例如 Ipv6),然后:right click
> copy
> bytes
> hex stream
构建伪header
要计算校验和,首先要做的是根据RFC 2460 section 8.1.
构建伪header
校验和是在 pseudo-header 和 ICMPv6 数据包上计算的。
The IPv6 version of ICMP [ICMPv6] includes the above pseudo-header in
its checksum computation
要构建伪 header 我们需要:
- 来源IP
- 目标 IP
- Upper-Layer 数据包长度
- 下一个Header
源 IP 和目标 IP 来自 IPv6 层。
下一个Header字段固定为58:
The Next Header field in the pseudo-header for ICMP contains the value 58, which identifies the IPv6 version of ICMP.
Upper-Layer 数据包长度:
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.
在我们的例子中,上层(ICMPv6)不携带长度字段,所以在这种情况下,我们必须使用 IPv6 层的有效载荷长度字段,对于这个数据包来说是 32 (0x20)。
让我们尝试一些代码:
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
代码应该这样调用:
SOURCE_IP = "fe80000000000000020086fffe0580da"
DEST_IP = "fe80000000000000026097fffe0769ea"
pseudo_header = build_pseudo_header(SOURCE_IP, DEST_IP, 32)
构建 ICMPV6 数据包
如 rfc 4443 section 2.3 中所述,在任何计算之前必须将校验和字段设置为 0。
For computing the checksum, the checksum field is first set to zero.
在这种情况下,我使用来自 ICMPv6 的 type
和 code
字段作为一个 signle 16 位值。校验和字段被删除,数据包的剩余部分简称为“剩余”:
TYPE_CODE = "8700"
REMAINDER = "00000000fe80000000000000026097fffe0769ea01010000860580da"
构建用于校验和计算的数据包的 ICMPv6 部分:
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
调用如下:
TYPE_CODE = "8700"
REMAINDER = "00000000fe80000000000000026097fffe0769ea01010000860580da"
icmpv6_chunk = build_icmpv6_chunk(TYPE_CODE, REMAINDER)
计算校验和
根据RFC 1701计算校验和。 Python 的主要难点是将总和包装在 16 位数量中。
calc_checksum()
函数的输入是数据包的伪 header 和 ICMPv6 部分(校验和设置为 0)的串联:
Python 例子:
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)
代码示例
代码很丑陋,但 returns 校验和正确。在我们的示例中,此代码 returns 0x68db
根据 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()
请问我根据ICMPv6协议计算16位校验和的方案是否正确。我尝试遵循 Wikipedia,但我主要不确定两件事。
首先是the packet length
的意思——它是没有校验和的整个ICMPv6数据包的数据包长度,还是只有有效载荷?它是像 IPv6 一样的八位字节吗?此 ICMPv6 回显请求的长度是多少?
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
上面的长度是不是像我在下面的代码中所说的那样是56个八位字节?
那么我在理解这个问题时遇到了问题(再次来自 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
这是否意味着我也应该将校验和字段上带有 0000 的整个 ICMPv6 帧也添加到校验和中?
我试图在 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
用于以下 ICMPv6 ping 请求(使用 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
它给出输出:
27741 37794
0xe672 # correct?
0b1111111111111111
所以我应该将 xxxx
替换为 e672
。这是正确的吗?当我尝试用 wireshark 计算这个时,我得到了不同的答案。
我将尝试通过示例来解决您的问题。
让我们从 Wireshark wiki 获取 this sample capture 所以我们有相同的数据包,在 Wireshark 中打开它,让我们获取第一个 ICMPv6 数据包(第 3 帧)。
请注意此数据包的至少一件重要事项:IPv6 层的有效负载长度 是 32 (0x20)。
注意:要在 Wireshark 上将数据包提取为字符串,select 数据包和所需的层(例如 Ipv6),然后:right click
> copy
> bytes
> hex stream
构建伪header
要计算校验和,首先要做的是根据RFC 2460 section 8.1.
构建伪header校验和是在 pseudo-header 和 ICMPv6 数据包上计算的。
The IPv6 version of ICMP [ICMPv6] includes the above pseudo-header in its checksum computation
要构建伪 header 我们需要:
- 来源IP
- 目标 IP
- Upper-Layer 数据包长度
- 下一个Header
源 IP 和目标 IP 来自 IPv6 层。
下一个Header字段固定为58:
The Next Header field in the pseudo-header for ICMP contains the value 58, which identifies the IPv6 version of ICMP.
Upper-Layer 数据包长度:
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.
在我们的例子中,上层(ICMPv6)不携带长度字段,所以在这种情况下,我们必须使用 IPv6 层的有效载荷长度字段,对于这个数据包来说是 32 (0x20)。
让我们尝试一些代码:
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
代码应该这样调用:
SOURCE_IP = "fe80000000000000020086fffe0580da"
DEST_IP = "fe80000000000000026097fffe0769ea"
pseudo_header = build_pseudo_header(SOURCE_IP, DEST_IP, 32)
构建 ICMPV6 数据包
如 rfc 4443 section 2.3 中所述,在任何计算之前必须将校验和字段设置为 0。
For computing the checksum, the checksum field is first set to zero.
在这种情况下,我使用来自 ICMPv6 的 type
和 code
字段作为一个 signle 16 位值。校验和字段被删除,数据包的剩余部分简称为“剩余”:
TYPE_CODE = "8700"
REMAINDER = "00000000fe80000000000000026097fffe0769ea01010000860580da"
构建用于校验和计算的数据包的 ICMPv6 部分:
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
调用如下:
TYPE_CODE = "8700"
REMAINDER = "00000000fe80000000000000026097fffe0769ea01010000860580da"
icmpv6_chunk = build_icmpv6_chunk(TYPE_CODE, REMAINDER)
计算校验和
根据RFC 1701计算校验和。 Python 的主要难点是将总和包装在 16 位数量中。
calc_checksum()
函数的输入是数据包的伪 header 和 ICMPv6 部分(校验和设置为 0)的串联:
Python 例子:
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)
代码示例
代码很丑陋,但 returns 校验和正确。在我们的示例中,此代码 returns 0x68db
根据 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()