tcp层的校验值难道还需要ip层的元数据也就是ip头吗?如果一切都是理想的显然不需要,因为这违背了分层隔离的原则,下层一定不能依赖上层,但是上层可以访问下层,还好tcp使用ip信息正是这一点。按照封包原则,封装到TCP层的时候,ip信息还没有封装上去,但是校验值却需要马上进行计算,所以必须手工构造一个伪头部来表示ip层的信息,怎么构造呢?在数据到tcp层的时候其实用户肯定知道数据发往何处,源地址和目的地址都有,只不过是还没有封装到数据上罢了,简单的例子就是在socket程序中,connect或者accept或者recvfrom以及sendto都会保留有地址信息,另外伪头中还将保留有传输层协议信息,所有这一切都是为了使得通信更加安全和缜密,试想如果一个中间人截获了一个icmp包,然后改为了udp包或发生什么,该udp不是随意的而是精心构造的,但是加入了伪头部如此之行为得逞就困难多了,因为伪头部中有协议字段,除此之外,任何错误的投递,错误的数据长度以及错误的协议都会被检测到。看一下伪头吧:
struct psd_head
{
__u32 saddr; // 源网络层地址
__u32 daddr; // 目的网络层地址
__u8 mbz; //赋0
__u8 ptcl; // 传输层协议
__u16 tcpudpl; //传输层长度
};
以下是一个简单的校验和校验码的计算函数
void tcpv4_check_addr( __u16 * ppkgdata )
{
char * indata;
__u16 ippktlen, udppktlen,tcppktlen,wd;
__u32 ipheadlen;
__u32 sum,i,pl,el;
struct psd_head psd;
struct iphdr * ipd;
struct tcphdr * tcpd;
struct udphdr * udpd;
__u16 * databegin;
indata = (char *)ppkgdata; //从MAC开始的整个帧
ipheadlen = 14 + (indata[14]&0x0f)*4 ; //MAC和ip头的长度和
databegin = (__u16 *)(indata + ipheadlen); //ip数据
ipd = (struct iphdr *)(indata + 14); //MAC数据
tcpd = (struct tcphdr *)(indata + ipheadlen); //ip数据
ippktlen = htons(ipd->tot_len); //ip头和ip数据的总长度
if(ipd->protocol == 0x6){
tcppktlen = ippktlen +14 - ipheadlen; //tcp头和tcp数据的总长度
tcpd->check = 0;
psd.saddr = ipd->saddr; //构造伪头部
psd.daddr = ipd->daddr;
psd.mbz=0;
psd.ptcl = 0x06; //ip的下一个头
psd.tcpudpl = htons(tcppktlen);
sum = 0;
wd = tcppktlen/2; //每次数据前移16位而不是一个字节
for(i=0;i sum += *databegin; databegin++; } el = tcppktlen - wd*2; if(el != 0) sum += (*databegin&0xff); wd = sizeof(struct psd_head)/2; databegin = (__u16 *) &psd.saddr; for(i=0;i sum += *databegin; databegin++; }//下面这个表达式就是高低16分别相加,sum/65536就是高16位:sum<<16 pl = (sum + sum/65536)&0xffff; sum = 0xffff^pl; tcpd->check = (__u16)sum; //检验和的计算很简单,就是将数据相加并且回卷之后取反 } return; } 以上的算法再清晰不过了,甚至将tcp,ip头部的偏移怎么计算都表达了出来,但是这个函数并不适用于实际情况,因为在高负载网络环境下,特别是NAT或者数据过滤网关环境下,校验和的计算是一个很频繁的过程,因此上述函数的c语言本质将很影响效率,取而代之的是用汇编实现,正如linux内核中的那样: static inline __sum16 csum_fold(__wsum sum) { __asm__( "addl %1, %0 ;/n" "adcl $0xffff, %0 ;/n" : "=r" (sum) : "r" ((__force u32)sum << 16), "" ((__force u32)sum & 0xffff0000) ); return (__force __sum16)(~(__force u32)sum >> 16); } static inline __wsum csum_tcpudp_nofold(__be32 saddr, __be32 daddr, unsigned short len, unsigned short proto, __wsum sum) { __asm__( "addl %1, %0 ;/n" "adcl %2, %0 ;/n" "adcl %3, %0 ;/n" "adcl $0, %0 ;/n" : "=r" (sum) : "g" (daddr), "g"(saddr), "g"((len + proto) << 8), ""(sum)); return sum; } 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)); } 别看一个小小的计算校验和,它本质上影响了网络传输的效率,如果用tcpv4_check_addr这个函数计算校验和,效率慢了10倍之多,但是用汇编取而代之的话,效率虽然由于额外吸收而有所下降,但是数量级并没有改变。