常用的简单校验算法:校验和,异或校验,crc校验,LRC校验,补码求和,checksum
相关思路和源码来自网络,自己只是整理, 做笔记用。
并未完整完善正确归纳,只是个人理解初步做笔记记录。
在实现业务需求过程中,通常要用到相关一些校验算法,简单整理常用校验算法并做笔记:
常用校验算法简单说明:
校验和:按每个字节,计算累加和,
异或校验:定义初值,按每个字节异或,求结果。
CRC校验:已有很多的标准及计算方式,可以返回8字节,16字节,32字节的结果。
受益匪浅的文章:https://blog.csdn.net/u013073067/article/details/86621770 ?
设置crc值和多项式码;依次遍历每个字节,与crc值进行异或;crc值取出最低位的值,并右移一位;如果最低位值位1,则于多项式码进行异或;循环直到8位结束。
crc查表思想:观察,内部有个循环,每次对一个字节(8位)的循环中,对crc码的数字是不变的,这里与多项式码相关,可以直接用数组代替这里的求值。(crc表与crc返回8/16/32位有关,与多项式码有关)
LRC校验:是不可靠的,先求和,再对结果取反+1
checksum:对checksum值归0,每16bit求和,不够16bit的高位补0,如果checksum溢出,则高16bit和低16bit相加进行处理(依次循环判断)。
(上面截图来自《计算机网络-自顶向下方法》陈鸣 译,机械工业出版社)
(上面截图来自《计算机网络-自顶向下方法》陈鸣 译,机械工业出版社)
(上面截图来自《计算机网络-自顶向下方法》陈鸣 译,机械工业出版社)
在数据链路层要解决数据传输的三个问题:
这里,重点讨论一下差错检测里面最常用的一种检测算法,循环冗余算法(CRC)以及通过这个算法生成的帧检验序列(FCS)
FCS是802.3帧和Ethernet帧的最后一个字段(4字节).
帧校验序列(FCS)是指特别的检测码字符被添加到在一个通信协议中的帧中进行检错和纠错。发送主机在整个帧中有一个检测码随着发送。接收主机在整个帧中的检测码使用相同的运算法则,并将它与接收到的 FCS 相比较。这样,它能够探测是否任何数据在运输中丢失或被改变。它可能当时丢失这个数据,和请求错误帧的重传。一个循环冗余码校验常被用来估算 FCS。
FCS 字段 — 包含帧的 32 位循环冗余校验 (CRC), 数据链路层帧方式接入协议(LAPF)中的字段,是一个16比特的序列。它具有很强的检错能力,它能检测出在任何位置上的 3 个以内的错误、所有的奇数个错误、16个比特之内的连续错误以及大部分的大量突发错误。
一个符合长度但FCS错误的信息包可能有几种可能的问题。问题可能是延迟碰撞,坏的网卡或驱动器,电缆,集线器或是噪声等。一般而言,主要是物理链路层的错误引起的。
比如:1、阻容匹配不合适,电阻值偏大或偏小,电容型号的匹配都会造成FCS错误的产生。还有电阻电容的错焊或者漏焊以及虚焊都容易造成这种问题。2、PCB线路的设计问题,尤其是千兆以太网的PCB设计,千兆信号的对信号质量要求很高,如果线路排列不合理,会造成线间串扰,影响信号质量。3、FCS错误主要发生在PHY与RJ45接口之间的链路上,所以出现FCS错一定需要排查PHY与RJ45之间的错焊、漏焊、虚焊等问题。4、在一些设备生产厂遇到FCS错误出现的问题,也可能是由于使用的物理连接介质质量太差造成的(千兆以太网对网线的要求至少是超5类线缆),还包括RJ45的水晶头的制造(制作)工艺。5、FCS错误的出现,我们可以通过使用以太网测试仪来检测到,通过强压力的冲击,容易暴露一些错焊,虚焊的问题。
如果有1%以上的帧是FCS错误就必须作为严重问题来对待。因为它严重影响网络的吞吐量。
以上转载自帧检验序列(FCS),特别感谢!
(截图来自《TCP/IP详解 卷1:协议》吴英 张玉 许昱玮 译,机械工业出版社)
(上面截图来自《TCP/IP详解 卷1:协议》吴英 张玉 许昱玮 译,机械工业出版社)
(上面截图来自《TCP/IP详解 卷1:协议》吴英 张玉 许昱玮 译,机械工业出版社)
IPv4首部检验和字段是根据 I P首部计算的检验和码。它不对首部后面的数据进行计算。 ICMP、IGMP、UDP和TCP在它们各自的首部中均含有同时覆盖首部和数据检验和码。数据报每经过一个路由器,首部的字段都可能发生变化(如TTL),所以需要重新校验。而数据部分不发生变化,所以不用重新生成校验值。
IPv6头部格式如上图所示;
UDP检验和是一个端到端的检验和。它由发送端计算,然后由接收端验证。其目的是为了发现UDP首部和数据在发送端到接收端之间发生的任何改动。
(上面截图来自《计算机网络-自顶向下方法》陈鸣 译,机械工业出版社)
linux 内核中有专门代码对UDP的校验和进行处理和计算,见博主redwingz的这篇文章UDP的checksum计算与硬件Offload;
通过linux内核中的这些判断逻辑,可以看出,可以让内核不执行UDP报文的校验和操作,而交个硬件去进行计算。
udp中的checksum,在IPv4中是可选的,但是在IPv6中是强制的(本人认为是IPv6中本来就没有checksum校验域段,于是报文正确性就落在了udp报文上了),如果不使用,则应填充0;
TCP校验和字段长度,16bit,校验范围包括段首部、数据以及伪首部。
ICMPv6和ICMPv4的校验和的计算方法是不一样的,v6校验范围更广,包含了伪首部,先解释下什么是伪首部。
伪首部并非TCP&UDP数据报中实际的有效成分。伪首部是一个虚拟的数据结构,其中的信息是从数据报所在IP分组头的分组头中提取的,既不向下传送也不向上递交,而仅仅是为计算校验和。这样的校验和,既校验了TCP&UDP用户数据的源端口号和目的端口号以及TCP&UDP用户数据报的数据部分,又检验了IP数据报的源IP地址和目的地址。伪报头保证TCP&UDP数据单元到达正确的目的地址。因此,伪报头中包含IP地址并且作为计算校验和需要考虑的一部分。最终目的端根据伪报头和数据单元计算校验和以验证通信数据在传输过程中没有改变而且到达了正确的目的地址。伪首部更确切的说是校验和包含的—个96位的伪首标,是个理论上的值,只是理论上它位于TCP&UDP首标的前面。这个伪首标包含了源地址、目的地址、协议和TCP&UDP长度等字段,这使得TCP&UDP能够防止出现路由选择错误的数据段。这些信息由网际协议(IP)承载,通过TCP&UDP网络接口,在IP上运行的TCP&UDP调用参数或者结果中传递。
上面是copy的百度百科,回到我们的ICMPv6报文来,简单说来,我们的伪首部包含4个部分:sourse address, destination addrss, payload length, next header。大小为16 + 16 + 2 + 1字节。要计算校验和,还需要的信息是ICMPv6首部的内容,其中checksum值在计算之前需要先置为0,为什么就不用解释里吧,呵呵,咱就是算这东西的呀。
在TCP、UDP和ICMPv6中,in6_chsum()函数提供校验和计算方法,而in6_cksum()函数假定分组是以mbuf结构进行传递的,不适合我们自己做包调试。这里给出我采用的一种方法,参考了http://hi.baidu.com/fleago/blog/item/846e86864489743dc75cc3f4.html,感谢fleago同学。
我们以如下方法构造一个报文,并给出计算函数:
unsigned char packet_buffer[] = {
//icmp header 完整的ICMPv6首部,长度不一定是这么长
0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x75, 0xc5, 0xf1, 0x20, 0x80, 0x97, 0x0e, 0x39,
// pseudo header
// source addr
0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
// dest addr
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xff, 0x97, 0x0e, 0x39,
// payload len
0x00, 0x00, 0x00, 0x18,
// next header
0x00, 0x00, 0x00, 0x3a
};
unsigned short checksum(int len, unsigned char *buffer)
{
unsigned long cksum = 0;
unsigned short p = (unsigned short)buffer;
int size = (len >> 1) + (len & 0x1); // 以两个字节为运算单位, // len除以2四舍五入
while (size > 0) {
cksum += *p;
printf(“%4x, %8x\n”, *p, cksum);
p ++;
size --;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
printf(“before ~cksum: %8x\n”, cksum);
printf(“checksum is (hex, in packet byte seq): %02x, %02x\n”, ~cksum & 0xff, ~cksum >> 8);
return (unsigned short) (~cksum); // 取补码,和v4不同
}
以上代码在我的环境里均可以正确执行。我分别以NS报文和PING报文测试,校验和计算结果正确。
上面来自《ICMPv6报文中校验和的计算方法》,特此感谢!