linux内核网卡checksum卸载功能

概述

        checksum卸载分为发送和接收两个方向。

        发送方向是指host主机发送报文,如果网卡不支持checksum卸载,那么需要内核网络协议栈计算报文的checksum,如果报文的数据部分长度比较长,那么这个运算会有一定的CPU开销。如果网卡支持checksum卸载,那么可以将这一部分计算卸载到网卡,由网卡硬件实现。

        接收方向是指网卡收到报文上送主机处理。如果网卡不支持checksum卸载,那么内核协议栈收到报文后,需要计算报文的checksum,校验报文是否数据失真。如果网卡支持checksum卸载,那么可以将这一部分计算卸载到网卡,由网卡硬件计算报文的checksum,并将计算结果告知内核协议栈。

        一个报文可能有多个部分需要计算checksum,例如ip head有一个checksum计算、UDP/TCP等,也有一个checksum计算。另外如果当前是隧道报文的话,则隧道的内外层报文都可能需要计算checksum。如何考虑这些不同的情况,后面介绍。

内核实现

网卡checksum能力的获取

        首先网卡的内核驱动需要告知内核,网卡的checksum能力。这是在驱动probe,创建net_device时,设置net_device的features字段实现。features字段不仅仅有checksum标记,还有很多其他标记。

    netdev->features = NETIF_F_SG |
               NETIF_F_TSO |
               NETIF_F_TSO6 |
               NETIF_F_RXHASH |
               NETIF_F_RXCSUM |
               NETIF_F_HW_CSUM;

NETIF_F_HW_CSUM:发送方向的checksum卸载能力标记。

NETIF_F_RXCSUM:接收方向的checksum卸载能力标记。

linux内核之前还有NETIF_F_IP_CSUM和NETIF_F_IPV6_CSUM两个标记,目前已经废弃。

发送方向checksum卸载

        对于发送方向,当内核网络协议栈要发送一个数据报文时,会查询数据报文的网卡出接口的features标记,如果没有NETIF_F_HW_CSUM标记,则协议栈自己会计算报文的checksum。如果有该标记,则表示可以省略一些checksum计算,将其交给网卡硬件做,但有些情况,不能交给硬件做,例如一个报文分为多个sk_buff发送。

        如果一个报文由一个sk_buff可以直接发送,那么此时可以进行checksum卸载,但是需要将checksum卸载信息告知网卡硬件。该接口信息由3个字段组成,都存在sk_buff中:

__u8           ip_summed:2;//设置为CHECKSUM_PARTIAL,告诉网卡驱动该报文需要checksum卸载
union {
    __wsum      csum;
    struct {
        __u16   csum_start; //硬件计算checksum的起始位置,硬件从报文的该偏移位置开始,
                              直到报文结尾,计算checksum
        __u16   csum_offset; //硬件计算完checksum后,写入报文的位置。
                               该位置是相对于csum_start的偏移位置。
    };
};
__u8           csum_not_inet:1; //采用CRC32来计算checksum,SCTP报文支持

        从上面的字段可以看出,checksum卸载是一个通用的计算方式,并没有关心报文的类型,且只计算了一个位置。

        对于ip head中的checksum计算,是由协议栈软件自己计算的,将L4的checksum卸载给硬件计算。

        对于TCP/UDP的伪头的计算,由于选取的字段在报文中并不连续,因此也是由协议栈软件算好,并放在了L4 head的checksum位置,硬件在计算checksum时,会包含这一字段一起计算,因此也就相当于把伪头也算进去了。

        对于linux内核的隧道报文,UDP隧道(VXLAN等)或者GRE隧道,如果存在内层checksum的offload,那么其外层L4 head的checksum计算是由软件计算的。此时软件只计算有限长度的报文checksum,因为内层checksum计算的结果最终为0,因此利用这一特性,外层checksum的计算,只计算隧道head + 内层head的一些字段即可(需要对内层伪头做一些修正),不必计算payload(这一特性叫做Local Checksum Offload,linux内核文档有解释)。

        如果隧道报文的内层不需要做payload的checksum,此时隧道卸载外层报文的checksum。

        SCTP报文的checksum比较特殊,是4字节,其计算方法和UCP/TCP不一样,因此硬件的实现也不一样。因此需要区分这种情况。内核对于支持SCTP ckecksum的卸载特别增加了一个新的特性:NETIF_F_SCTP_CRC。如果硬件不支持这种特性,由内核协议栈软件计算checksum。如果硬件支持,那么需要告知驱动采用32bit的checksum算法,这是通过设置sk_buff中的csum_not_inet=1来实现。驱动也要通过描述符告知硬件是否采用CRC32算法。

        对于vxlan或者gre隧道内层是SCTP报文的情况,因为内外层的checksum算法不一样,是否可以使用Local Checksum Offload特性,外层的checksum只计算部分头,这部分的代码待分析。

        对于内核发送的icmp报文,从代码来看,目前都是内核协议栈自己计算的checksum,并没有要求硬件卸载。但是硬件其实是支持icmp报文的checksum offload,icmp的checksum offload跟tcp和udp报文是一样,硬件并不关心报文是tcp、udp还是icmp。

小结:

  • 报文存储在单个sk_buff的情况下,才卸载checksum。 一个报文分多个sk_buff存储时,不卸载checksum。

  • Ip head的checksum,由协议栈软件自己计算。L4 payload的checksum才会卸载

  • 隧道报文内层存在L4 payload的checksum计算时,卸载内存的checksum计算,外层checksum计算由协议栈软件自己完成。隧道报文内层不存在L4 payload的checksum计算时,卸载外层checksum。

  • 网卡驱动不关心发送的是什么报文,只关心sk_buff的3个字段。ip_summed = CHECKSUM_PARTIAL时,表示该报文需要卸载checksum,此时构造报文描述符时,需要打上checksum标记,告诉硬件该报文需要做checksum,同时在描述符中写入csum_start和csum_offset。

  • SCTP报文的checksum的卸载需要网卡支持额外的特性NETIF_F_SCTP_CRC。其checksum算法采用CRC32来实现,因此需要在描述符中告知硬件采用CRC32算法。

  • 对于icmp报文,内核目前自己计算了checksum,并没有使用checksum offload。硬件其实是支持的,icmp的checksum offload跟tcp和udp报文是一样。

  • 网卡硬件不支持SG场景下的checksum?

接收方向checksum卸载

        接收方向,硬件可以计算checksum,并把计算结果通过描述符带给主机,硬件支持这个功能需要设置NETIF_F_RXCSUM特性。

        实际上,内核协议栈并不关系网卡是否有NETIF_F_RXCSUM特性。内核协议栈只关心sk_buff中的相关字段。这些字段需要网卡驱动收到报文后设置。

__u8           ip_summed:2;//驱动设置为CHECKSUM_COMPLETE,告诉内核硬件计算了checksum
union {
    __wsum      csum;   //硬件计算的checksum值,不包括伪头
    struct {
        __u16   csum_start;
        __u16   csum_offset;
    };
};

        注意:硬件从udp head到报文结尾,计算的checksum值,不包括伪头。硬件对于udp_lite协议报文,也是同样的计算方法,从udp head到报文结尾,计算的checksum值,而不是按照udp_lite协议那样,只计算部分payload的checksum。对于udp_lite报文,如果其计算checksum的长度不是完整的payload长度,则协议栈软件会自己计算checksum,如果其计算checksum的长度是完整的payload长度,那么利用硬件计算的checksum值。

UDP/TCP报文checksum协议栈处理流程:

  • 硬件计算了从udp/tcp head开始到报文结束部分的checksum,不包括伪头,存放在skb->csum字段,并设置skb->ip_summed = CHECKSUM_COMPLETE。 对于udp_lite报文,也是同样的处理流程。

  • 内核协议栈的udp模块收到报文后,对于udp_lite报文,检查其设置的checksum长度是部分payload的长度,则会自己计算checsum,否则跟普通udp的处理方式一样。

  • 对于普通udp/tcp报文,如果skb->ip_summed == CHECKSUM_COMPLETE,那么自己计算一个伪头的checksum,并将硬件计算的结果skb->csum和伪头的checksum再组合一起计算一下,如果结果为0,则设置skb->csum_valid = 1,表面checksum结果已经校验过,且正确。如果结果不为0,则软件会重新再计算一下完整的checksum,如果软件再次计算的结果正确,还是会认为报文正常。

ICMP报文checksum协议栈处理流程:

  • 硬件计算了从icmp head开始到报文结束部分的checksum,icmp报文没有伪头概念

  • 如果skb->ip_summed == CHECKSUM_COMPLETE,计算checksum值,跟tcp/udp复用一套通用的函数,只是这里传递的伪头checksum结果为0,相当于没有伪头。

        对于SCTP报文,目前内核协议栈是自己计算的checksum,不需要硬件计算,即使驱动打上了skb->ip_summed = CHECKSUM_COMPLETE标记,也不影响内核协议栈自己计算SCTP的checksum。

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值