解析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數據包部分計算完成