IP/UDP校驗和的C程式碼實現
阿新 • • 發佈:2019-01-24
序言
之前寫資料封裝程式的時候涉及到IP頭校驗和、UDP校驗和計算,在這裡回顧。
IP頭校驗和原理
IP校驗和只針對IP資料包頭部進行。在路由資料轉發的過程中如果對每一個數據包的資料都進行校驗將會是很耗時的事情,而且TCP提供了資料的差錯控制和擁塞管理。
IP頭部校驗的原理如下,分發送端和接收端來介紹:
傳送端:
- 置零:把IP資料報頭的校驗和欄位置零
- 分組:把首部看成以16位(2位元組)為單位的數字組成
- 求和取反:依次進行二進位制反碼求和(先進行二進位制求和,然後對和取反)
接收端:
- 分組:把首部看成以16位為單位的數字組成
- 求和取反:然後依次進行二進位制反碼求和,包括校驗和欄位
- 校驗:檢查計算出的校驗和的結果是否等於零
注:
1. 關於二進位制求和取反和反碼求和是一樣的。即先取反後相加與先相加後取反結果一樣。
2. 進位相加:求和過程中產生的進位(最高位的進位)需要加到低16位(見示例程式),之後還要把該次加法最高位產生的進位再加到低16位。
3. 接收端校驗時,由於傳送方是將校驗和欄位置零求和取反的,所以接收端校驗後得‘0 - 正確’,否則丟棄該報文。
- 計算舉例
- 報頭內容:0x4500 0x0020 0x0FB8 0x0000 0x8011 0x0000 0xC0A8 0x0A9F 0xC0A8 0x0AC7
- 依次相加:所得結果為0x26B9F,然後將 0x0002 + 0x6B9F = 0x6BA1
- 然後再將 0x6BA1 取反得 0x945E 賦值給校驗和欄位
UDP校驗和原理
UDP校驗和不僅需要考慮UDP報頭、UDP淨荷,還需要加上UDP偽首部。
即計算校驗和需要用到三個部分資訊
- UDP偽首部
- UDP首部
- UDP的資料部分
具體計算方法為
- 置零:將校驗和欄位置零
- 分組:按每16位求和得出一個32位的數(如果有的話)
- 求和取反:如果這個32位的數,高16位不為0,則高16位加低16位再得到一個32位的數;重複第2步直到高16位為0,將低16位取反,得到校驗和
IP頭校驗和計算示例程式
short int IpCheckSum(short int *ip_head_buffer, int ip_hdr_len)
{
unsigned int check_sum = 0; //校驗和初始化
/* 校驗和計算 */
while (ip_hdr_len > 1)
{
check_sum += *ip_head_buffer++; //一次移動2位元組,注意short int實際型別長度
ip_hdr_len -= sizeof(short int);
}
/* 如果有options欄位;一般為3位元組 */
if (ip_hdr_len > 0)
{
check_sum += *(short int *)ip_head_buffer; //如果只有1位元組,需要型別轉換
}
/* 進位相加 */
check_sum = (check_sum & 0x0000FFFF) + (check_sum >> 16);
check_sum += (check_sum >> 16); //上次進位相加的進位再加一次
check_sum = ~check_sum; //取反
return check_sum;
}
當輸入不為short int或short int型別長度不確定時,可自定義型別變數並做型別強制轉換
u_int16_t IpCheckSum(struct iphdr *ip_head_buffer,int iphdr_len) //輸入是報頭快取、報頭長
{
ip_head_buffer -> check = 0; //置零
char *temp_hdr = (char *)ip_head_buffer; //型別強制轉換
unsigned int temp_high = 0, temp_low = 0, result = 0;
int i;
for (i = 0; i < 10; i++)
{
temp_high = *((char *)(temp_hdr + 2 * i)) & 0x00ff; //高8位
printf("%02x\n",temp_high);
temp_low = *((char *)(temp_hdr + 2 * i + 1)) & 0x00ff; //低8位;必須加0x00ff,否則輸出格式不對,計算結果也有誤
printf("%02x\n",temp_low);
result = result + ((temp_high << 8) + temp_low);
}
while (result & 0xffff0000)
{
result = ((result & 0xffff0000) >> 16) + (result & 0x0000ffff);
}
result = 0xffff - result; //或~result直接取反
return result;
}
IP校驗和完整示例程式
#include <netinet/ip.h> /* for the IPv4 header */
#include <stdio.h>
#include <stdlib.h>
u_int16_t IpCheckSum(struct iphdr *ip_head_buffer,int iphdr_len);
int main()
{
struct iphdr *ip_header;
ip_header = (struct iphdr *)malloc(sizeof(struct iphdr));
printf("build IP header\n");
ip_header->version = 4; /* we create an IP header version 4 */
ip_header->ihl = 5; /* min. IPv4 header length (in 32-bit words) */
int len = 0;
len += ip_header->ihl * 4;
ip_header->tos = 0; /* TOS is not important for the example */
ip_header->tot_len = htons(len + 18);
printf("%02x\n",len + 18);
ip_header->id = 0; /* ID is not important for the example */
ip_header->frag_off = 0; /* No packet fragmentation */
ip_header->ttl = 1; /* TTL is not important for the example */
ip_header->protocol = 134; /* protocol number */
ip_header -> check = 0;
ip_header->saddr = htonl(0x01020304); /* source address 1.2.3.4 */
ip_header->daddr = htonl(0x05060708); /* destination addr. 5.6.7.8 */
ip_header -> check = IpCheckSum(ip_header,len);
printf("%02x\n", ip_header -> check);
return 0;
}
u_int16_t IpCheckSum(struct iphdr *ip_head_buffer,int iphdr_len) //輸入是報頭快取、報頭長
{
ip_head_buffer -> check = 0; //置零
char *temp_hdr = (char *)ip_head_buffer; //型別強制轉換
unsigned int temp_high = 0, temp_low = 0, result = 0;
int i;
for (i = 0; i < 10; i++)
{
temp_high = *((char *)(temp_hdr + 2 * i)) & 0x00ff; //高8位
printf("%02x\n",temp_high);
temp_low = *((char *)(temp_hdr + 2 * i + 1)) & 0x00ff; //低8位;必須加0x00ff,否則輸出格式不對,計算結果也有誤
printf("%02x\n",temp_low);
result = result + ((temp_high << 8) + temp_low);
}
while (result & 0xffff0000)
{
result = ((result & 0xffff0000) >> 16) + (result & 0x0000ffff);
}
result = 0xffff - result;
return result;
}
UDP校驗和計算示例程式
short int UdpCheckSum(int ip_src_addr, int ip_dst_addr, int *udp_buffer, int udp_size)
{
/* 定義偽首部 */
unsigned char rawBuffer[20000]; //定義快取陣列
struct pseudo_hdr
{
//struct in_addr src;
int src; //源IP地址,32bit;看源程式中ip_src_addr和ip_dst_addr型別而定
//struct in_addr dst;
int dst; //目的IP地址,32bit
//uint8_t mbz;
char mbz; //全0,8bit
//uint8_t protocol;
char protocol; //協議欄位,8bit
//uint16_t len;
short int len; //UDP長度,16bit;UDP首部+淨荷總長
};
struct pseudo_hdr *phead;
phead = (struct pseudo_hdr *)rawBuffer; //快取陣列轉換成結構體指標
int phead_len = sizeof(struct pseudo_hdr); //計算偽首部佔用的位元組長度
/* 偽首部賦值,即陣列中phead_len部分 */
short int check_sum = 0; //校驗和欄位置零;原來程式中定義為unsigned long
//phead -> src.s_addr = ip_src_addr;
phead -> src = ip_src_addr;
//phead -> dst.s_addr = ip_dst_addr;
phead -> dst = ip_dst_addr;
//phead -> mbz = 0;
phead -> mbz = 0;
//phead -> protocol = 17;
phead -> protocol = 17; //UDP協議程式碼為17
//phead -> len = htons(udp_size);
phead -> len = htons(udp_size);
/* 計算校驗和 */
memcpy(rawBuffer + phead_len, udp_buffer, udp_size); //UDP報頭和淨荷部分
check_sum = IpCheckSum((short int *)rawBuffer, phead_len + udp_size); //使用IP校驗和計算函式
return check_sum;
}
Acknowledgements:
http://www.cnblogs.com/xyl-share-happy/archive/2012/06/08/2542015.html
http://blog.csdn.net/u013005025/article/details/52870857
2017.05.16
由於作者水平有限,文中錯誤和不足之處歡迎讀者交流指正