一:硬件基础知识;
TSO,GSO,LRO,GRO,RSS的概念:TSO(TCPSegmentation Offload),网卡对tcp数据包分片,减轻cpu负荷的一种技术,也需要硬件支持TCP的校验计算的分散,聚集等功能。GSO(GenericSegmentation Offload):尽可能的推迟数据分片直至发送到网卡驱动之前。检查网卡是否支持分片功能,如果支持,直接发送到网卡,如果不支持,则进行分片后在发往网卡,
这样大数据包就只走一次协议栈,提高效率,
TSO只支持发送数据包,tcp层大的段会被网卡切包,然后传给对端,如果没有gro,则小的段会被一个个送到协议栈,有了gro后,就可以将tso切好的数据组成大包传递给协议栈,在napi的回调poll中读取数据包,
gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
{
skb_gro_reset_offset(skb);
return napi_skb_finish(dev_gro_receive(napi, skb), skb);
}
napi_skb_finish会判断dev_gro_receive的返回值,还是需要将数据包立即feed进协议栈还是保存起来,
2:tcp校验和的问题
tcp校验和是一个端对端的校验和,发送端计算,接收端验证,目的是发现tcp首部和数据在发送端到接收端是否有改动,
TCP校验和覆盖TCP首部和TCP数据,而IP首部中的校验和只覆盖IP的首部,不覆盖IP数据报中的任何数据。TCP和UDP计算校验和时,都要加上一个12字节的伪首部:源IP地址、目的IP地址、保留字节(置0)、传输层协议号(TCP是6)、TCP报文长度(报头+数据)。伪首部是为了增加TCP校验和的检错能力:如检查TCP报文是否收错了(目的IP地址)、传输层协议是否选对了(传输层协议号)等。
首先,把伪首部、TCP报头、TCP数据分为16位的字,如果总长度为奇数个字节,则在最后增添一个位都为0的字节。
其次,用反码相加法累加所有的16位字(进位也要累加)。
最后,对计算结果取反,作为TCP的校验和。
https://blog.csdn.net/zhangskd/article/details/11770647
#define CHECKSUM_NONE 0 //传输层自己计算校验和,硬件校验出错,单位丢弃数据包
#define CHECKSUM_UNNECESSARY 1 //硬件进行了完整的校验和,无需软件检查
//接收过程
#define CHECKSUM_COMPLETE 2//硬件已经校验了L4爆头和plaload部分,并且保存在csum,l4只计算伪报头
//发送过程
#define CHECKSUM_PARTIAL 3 //由硬件和软件配合计算校验和
2.1:用于计算发送校tcp验和
void tcp_v4_send_check(struct sock *sk, struct sk_buff *skb)
{
const struct inet_sock *inet = inet_sk(sk);
__tcp_v4_send_check(skb, inet->inet_saddr, inet->inet_daddr);
}
if (skb->ip_summed == CHECKSUM_PARTIAL) {
//只计算伪首部,tcp报头和tcp数据的累加由硬件完成
th->check = ~tcp_v4_check(skb->len, saddr, daddr, 0);
skb->csum_start = skb_transport_header(skb) - skb->head;
skb->csum_offset = offsetof(struct tcphdr, check); //校验和值在tcp的首部偏移
} else {
//tcp_v4_check累加到伪首部,获取最终的校验和,csum_partial累加tcp报头,
skb->csum是tcp数据部分累加,从用户空间复制时顺便累加
th->check = tcp_v4_check(skb->len, saddr, daddr, csum_partial(th,th->doff << 2,
skb->csum));
}
调用:csum_tcpudp_magic产生最终的校验和,调用
return csum_fold(csum_tcpudp_nofold(saddr, daddr, len, proto, sum));
csum_tcpudp_nofold:按4字节累加到伪首部的sum中,累加sum的低16位、sum的高16位,并且对累加的结果取反。截取sum的高16位,作为校验和。
csum_partial->do_csum()用于计算一段内存的校验和,这里用于累加TCP报头。
反码累加时,按16位、32位、64位来累加的效果是一样的。 使用内存对齐,减少内存操作的次数。
2.2:计算tcp的接收校验和
if (!skb_csum_unnecessary(skb) && tcp_v4_checksum_init(skb))//接收校验的第一步
static __sum16 tcp_v4_checksum_init(struct sk_buff *skb)
{
const struct iphdr *iph = ip_hdr(skb);
if (skb->ip_summed == CHECKSUM_COMPLETE) { //tcp报头tcp数据反码累加由硬件完成
//现在只需要在累加上伪首部,取反获取最终的校验和。
if (!tcp_v4_check(skb->len, iph->saddr, iph->daddr, skb->csum)) {skb->ip_summed = CHECKSUM_UNNECESSARY;
return 0;
}
}
//对伪首部进行反码累加,主要用于软件方法
skb->csum = csum_tcpudp_nofold(iph->saddr, iph->daddr, skb->len, IPPROTO_TCP, 0);
if (skb->len <= 76) { return __skb_checksum_complete(skb); }//对于长度小于76的小包,累加tcp的报文和报头,完成校验。
return 0;
}
if (tcp_checksum_complete_user(sk, skb))
最终调用校验和参数:__sum16 __skb_checksum_complete_head(struct sk_buff *skb, int len)
{
__sum16 sum;
sum = csum_fold(skb_checksum(skb, 0, len, skb->csum));
printk(KERN_EMERG "sum:%d��skb->ip_summed=%d,skb->csum=%d\n",sum,skb->ip_summed,skb->csum);
if (likely(!sum)) {
if (unlikely(skb->ip_summed == CHECKSUM_COMPLETE))
netdev_rx_csum_fault(skb->dev);
skb->ip_summed = CHECKSUM_UNNECESSARY;
}
return sum;
}
3:客户端通知mss协商
客户端在发送SYN请求报文前,设置MSS钳制值为TCP_MSS_DEFAULT。#define TCP_MSS_DEFAULT 536U
当TCP客户端发起连接建立请求时,通过函数tcp_connect实现,其中调用tcp_connect_init函数初始化连接相关参数,包括最大MSS协商值mss_clamp。如果用户层通过setsockopt选项TCP_MAXSEG指定了MSS值,使用用户指定值。函数tcp_sync_mss将当前连接的路径MTU转换为缓存MSS值mss_cache。tcp_mss_clamp函数将通过路由系统取到的MSS值与通过setsockopt设置的值user_mss比较,采用较小的一个作为最终通告值advmss。