2011-09-17 126 views
5

我正在開發一個接收IPv6路由器通告包的Linux用戶空間程序。作爲RFC4861的一部分,我需要驗證ICMPv6校驗和。基於我的研究,如果計算IPv6僞頭的恭維校驗和,並且結果應該是0xffff,那麼大多數都會引用IP校驗和。但我一直得到0x3fff的校驗和。如何驗證ICMPv6校驗和? (爲什麼我一直得到0x3fff的校驗和?)

我的校驗和執行有問題嗎?在將數據包傳遞給用戶空間之前,Linux內核是否驗證ICMPv6校驗和?是否有一個很好的參考資料來源,用於測試已知的良好ICMPv6數據包?

uint16_t 
checksum(const struct in6_addr *src, const struct in6_addr *dst, const void *data, size_t len) { 
    uint32_t checksum = 0; 
    union { 
     uint32_t dword; 
     uint16_t word[2]; 
     uint8_t byte[4]; 
    } temp; 

    // IPv6 Pseudo header source address, destination address, length, zeros, next header 
    checksum += src->s6_addr16[0]; 
    checksum += src->s6_addr16[1]; 
    checksum += src->s6_addr16[2]; 
    checksum += src->s6_addr16[3]; 
    checksum += src->s6_addr16[4]; 
    checksum += src->s6_addr16[5]; 
    checksum += src->s6_addr16[6]; 
    checksum += src->s6_addr16[7]; 

    checksum += dst->s6_addr16[0]; 
    checksum += dst->s6_addr16[1]; 
    checksum += dst->s6_addr16[2]; 
    checksum += dst->s6_addr16[3]; 
    checksum += dst->s6_addr16[4]; 
    checksum += dst->s6_addr16[5]; 
    checksum += dst->s6_addr16[6]; 
    checksum += dst->s6_addr16[7]; 

    temp.dword = htonl(len); 
    checksum += temp.word[0]; 
    checksum += temp.word[1]; 

    temp.byte[0] = 0; 
    temp.byte[1] = 0; 
    temp.byte[2] = 0; 
    temp.byte[3] = 58; // ICMPv6 
    checksum += temp.word[0]; 
    checksum += temp.word[1]; 

    while (len > 1) { 
     checksum += *((const uint16_t *)data); 
     data = (const uint16_t *)data + 1; 
     len -= 2; 
    } 

    if (len > 0) 
     checksum += *((const uint8_t *)data); 

    printf("Checksum %x\n", checksum); 

    while (checksum >> 16 != 0) 
     checksum = (checksum & 0xffff) + (checksum >> 16); 

    checksum = ~checksum; 

    return (uint16_t)checksum; 
} 
+0

這是運行在一個大的還是小的endian機器? – Alnitak

+0

Little endian(x86_64)我讀過的驗證校驗和應該是endian獨立的。 – dlundquist

+0

+1,僅用於「根據我的研究」 – Flexo

回答

1

while循環過度殺傷。身體只會發生一次。

while (checksum >> 16 != 0) 
    checksum = (checksum & 0xffff) + (checksum >> 16); 

checksum = ~checksum; 

return (uint16_t)checksum; 

相反

checksum += checksum >> 16; 

return (uint16_t)~checksum; 

這是不必要的。 len始終爲16位

temp.dword = htonl(len); 
checksum += temp.word[0]; 
checksum += temp.word[1]; 

這是不必要的。不變的是永遠00 00 00 58,所以只需添加58

temp.byte[0] = 0; 
temp.byte[1] = 0; 
temp.byte[2] = 0; 
temp.byte[3] = 58; // ICMPv6 
checksum += temp.word[0]; 
checksum += temp.word[1]; 

你的算法通常會正確的,否則,除了你處理整數的字節順序和最後一個字節奇數字節的方式。從我如何讀取協議中,字節將按照大端順序進行求和,即字節0xAB 0xCD將被解釋爲16位0xABCD。您的代碼取決於您的機器的順序。

整數構建的順序將影響正確添加到校驗和中的進位數。但是,如果您的代碼與您的目標機器相匹配,那麼最後一個奇數字節是錯誤的。 0xAB將導致0xAB00,而不是0x00AB寫入。

+0

同意所有的代碼建議,這是我的第四次嘗試,我試圖儘可能地遵循RFC 1071 4.1示例。我在想最後一個字節是錯誤的,但我的情況下數據包正好是64字節長。 – dlundquist

1

如果這是一個little-endian的機器上運行的話,我認爲你需要有詳細得多的字節交換而積累的校驗。

例如,在一個小端機上,開始2001:的典型IPv6地址的s6_addr16[0]元素將包含0x0120,而不是0x2001。這將把你的攜帶位置放在錯誤的地方。

由於您在那裏使用htonl(),所以長度代碼顯示爲正常,但0x00 0x00 0x00 0x58和後續的消息累積邏輯沒有。我認爲任何剩下的位都應該以高字節結束,而不是在代碼中發生的低字節。

此外,使用0x0000作爲僞首標校驗和字節是你應該做什麼,當生成的校驗和。若要驗證驗證校驗和使用IPv6 RA中收到的實際校驗和字節,則應該獲得0xffff作爲最終值。

+0

我的理解是校驗和是從IPv6僞頭中的數據完成的,其中地址處於固定淨工作完善,不受主人影響。只有長度字段已被轉換爲主機endineness。此外,我正在接收來自三臺路由器的路由器廣告,並且此校驗和算法爲所有收到的廣告計算相同的校驗和。 – dlundquist

1

我發現我的錯誤:我有一個256字節的輸入緩衝區,並假設元素msg_iovrecvmsg()被修改爲返回接收到的數據的長度。由於我的路由器廣告的長度是常量64字節,這種長度之間的差異導致校驗和中的常量錯誤。我不需要改變字節順序來驗證校驗和(雖然我沒有一個奇數長度的ICMPv6數據包來驗證我處理奇數長度的情況下的最後一個字節)

另外,校驗和只有在計算校驗和時才需要校驗,如果校驗和是有效的,那麼checksum()將返回0