计算ICMPv6头的16位校验和

时间:2015-12-12 20:38:05

标签: python checksum

我想问一下根据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个八位字节,正如我在下面的代码中所说的那样?

然后我有理解这个问题(再次来自维基)。

  

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

这是否意味着我应该在校验和字段中将整个ICMPv6帧与0000一起添加到校验和中?

我尝试用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进行计算时,我会得到一个不同的答案。

1 个答案:

答案 0 :(得分:2)

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

让我们从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
  • Dest IP
  • 上层数据包长度
  • 下一个标题

源IP和目标IP来自IPv6层。

Next Header字段固定为58:

  

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

上层数据包长度:

  

伪标头中的上层分组长度是长度   上层报头和数据(例如,TCP报头加TCP数据)。   一些上层协议携带自己的长度信息(例如,   UDP头中的Length字段);对于这样的协议,就是这样   伪标头中使用的长度。其他协议(如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

应该像这样调用代码:

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()