【问题标题】:Computing 16-bit checksum of ICMPv6 header计算 ICMPv6 标头的 16 位校验和
【发布时间】:2015-12-12 20:38:05
【问题描述】:

我想问一下我根据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)。

在这个伪标头之后,校验和继续以 ICMPv6 消息,其中校验和最初设置为零。这 校验和计算是根据互联网协议执行的 标准使用 16 位的补码求和,然后是 补充校验和本身并将其插入校验和 字段

这是否意味着我也应该将校验和字段上带有 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 标头):

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 计算时,我得到了不同的答案。

【问题讨论】:

    标签: python checksum


    【解决方案1】:

    我会尝试用一个例子来解决你的问题。

    让我们从 Wireshark wiki 获取 this sample capture,这样我们就有了相同的数据包,在 Wireshark 中打开它,然后我们获取第一个 ICMPv6 数据包(第 3 帧)。

    请注意此数据包的至少一个重要事项:IPv6 层的有效负载长度是 32 (0x20)。

    注意:要在 Wireshark 上将数据包提取为字符串,请选择数据包和所需的层(例如 Ipv6),然后:right click > copy > bytes > hex stream

    构建伪标题

    要计算校验和,首先要做的是根据RFC 2460 section 8.1构建伪头。

    校验和是根据伪标头 ICMPv6 数据包计算的。

    IPv6 版本的 ICMP [ICMPv6] 在 它的校验和计算

    构建我们需要的伪头文件:

    • 源 IP
    • 目标 IP
    • 上层数据包长度
    • 下一个标题

    源 IP 和目标 IP 来自 IPv6 层。

    Next Header 字段固定为 58:

    ICMP 伪标头中的 Next Header 字段包含值 58,它标识 ICMP 的 IPv6 版本。

    上层数据包长度:

    伪报头中的Upper-Layer Packet Length是 上层报头和数据(例如,TCP 报头加上 TCP 数据)。 一些上层协议携带自己的长度信息(例如, UDP 头中的 Length 字段);对于此类协议,即 伪标头中使用的长度。其他协议(如 TCP)可以 不携带自己的长度信息,在这种情况下使用的长度 在伪报头中是来自 IPv6 报头的有效负载长度,减去 IPv6 标头之间存在的任何扩展标头的长度 和上层header。

    在我们的例子中,上层(ICMPv6)不携带长度字段,所以在这种情况下,我们必须使用来自 IPv6 层的 payload length 字段,即 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。

    为了计算校验和,校验和字段首先设置为零。

    在这种情况下,我使用来自 ICMPv6 的 typecode 字段作为一个 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() 函数的输入是伪报头和数据包的 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)
    

    代码示例

    代码非常难看,但返回了正确的校验和。在我们的示例中,此代码返回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()
    

    【讨论】:

    • 几点说明:您的答案将校验和打包为一个 32 位字段(带有“>I”),但它应该是一个 16 位字段(source)导致 ICMP 数据包长度为 34 个字节,而不是您用于校验和计算的 32 个字节。它似乎还假设 ICMP 数据包的类型是 135/NDP,但 OP 特别提到了 128/Echo Request。如果我错了,请纠正我,我正在尝试将您的解决方案用于我自己的基于 python 的 ping 程序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多