2017-12-27 551 views
0

我一直在嘗試爲IPv6實現我自己的簡化TCP/IP協議棧,目前我的目標是能夠回答ICMPv6迴應請求。
我使用以下結構,用於存儲在稍後的計算所需要的數據:C中ICMPv6校驗和計算返回錯誤結果

typedef uint16_t n_uint16_t;  //network byte order 
typedef uint32_t n_uint32_t; 

n_uint16_t htons(uint16_t n); 
n_uint32_t htonl(uint32_t n); 

struct ipv6hdr { 
    n_uint32_t  vtcfl;   //version, traffic class, flow label 

    n_uint16_t  payload_len; 
    unsigned char nexthdr; 
    unsigned char hop_limit; 

    unsigned char saddr[IP6_ALEN]; //IP6_ALEN = 16 
    unsigned char daddr[IP6_ALEN]; 
}; 

struct icmp6hdr { 
    unsigned char type; 
    unsigned char code; 
    n_uint16_t  cksum; 

    union { 
     n_uint32_t  un_data32[1]; /* type-specific field */ 
     n_uint16_t  un_data16[2]; /* type-specific field */ 
     unsigned char un_data8[4]; /* type-specific field */ 
    } dataun; 
}; 

(也有定義的類型,用於處理endianity)

我使用以下函數來計算的ICMPv6校驗和。前兩個是相應地創建ICMPv6數據包和IPv6僞頭字段的緩衝區。然後我計算緩衝區的16位字段之和的餘數。

n_uint16_t icmpv6_chksum(struct ipv6hdr *ip6, struct icmp6hdr *icmp) { 
    unsigned char buf[65535]; 
    unsigned char *ptr = &(buf[0]); 
    int chksumlen = 0; 

    //ICMPv6 type 
    memcpy(ptr, &icmp->type, sizeof(icmp->type)); 
    ptr += sizeof(icmp->type); 
    chksumlen += sizeof(icmp->type); 

    //ICMPv6 code 
    memcpy(ptr, &icmp->code, sizeof(icmp->code)); 
    ptr += sizeof(icmp->code); 
    chksumlen += sizeof(icmp->code); 

    //ICMPv6 payload 
    memcpy(ptr, &icmp->dataun.un_data32, sizeof(icmp->dataun.un_data32)); 
    ptr += sizeof(icmp->dataun.un_data32); 
    chksumlen += sizeof(icmp->dataun); 

    return pseudoheaderchksum(buf, ptr, ip6, chksumlen); 
} 

n_uint16_t pseudoheaderchksum(unsigned char *buf, unsigned char *ptr, struct ipv6hdr *ip6, int chksumlen) { 
    //source address 
    memcpy(ptr, &ip6->saddr, sizeof(ip6->saddr)); 
    ptr += sizeof(ip6->saddr); 
    chksumlen += sizeof(ip6->saddr); 

    //dest address 
    memcpy(ptr, &ip6->daddr, sizeof(ip6->daddr)); 
    ptr += sizeof(ip6->daddr); 
    chksumlen += sizeof(ip6->daddr); 

    //upper layer length 
    n_uint32_t upprlen = 0; 
    upprlen += sizeof(ip6->payload_len); 
    memcpy(ptr, &upprlen, sizeof(upprlen)); 
    ptr += 4; 
    chksumlen += 4; 

    //3 bytes of zeros, then next header byte 
    *ptr = 0; 
    ptr++; 
    *ptr = 0; 
    ptr++; 
    *ptr = 0; 
    ptr++; 
    chksumlen += 3; 
    memcpy(ptr, &ip6->nexthdr, sizeof(ip6->nexthdr)); 
    ptr += sizeof(ip6->nexthdr); 
    chksumlen += sizeof(ip6->nexthdr); 
    return chksum((uint16_t *) buf, chksumlen); 
} 

//counting internet checksum 
n_uint16_t chksum(uint16_t *buf, int len) { 
    int count = len; 
    n_uint32_t sum = 0; 
    n_uint16_t res = 0; 
    while (count > 1) { 
     sum += (*(buf)); 
     buf++; 
     count -= 2; 
    } 
    //if number of bytes was odd 
    if (count > 0) { 
     sum += *(unsigned char *) buf; 
    } 
    while (sum >> 16) { 
     sum = sum + (sum >> 16); 
    } 
    res = (n_uint16_t) sum; 
    return ~res; 
} 

不幸的是,我測試了現有的數據包,捕獲的ICMPv6的校驗和比我的計算結果不同。我做錯了什麼?

PS。我正在使用libpcap來捕獲和/或發送原始的以太網數據包。


編輯:什麼功能做

icmpv6_chksum更詳細的說明 - 獲得ICMPv6報文加在它的封裝的IPv6數據包的報頭的結構。爲後面的計算創建一個空緩衝區,並複製ICMPv6類型,ICMPv6代碼,ICMPv6消息(基本上是整個ICMPv6數據包,除校驗和字段外,在計算過程中無論如何都爲零)的值。
將緩衝區,其第一個空位指針和IPv6頭部傳遞到僞首標校驗塊

pseudoheaderchecksum - 獲取緩衝區,其第一個空位置指針和IPv6頭部。複製IPv6源地址,IPv6目標地址,數據長度(在這種情況下,它是我們的ICMPv6數據包長度),然後是3個零字節和IPv6下一個標頭值(= ICMPv6標頭)。
該功能將添加到所謂的「IPv6僞首部」的緩衝器,其由前述的字段組成,如在例如RFC 2460.
現在填充的緩衝區被傳遞到chksum函數 - 一個應該爲緩衝區統計Internet Checksum的函數。

chksum - 獲取緩衝區及其以字節爲單位的長度。它將緩衝區的16位碎片彙總在一起(如果需要,最後添加一個奇數字節)。溢出被添加到16位和。最後,函數返回計算結果的補碼(二進制反轉)。
在這個函數中,我試圖計算Internet Checksum,如RFC 1071中所述。查看示例實現,它應該(雖然我不是100%確定,算法描述通常是模糊的)正確。


編輯2
好吧,我注意到,我並沒有包括ICMPv6的消息體(即跟隨標題的內容),所以現在

n_uint16_t icmpv6_chksum(struct ipv6hdr *ip6, struct icmp6hdr *icmp, unsigned char* data, int len) { 
    unsigned char buf[65535]; 
    unsigned char *ptr = &(buf[0]); 
    int chksumlen = 0; 

    //ICMPv6 type 
    memcpy(ptr, &icmp->type, sizeof(icmp->type)); 
    ptr += sizeof(icmp->type); 
    chksumlen += sizeof(icmp->type); 

    //ICMPv6 code 
    memcpy(ptr, &icmp->code, sizeof(icmp->code)); 
    ptr += sizeof(icmp->code); 
    chksumlen += sizeof(icmp->code); 

    //ICMPv6 rest of header 
    memcpy(ptr, &icmp->dataun.un_data32, sizeof(icmp->dataun.un_data32)); 
    ptr += sizeof(icmp->dataun.un_data32); 
    chksumlen += sizeof(icmp->dataun); 

    unsigned char *tmp = data; 
    for(int i=0; i<len; i++){ 
     memcpy(ptr, &tmp, sizeof(unsigned char)); 
     ptr += sizeof(unsigned char); 
     tmp += sizeof(unsigned char); 
     chksumlen += sizeof(unsigned char); 
    } 
    return pseudoheader_chksum(buf, ptr, ip6, chksumlen); 
} 

哪裏unsigned char *data的是,一切都來了在ICMPv6頭後(不包括以太網FCS)。
不幸的是,校驗和計數仍然無法正常工作。

+0

請添加此校驗和算法的完整和完整的描述!我沒有看到任何'htonl()'和 - 朋友的電話,爲什麼? –

+0

這些結構可能需要一些'包裝'雜誌? –

+0

您在校驗和中不包含IPv6數據包標頭。將僞頭添加到ICMP數據報,然後計算僞頭和數據報的校驗和。 –

回答

0

我懷疑你正在失去跟蹤數字應該在主機順序,以及他們應該在網絡順序。僅僅在n_uint16_tuint16_t之間來回轉換不會做任何事情。您需要使用htons() and ntohs()和其他相關功能。