一、计算检验和的步骤
检验和的计算都是一个模板,只是各种检验和的初始数据不一样
总结一下就是:求和、回卷、取反
- 把校验和字段设置为0。
- 求和:把需要校验的数据看成以16位为单位的数字组成,依次进行二进制求和。
- 回卷:求和后超过16位的加到低16位。
- 取反:最后结果取反码就是检验和。
(1)可以每两个求和后回卷,再求和,在回卷,直到全部求和,然后取反
(2)也可以全部求和,然后一直回卷直到高 16 位全部为 0,然后取反
一般采用第二种
另外UDP、TCP数据报的长度可以为奇数字节,因为计算时是16位为单位,所以此时计算校验和时需要在最后一个填充字节0(只是计算校验和用,不发送出去)。
二、接收端校验校验和步骤
把需要校验的内容(包括校验和字段)看成以16位为单位的数字,依次进行二进制反码求和,如果结果是0表示正确,否则表示错误。
三、说明
校验和覆盖的内容:
- IP首部校验和:IP首部
- ICMP校验和:ICMP首部+ICMP数据
- UDP、TCP校验和:首部+数据+12个字节伪首部(源IP地址、目的IP地址、协议、TCP/UDP包长)
四、IP首部校验和
1、结构图如下
2、详细计算过程
为了不出问题,按照报文格式依次编写数据即可
版本和首部长度放在一起
- 版本:4
- 首部长度:20字节 ==》 5
- 总长度:46(首部 + 数据)
- 标识:
- 标志:是否分片,3 bit(不分片——010)
(1) 一个比特保留为以后用;
(2)第二个比特是DF(Don’t Fragment):“不分片”比特,若为1,IP将不对数据报进行分片,若无法将此数据报通过任何可用网络转发,则丢弃,并发送一个ICMP差错报文给起始端,若为0, 则在需要时将数据报分片;
(3)第三个比特是MF(More Fragment):“更多分片”比特,为1,表示后面还有更多的分片,为0,则表示是最后的分片。 - 偏移:偏移量,13 bit
- 生存时间:64
- 协议:17(UDP)
- 检验和:置0
- 源IP地址:192.168.1.81
- 目的IP地址:192.168.1.166
然后把这些数据全部二进制相加求和,回卷,取反。得到校验和:0xa443
不分片
五、UDP校验和
1、结构图如下
2、详细计算过程
为了不出问题,按照报文格式依次编写数据即可
- 源IP地址:192.168.1.81
- 目的IP地址:192.168.1.166
- 上层协议类型:17(UDP)
- UDP长度:26(首部 + 数据)
- 源端口:6081
- 目的端口:7081
- UDP长度:26(首部 + 数据)
- 检验和:置0
- 数据:81(18个,只是为了凑齐最小帧64字节)
然后把这些数据全部二进制相加求和,回卷,取反。得到校验和:0x6c2c
六、代码及解释(代码一样,只是初始数据不一样)
1、检验和是16 bit 的
2、
- 数据有的是 8 位:所以两个一组,高位(在前面)左移8位(后面补0)变16 位,在后面 8 位加上低位,就组成了 16 bit
- 数据有的是 16 位:直接相加
3、因为求和会溢出,所以声明一个 32 bit 来存放和,回卷就直接把高 16 位,加到低 16 位,直到高 16 位就变成 0,然后再强转格式即可。
4、最后要声明的是数据的个数
- 偶数:直接相加
- 奇数:最后一位左移 8 位
#include <stdio.h>
#include <malloc.h>
typedef unsigned char uint8_t; // 8 比特
typedef unsigned short uint16_t; // 16 比特
typedef unsigned int uint32_t; // 32 比特
typedef unsigned long uint64_t; // 64 比特
//单个数据是:8 bit数据 ipv4
uint8_t ipv4_8[] = {
0x45, //版本号 4,首部长度 20 字节 ==> 5
0x00, //服务类型,默认
0x00, 0x2e, //总长度(首部 + 数据)
0x12, 0x34, //16 标识位
0x40, 0x00, //3 比特标志不分片010 ,13 比特片偏移
0x40, //生存时间 64
0x11, //上层协议,以 UDP 为例 17
0x00, 0x00, //首部校验和
0xc0, 0xa8, 0x01, 0x51, //源 IP 地址 192.168.1.81
0xc0, 0xa8, 0x01, 0xa6, //目的地 IP 地址 192.168.1.166
};
//单个数据是:8 bit数据 udp
uint8_t udp_8[] = {
0xc0, 0xa8, 0x01, 0x51, //源 IP 地址 192.168.1.81
0xc0, 0xa8, 0x01, 0xa6, //目的地 IP 地址 192.168.1.166
0x00, 0x11, //协议类型 UDP = 17
0x00, 0x1a, //UDP 长度(首部 + 数据)
0x17, 0xc1, //源端口
0x1b, 0xa9, //目的地端口
0x00, 0x1a, //UDP 长度
0x00, 0x00, //检验和置 0
0x51, 0x51, 0x51, 0x51, 0x51, 0x51, //数据 18 字节
0x51, 0x51, 0x51, 0x51, 0x51, 0x51,
0x51, 0x51, 0x51, 0x51, 0x51, 0x51
};
//单个数据是:16 bit数据 ipv4
uint16_t ipv4_16[] = {
0x4500, 0x002e,
0x1234, 0x4000,
0x4011, 0x0000,
0xc0a8, 0x0151,
0xc0a8, 0x01a6,
};
//单个数据是:16 bit数据 udp
uint16_t udp_16[] = {
0xc0a8, 0x0151, 0xc0a8, 0x01a6,
0x0011, 0x001a, 0x17c1, 0x1ba9,
0x001a, 0x0000, 0x5151, 0x5151,
0x5151, 0x5151, 0x5151, 0x5151,
0x5151, 0x5151, 0x5151
};
uint16_t CheckSum_8(uint8_t array[], int len) {
uint32_t sum = 0; //32 比特,存放和
int i;
for (i = 0; i < len - 1; i = i + 2) { //高字节和低字节
sum += ((uint16_t)array[i] << 8) + ((uint16_t)array[i + 1]); //每 16 比特相加
}
if (i + 1 == len) { //如果位奇数,多出来的扩充字节至16
sum += (uint16_t)array[i] << 8;
}
while (sum >> 16) { //直到高 16 比特全为 0,检验和在低 16 比特(一般一次就行了)
sum = (sum >> 16) + (sum & 0xffff); //回卷
}
sum += (sum >> 16);
return (uint16_t)(~sum); //强制转换结果取反
}
uint16_t CheckSum_16(uint16_t array[], int len) {
uint32_t sum = 0;
for (int i = 0; i < len - 1; i++) { //16 比特直接相加(除最后一个)
sum += array[i];
}
if (array[len - 1] >> 8) { //判断最后一个是 0x00 还是 0x0000
sum += array[len - 1]; //如果是 0x0000,直接加
}
else {
sum += array[len - 1] << 8; //如果是 0x00,移到高位相加
}
while (sum >> 16) { //直到高 16 比特全为 0,检验和在低 16 比特(一般一次就行了)
sum = (sum >> 16) + (sum & 0xffff); //回卷
}
sum += (sum >> 16);
return (uint16_t)(~sum); //强制转换结果取反
}
int main() {
int udp_len_8 = sizeof(udp_8) / sizeof(udp_8[0]);
unsigned short check1 = CheckSum_8(udp_8, udp_len_8);
printf("udp_8 检验和为:%x\n", check1);
int udp_len_16 = sizeof(udp_16) / sizeof(udp_16[0]);
unsigned short check2 = CheckSum_16(udp_16, udp_len_16);
printf("udp_16 检验和为:%x\n", check2);
int ipv4_len_8 = sizeof(ipv4_8) / sizeof(ipv4_8[0]);
unsigned short check3 = CheckSum_8(ipv4_8, ipv4_len_8);
printf("ipv4_8 检验和为:%x\n", check3);
int ipv4_len_16 = sizeof(ipv4_16) / sizeof(ipv4_16[0]);
unsigned short check4 = CheckSum_16(ipv4_16, ipv4_len_16);
printf("ipv4_16检验和为:%x\n", check4);
return 0;
}
七、wireshark抓包查看
待验证
八、其它
原码、补码、反码
正整数部分:
(1)原码、反码和补码都一样
负整数部分:
(1)原码和反码的相互转换:符号位不变,数值位按位取反
(2)原码和补码的相互转换:符号位不变,数值位按位取反,末位再加1
九、参考
1、IP首部校验和结果
https://wenku.baidu.com/view/c5ad9131581b6bd97f19ea85.html
IP首部检验和:2f01
uint8_t data[] = {
0x45, 0x00, 0x00, 0x30, //IP 首部检验和
0x4a, 0x3e, 0x40, 0x00,
0x80, 0x06, 0x00, 0x00,
0xc0, 0xa8, 0x00, 0x37,
0xc0, 0xa8, 0x00, 0x01
};
2、UDP校验和
https://blog.csdn.net/stone_yu/article/details/81611067
UDP检验和:285c
uint8_t data[] = {
0x0a,0xaa,0x3b,0xbf, //UDP 检验和
0xd2,0x0e,0x96,0x0d,
0x00,0x11,
0x00,0x1c,
0xd1,0x23,
0x27,0x42,
0x00,0x1c,
0x00,0x00,
0x6c,0x41,0x56,0x61,
0x00,0x00,0x0e,0x00,
0xf8,0xb6,0xd4,0x01,
0x93,0x13,0x00,0x00,
0x00,0x00,0x00,0x00
};