2015-12-12 490 views
1

我想问一下根据ICMPv6协议计算16位校验和的方案是否正确。我试图按照Wikipedia,但我不确定主要是关于两件事情。计算ICMPv6头的16位校验和

首先是什么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 

所以我应该用e672替换xxxx。这是对的吗?当我尝试用wireshark计算这个时,我得到了一个不同的答案。

回答

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数据包上计算。

ICMP的IPv6版本[的ICMPv6]包括在 其校验和计算

上述伪首要构建伪报头,我们需要:

  • 源IP
  • 目的地IP
  • 上层包长度
  • Next Header

Source和Dest IP来自IPv6层。

Next Header字段被固定为58:

在伪首部用于ICMP Next Header字段包含值58,它标识ICMP的IPv6版本。

上层协议包长度:

在伪首上层分组长度是 长度上层报头和数据(例如,TCP首部加上TCP数据) 。一些上层协议携带它们自己的长度信息(例如,在UDP报头中的长度字段);对于这样的协议,即伪报头中使用的长度为 。其他协议(例如TCP)do 不携带它们自己的长度信息,在这种情况下,在伪报头中使用的长度为 是来自IPv6报头的有效负载长度,减去在IPv6报头之间存在的任何扩展报头的长度减去 和上层头。

在我们的情况下,上层(ICMPv6的)不携带长度字段,因此,在这种情况下,我们必须使用有效载荷长度字段从IPv6层,它是32(0×20)为这个数据包。

让我们尝试一些代码:

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 mentionned校验字段必须在任何计算之前设置为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() 
+0

夫妇的注意事项:你的回答包的校验和一个32位的字段(“> I”),但它应该是一个16位字段([来源](https://开头的连接。 wikipedia.org/wiki/Internet_Control_Message_Protocol_version_6#Packet_format))导致长度为34个字节的ICMP数据包,而不是您用于校验和计算的32个数据包。它似乎也假定ICMP数据包的类型是135/NDP,但OP特别提到了128/Echo Request。如果我错了,请纠正我,我正在尝试将您的解决方案用于我自己的基于python的ping程序。 – ocket8888