请问我根据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
Run Code Online (Sandbox Code Playgroud)
这是否意味着上面的长度是 56 个八位字节,正如我在下面的代码中所说的那样?
然后我在理解这一点时遇到了问题(再次来自维基)。
在此伪标头之后,校验和继续使用 ICMPv6 消息,其中校验和最初设置为零。校验和计算是根据互联网协议标准使用 16 位 1 的补码求和来执行的,然后对校验和本身进行补码并将其插入校验和字段
这是否意味着我也应该将校验和字段上带有 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
Run Code Online (Sandbox Code Playgroud)
这是针对以下 ICMPv6 ping 请求(带有 IPv6 标头):
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
Run Code Online (Sandbox Code Playgroud)
它给出了输出:
27741 37794
0xe672 # correct?
0b1111111111111111
Run Code Online (Sandbox Code Playgroud)
所以我应该xxxx
用e672
. 这是正确的吗?当我尝试用wireshark计算这个时,我得到了不同的答案。
我会试着用一个例子来解决你的问题。
让我们进行这个示例捕获从 Wireshark wiki,因此我们有相同的数据包,在 Wireshark 中打开它,让我们获取第一个 ICMPv6 数据包(第 3 帧)。
请注意此数据包的至少一件重要事情:有效载荷长度 IPv6 层为 32 (0x20)。
注意:要在 Wireshark 上将数据包提取为字符串,请选择数据包和所需的层(例如 Ipv6),然后:right click
> copy
> bytes
>hex stream
构建伪标头
要计算校验和,要做的第一件事是根据RFC 2460 第 8.1 节构建伪标头。
校验和是在伪头上计算的,并且ICMPv6 数据包计算的。
ICMP [ICMPv6] 的 IPv6 版本在其校验和计算中包含上述伪标头
要构建伪标头,我们需要:
源和目标 IP 来自 IPv6 层。
Next Header 字段固定为 58:
ICMP 伪头中的 Next Header 字段包含值 58,它标识 ICMP 的 IPv6 版本。
上层数据包长度:
伪头中的Upper-Layer Packet Length是上层头和数据(例如TCP头加上TCP数据)的长度。一些上层协议携带自己的长度信息(例如UDP头中的长度字段);对于此类协议,这是伪报头中使用的长度。其他协议(如 TCP)不携带自己的长度信息,在这种情况下,伪头中使用的长度是来自 IPv6 头的有效载荷长度减去 IPv6 头和上层之间存在的任何扩展头的长度-层标题。
在我们的例子中,上层(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
Run Code Online (Sandbox Code Playgroud)
代码应该这样调用:
SOURCE_IP = "fe80000000000000020086fffe0580da"
DEST_IP = "fe80000000000000026097fffe0769ea"
pseudo_header = build_pseudo_header(SOURCE_IP, DEST_IP, 32)
Run Code Online (Sandbox Code Playgroud)
构建 ICMPV6 数据包
正如rfc 4443 第 2.3 节中提到的,在进行任何计算之前,校验和字段必须设置为 0。
为了计算校验和,校验和字段首先设置为零。
在这种情况下,我使用ICMPv6 中的type
和code
字段作为信号 16 位值。校验和字段被删除,数据包的其余部分简称为“剩余部分”:
TYPE_CODE = "8700"
REMAINDER = "00000000fe80000000000000026097fffe0769ea01010000860580da"
Run Code Online (Sandbox Code Playgroud)
构建数据包的 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
Run Code Online (Sandbox Code Playgroud)
调用如下:
TYPE_CODE = "8700"
REMAINDER = "00000000fe80000000000000026097fffe0769ea01010000860580da"
icmpv6_chunk = build_icmpv6_chunk(TYPE_CODE, REMAINDER)
Run Code Online (Sandbox Code Playgroud)
计算校验和
根据RFC 1701计算校验和。Python 中的主要困难是将总和包装成 16 位数量。
的输入 calc_checksum()
函数是伪标头和数据包的 ICMPv6 部分的串联(校验和设置为 0):
蟒蛇示例:
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)
Run Code Online (Sandbox Code Playgroud)
代码示例
代码很丑,但返回正确的校验和。在我们的示例中,此代码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()
Run Code Online (Sandbox Code Playgroud)