解析IPV4報文和IPV6報文的checksum的算法

解析IPV4報文和IPV6報文的checksum的算法:

校驗和(checksum)算法,簡單的說就是16位累加的反碼運算:

計算函數如下:

我們在計算時是主機字節序,計算的結果封裝成IP包時是網絡字節序,注意這兩者之間的區別,我們在從IP包裏讀取要轉化爲主機字節序,往IP包裏存入時要轉化爲網絡字節序在存入。

UINT32 Checksum(UINT32 cksum, VOID *pBuffer, UINT32 size)

{

INT8 num = 0;

UINT8 *p = (UINT8 *)pBuffer;



if ((NULL == pBuffer) || (0 == size))

{

    return cksum;

}



while (size > 1)

{

   cksum += ((UINT16)p[num] << 8 & 0xff00) | (UINT16)p[num + 1] & 0x00FF;

2個字節累加,先取網絡字節序低位左移8位(變成主機字節序高位),與(加)上 網絡字節序中的高位(主機字節序地位),即網絡字節序要先變成主機字節序在進行累加,

    size  -= 2;

    num   += 2;

}

if (size > 0)

如果長度爲奇數

{

    cksum += ((UINT16)p[num] << 8) & 0xFFFF;

如果總的字節數爲奇數,則最後一個字節單獨相加

    num += 1;

}



while (cksum >> 16)

{

    cksum = (cksum & 0xFFFF) + (cksum >> 16);

累加完畢將結果中高16位再加到低16位上,重複這一過程直到高16位爲全0

}



return cksum;

}

注意:UINT32 cksum的類型,這裏是4個字節的,防止在累加的過程中,數據溢出,(例如0xFF累加時就會內存溢出)

詳細的計算過程和原理如下

一:

ip頭的計算:

直接對頭部數據進行累加(不包括原來的checksum值):

1、ipv4包頭

   ipHeadLen  = (pIpHeader->ver_ihl & 0x0F) << 2;

在ipv4頭中,版本類型和頭長度加在一起是1個字節(8位),各佔4位,版本類型在前,長度在後,所以要取長度只能取低4位,

   pIpHeader->chksum = 0;

因爲不包括原來的checksum值,所以在每次計算前先把checksum的值置0,然後計算

   sum = Checksum(0, (VOID *)pIpHeader, ipHeadLen);

對整個ip包頭的累加

   pIpHeader->chksum = HTONS((UINT16)(~sum));

結果爲計算值的反碼,(別忘轉化爲網絡字節序)

2、ipv6包頭

在ipv6中已經省略了checksum部分,但在後面的部分要有的,比如TCP/UDP包,別高興的太早

二、

TCP/UDP報文的計算(舉例UDP):

這裏的checksum包含兩部分,一部分是僞頭的累加,還有一部分是UDP包的累加(不包括原來的checksum值)

僞頭有分ipv4和ipv6兩種,分別包含如下幾部分,這裏做下比較

IPV4

IPV6

目的地址

4字節(32位)

16字節(128位)

源地址

4字節(32位)

16字節(128位)

協議類型

1字節(8位)(Protocol)

1字節(8位)(next header)

(TCP/UDP)長度

2字節(16位)

2字節(16位)

1、 ipv4類型的:

第一部分,僞頭部分的計算:

sum = 0;

    udpLen  = sizeof(UDP_HEADER_T) + dhcpLen;

UDP的長度= UDP的包頭長度+ UDP的數據長度

sum += udpLen;

或者,下面也是一樣的,這裏就是網絡字節序和主機字節序的區別了,上面的是(主機字節序)直接累加,下面的是網絡字節序,一定要變成主機字節序後累加

   pUdpHeader->len = HTONS(udpLen);

主機字節序轉化爲網絡字節序,存入數據包中,一定要注意,我們做的所有累加也是網絡字節序,這裏一定要搞清楚,以防混淆搞錯了

   sum += (pUdpHeader->len >> 8 & 0x00FF);

2個字節的累加,先取網絡字節序的高位,右移8位,變成主機字節序的低位,累加

   sum += (pUdpHeader->len << 8 & 0xFF00);

在取網絡字節序的低位,左移8位,變成主機字節序的高位,累加

   sum  = Checksum(sum, (VOID *)&pIpHeader->saddr, 4);

    sum  = Checksum(sum, (VOID *)&pIpHeader->daddr, 4);

對4位的地址進行累加

   sum += ((UINT16)pIpHeader->proto & 0x00FF);

對1位的協議類型進行累加

僞頭部分計算完成

第二部分,UDP數據包的計算

         pUdpHeader->chksum = 0;

注意:每次計算前別忘先把checksum的值置0,然後計算

   sum = Checksum(sum, (VOID *)pUdpHeader, udpLen);

對整個UDP包的累加

   pUdpHeader->chksum = HTONS((UINT16)(~sum));

結果爲計算值的反碼,(別忘轉化爲網絡字節序)

UDP數據包部分計算完成

2、 ipv6類型的:

第一部分,僞頭部分的計算:

sum = 0;

    udpLen  = sizeof(UDP_HEADER_T) + dhcpLen;

sum += udpLen;

或者

   pUdpHeader->len = HTONS(udpLen);

    sum += (pUdpHeader->len >> 8 & 0x00FF);

    sum += (pUdpHeader->len << 8 & 0xFF00);



    sum  = Checksum(sum, (VOID *)&pIpHeader->saddr, 16);

    sum  = Checksum(sum, (VOID *)&pIpHeader->daddr, 16);

對16位的地址進行累加

   sum += ((UINT16)pIpHeader->proto & 0x00FF);             

僞頭部分計算完成

第二部分,UDP數據包的計算

         pUdpHeader->chksum = 0;

注意:每次計算前別忘先把checksum的值置0,然後計算

   sum = Checksum(sum, (VOID *)pUdpHeader, udpLen);

對整個UDP包的累加

   pUdpHeader->chksum = HTONS((UINT16)(~sum));

結果爲計算值的反碼,(別忘轉化爲網絡字節序)

UDP數據包部分計算完成

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章