2015-12-12 487 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