首页 > 代码库 > TCP校验和计算原理与实现
TCP校验和计算原理与实现
1. 概述
TCP首部校验和计算三部分:TCP首部+TCP数据+TCP伪首部。
TCP校验和覆盖TCP首部和TCP数据,而IP首部中的校验和只覆盖IP的首部,不覆盖IP数据报中的任何数据。
伪首部是为了增加TCP校验和的检错能力:如检查TCP报文是否收错了(目的IP地址)、传输层协议是否选对了(传输层协议号)等。伪首部来自IP首部。
RFC 793的TCP校验和定义
The checksum field is the 16 bit one‘s complement of the one‘s complement sum of all 16-bit words in the header and text.
If a segment contains an odd number of header and text octets to be checksummed, the last octet is padded on the right
with zeros to form a 16-bit word for checksum purposes. The pad is not transmitted as part of the segment. While computing
the checksum, the checksum field itself is replaced with zeros.
上述的定义说得很明确:
首先,把伪首部、TCP报头、TCP数据分为16位的字,如果总长度为奇数个字节,则在最后增添一个位都为0的字节。
把TCP报头中的校验和字段置为0。
其次,用反码相加法累加所有的16位字(进位也要累加)。
最后,将上述结果作为TCP的校验和。
验证示例:
校验和 反码求和过程
以4bit 为例
发送端计算:
数据: 1000 0100 校验和 0000
则反码:0111 1011 1111
叠加: 0111+1011+1111 = 0010 0001 高于4bit的, 叠加到低4位 0001 + 0010 = 0011 即为校验和
接收端计算:
数据: 1000 0100 检验和 0011
反码: 0111 1011 1100
叠加: 0111 + 1011 +1100 = 0001 1110 叠加为4bit为1111. 全为1,则正确
2. 校验和反码求和 的实现
发送方:原码相加 ,并将高位叠加到低位,取反 ,得到反码求和结果,放入校验和
接收方:将所有原码 相加,高位叠加, 如全为1,则正确
代码如下:
USHORT checksum (USHORT *buffer,int size)
{
Unsigned long cksum=0;
While (size>1)
{
Cksum +=*buffer++;
size -=sizeof(USHORT);
}
If (size)
{
Cksum +=*(UCHAR *) buffer;
}
//将32位转换为16位
While (cksum>>16)
Cksum = (cksum>>16) + (cksum & 0xffff);
return (USHORT) (~cksum);
}
buffer是指向需要校验数据缓冲区的指针,size是需要检验数据的总长度(字节为单位)。
4-8行代码是对数据按16bit累加求和,由于最高位的进位需要加在最低位上,所以cksum必须是32位的unsigned long型,高16bit用于保存累加过程中的进位;
9-11行是对size为奇数情况的处理。
14~15行代码的作用是将cksum高16bit的值加到低16bit上,即把累加中最高位的进位加到最低位上。这里使用了 while循环,判断cksum高16bit是否非零,因为第16行代码执行的时候,还是可能向cksum的高16bit进位。
有些地方是通过下面两条代码实现的:
Cksum = (cksum >> 16) + (cksum & 0xffff);
Cksum = (cksum >> 16);
这里只进行了两次相加,即可保证相加后cksum的高16位为0,两种方式的效果是一样,事实上,上面的循环也最多执行两次!
16行代码即对16bit数据累加的结果取反,得到二进制反码求和的结果,然后函数返回该值。
3. Linux kernel中实现:
csum为32bit ,存储原码相加的结果, 将原码高16bit叠加到低16bit,然后求反,即可得到 反码求和的结果
static inline __sum16 csum_fold(__wsum csum)
{
u32 sum = (__force u32)csum;
sum = (sum & 0xffff) + (sum >> 16); //将高16 叠加到低16
sum = (sum & 0xffff) + (sum >> 16); //将产生的进位 叠加到 低16
return (__force __sum16)~sum; //求反, 得到二进制反码求和的结果
}
其中上述csum的计算如下:
skb_add_data -> csum_partial 计算 添加数据段的校验和 ,将其放置在 skb->csum 中,完成tcp数据部分计算。
tcp_transmit_skb(传输层统一出口函数) 为 skb添加 tcp首部,并在其中调用 tcp_v4_send_check 来完成 tcp首部的最终校验和计算( tcp首部+tcp数据+tcp伪首部 )。
源码分析:
tcp_transmit_skb
{
icsk->icsk_af_ops->send_check(sk, skb->len, skb); //完成 tcp首部校验和的计算
...
...
err = icsk->icsk_af_ops->queue_xmit(skb, 0); //将其发往 IP层 ip_queue_xmit
}
其中 tcp_transmit_skb-->icsk->icsk_af_ops->send_check(tcp_v4_send_check)
tcp_v4_send_check
{
.....
th->check = tcp_v4_check(len, inet->inet_saddr, inet->inet_daddr,
csum_partial(th, th->doff << 2, skb->csum)); //将 th首部 校验和 和 tcp数据段校验和相加
....
}
//NETIF_F_ALL_CSUM
static inline __sum16 tcp_v4_check(int len, __be32 saddr,
__be32 daddr, __wsum base)
{
return csum_tcpudp_magic(saddr,daddr,len,IPPROTO_TCP,base);// 将tcp首部+tcp数据+tcp伪首部 三部分校验和相加,并将 32bit的 csum (skb->csum),折叠计算为16bit的 check,存放在 th->check (tcp首部校验和字段)中
即产生最终校验和
}
//对伪头 计算校验和
static inline __sum16 csum_tcpudp_magic(__be32 saddr, __be32 daddr, unsigned short len,
unsigned short proto, __wsum sum)
{
return csum_fold(csum_tcpudp_nofold(saddr,daddr,len,proto,sum));
}
//将32位值的高16位折叠到低16位中,然后取反输出值,即为发送端应填入的校验和
static inline __sum16 csum_fold(__wsum csum)
{
u32 sum = (__force u32)csum;
sum = (sum & 0xffff) + (sum >> 16); //将高16 叠加到低16
sum = (sum & 0xffff) + (sum >> 16); //将产生的进位 叠加到 低16
return (__force __sum16)~sum; //求反, 得到二进制反码求和的结果
}
TCP校验和计算原理与实现
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。