检验和计算——C语言

一、计算检验和的步骤

检验和的计算都是一个模板,只是各种检验和的初始数据不一样
总结一下就是:求和、回卷、取反

  1. 把校验和字段设置为0。
  2. 求和:把需要校验的数据看成以16位为单位的数字组成,依次进行二进制求和。
  3. 回卷:求和后超过16位的加到低16位。
  4. 取反:最后结果取反码就是检验和。

(1)可以每两个求和后回卷,再求和,在回卷,直到全部求和,然后取反
(2)也可以全部求和,然后一直回卷直到高 16 位全部为 0,然后取反
一般采用第二种

另外UDP、TCP数据报的长度可以为奇数字节,因为计算时是16位为单位,所以此时计算校验和时需要在最后一个填充字节0(只是计算校验和用,不发送出去)。

二、接收端校验校验和步骤

把需要校验的内容(包括校验和字段)看成以16位为单位的数字,依次进行二进制反码求和,如果结果是0表示正确,否则表示错误。

三、说明

校验和覆盖的内容:

  1. IP首部校验和:IP首部
  2. ICMP校验和:ICMP首部+ICMP数据
  3. 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
};
发布了113 篇原创文章 · 获赞 109 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/weixin_42109012/article/details/103455953