序言
之前写数据封装程序的时候涉及到IP头校验和、UDP校验和计算,在这里回顾。
关于IP和UDP报头结构,可参考这篇文章:
http://blog.youkuaiyun.com/baidu_35692628/article/details/70495352#t10
IP头校验和原理
IP校验和只针对IP数据包头部进行。在路由数据转发的过程中如果对每一个数据包的数据都进行校验将会是很耗时的事情,而且TCP提供了数据的差错控制和拥塞管理。
IP头部校验的原理如下,分发送端和接收端来介绍:
发送端:
置零:把IP数据报头的校验和字段置零
分组:把首部看成以16位(2字节)为单位的数字组成
求和取反:依次进行二进制反码求和(先进行二进制求和,然后对和取反)
接收端:
分组:把首部看成以16位为单位的数字组成
求和取反:然后依次进行二进制反码求和,包括校验和字段
校验:检查计算出的校验和的结果是否等于零
注:
- 关于二进制求和取反和反码求和是一样的。即先取反后相加与先相加后取反结果一样。
- 进位相加:求和过程中产生的进位(最高位的进位)需要加到低16位(见示例程序),之后还要把该次加法最高位产生的进位再加到低16位。
- 接收端校验时,由于发送方是将校验和字段置零求和取反的,所以接收端校验后得‘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.youkuaiyun.com/u013005025/article/details/52870857
https://blog.youkuaiyun.com/stone_yu/article/details/81611067
https://blog.youkuaiyun.com/limanjihe/article/details/85270291