作者
QQ群:852283276
微信:arm80x86
微信公众号:青儿创客基地
B站:主页 https://space.bilibili.com/208826118
参考
网卡多队列技术与RSS功能介绍
ETHTOOL设置网卡接收哈希(RSS)
TSO、UFO、GSO、LRO、GRO和RSS介绍(ethtool命令)
Linux 内核协议栈的 TSO、GSO
linux内核网络协议栈学习笔记:关于GRO/GSO/LRO/TSO等patch的分析和测试
GSO: Generic Segmentation Offload
网络数据包分析 网卡Offload
linux内核网络协议栈学习笔记(6)
GSO: Generic Segmentation Offload
GSO/TSO/GRO等对VirtIO虚机的网络性能影响分析(by quqi99)
linux tcp GSO和TSO实现
Segmentation Offloads in the Linux Networking Stack
linux内核网络路径数据包分片功能介绍
【网络协议】TCP分段与IP分片
20201014
老哥我刚刚看了一下,文章是我2019年06月19日建立的,时隔一年,老哥我终于有时间来继续干这个事,很忙,今年疫情其实并没有耽误几天,过年比往常多了一个星期休息时间,后面开始进场干活,然后就是各种项目,干项目那都是以前的东西,都很熟了,这就像女仔一样,时间久了就没啥意思,对吧,年纪轻轻,血迹方刚,需要干点新的,干以前没干过的。
root@zynq:~# ethtool -k eth0
Features for eth0:
rx-checksumming: off [fixed]
tx-checksumming: on
tx-checksum-ipv4: on [fixed]
tx-checksum-ip-generic: off [fixed]
tx-checksum-ipv6: off [fixed]
tx-checksum-fcoe-crc: off [fixed]
tx-checksum-sctp: off [fixed]
scatter-gather: on
tx-scatter-gather: on [fixed]
tx-scatter-gather-fraglist: off [fixed]
tcp-segmentation-offload: off
tx-tcp-segmentation: off [fixed]
tx-tcp-ecn-segmentation: off [fixed]
tx-tcp6-segmentation: off [fixed]
udp-fragmentation-offload: off [fixed]
generic-segmentation-offload: on
generic-receive-offload: on
large-receive-offload: off [fixed]
rx-vlan-offload: off [fixed]
tx-vlan-offload: off [fixed]
ntuple-filters: off [fixed]
receive-hashing: off [fixed]
highdma: off [fixed]
rx-vlan-filter: off [fixed]
vlan-challenged: off [fixed]
tx-lockless: off [fixed]
netns-local: off [fixed]
tx-gso-robust: off [fixed]
tx-fcoe-segmentation: off [fixed]
tx-gre-segmentation: off [fixed]
tx-ipip-segmentation: off [fixed]
tx-sit-segmentation: off [fixed]
tx-udp_tnl-segmentation: off [fixed]
fcoe-mtu: off [fixed]
tx-nocache-copy: off
loopback: off [fixed]
rx-fcs: off [fixed]
rx-all: off [fixed]
tx-vlan-stag-hw-insert: off [fixed]
rx-vlan-stag-hw-parse: off [fixed]
rx-vlan-stag-filter: off [fixed]
l2-fwd-offload: off [fixed]
busy-poll: off [fixed]
首先,老哥我的gso是on的,下面的是我用nc
和windows上的网络调试助手进行通信的过程,
下面的是我用iperf3
进行通信的过程,中间连续两个1514的包,Seq不一样,Ack一样,这应该就是我需要的GSO的包,
GSO
dev_hard_start_xmit
里判断netif_needs_gso
判断网卡是否支持gso,如果不支持则调用dev_gso_segment
里面又调用skb_gso_segment
把报文分片,对于ipv4而言,实际调用了tcp_tso_segment
,最后返回多个sk_buff
组成的链表,头指针存在skb->next
里,如果网卡本身支持的话,直接把大块的skb交给网卡:调用netdev_ops->ndo_start_xmit
发送出去,判断netif_need_gso时,检查网卡的netdev->features
,位于include/linux/netdevice.h
,
#define NETIF_F_SG 1 /* Scatter/gather IO. */
#define NETIF_F_IP_CSUM 2 /* Can checksum TCP/UDP over IPv4. */
#define NETIF_F_NO_CSUM 4 /* Does not require checksum. F.e. loopack. */
#define NETIF_F_HW_CSUM 8 /* Can checksum all the packets. */
#define NETIF_F_FRAGLIST 64 /* Scatter/gather IO. */
#define NETIF_F_GSO 2048 /* Enable software GSO. */
#define NETIF_F_GSO_SHIFT 16
#define NETIF_F_GSO_MASK 0x00ff0000
#define NETIF_F_TSO (SKB_GSO_TCPV4 << NETIF_F_GSO_SHIFT)
#define NETIF_F_UFO (SKB_GSO_UDP << NETIF_F_GSO_SHIFT)
对于要支持TSO的网卡而言,需要有NETIF_F_SG | NETIF_F_TSO | NETIF_F_IP_CSUM
,相应如果要支持UFO,应该就需要NETIF_F_SG | NETIF_F_UFO | NETIF_F_IP_CSUM
,命令ethtool -k eth0
可查看网卡驱动的gso/tso特性:
$ ethtool -k enp1s0f0
Features for enp1s0f0:
rx-checksumming: on
tx-checksumming: on
tx-checksum-ipv4: off [fixed]
tx-checksum-ip-generic: on
tx-checksum-ipv6: off [fixed]
tx-checksum-fcoe-crc: on [fixed]
tx-checksum-sctp: on
scatter-gather: on
tx-scatter-gather: on
tx-scatter-gather-fraglist: off [fixed]
tcp-segmentation-offload: on
tx-tcp-segmentation: on
tx-tcp-ecn-segmentation: off [fixed]
tx-tcp6-segmentation: on
tx-tcp-mangleid-segmentation: off
udp-fragmentation-offload: off [fixed]
generic-segmentation-offload: on
generic-receive-offload: on
large-receive-offload: off
rx-vlan-offload: on
tx-vlan-offload: on
ntuple-filters: off
receive-hashing: on
highdma: on [fixed]
rx-vlan-filter: on
vlan-challenged: off [fixed]
tx-lockless: off [fixed]
netns-local: off [fixed]
tx-gso-robust: off [fixed]
tx-fcoe-segmentation: on [fixed]
tx-gre-segmentation: on
tx-ipip-segmentation: on
tx-sit-segmentation: on
tx-udp_tnl-segmentation: on
fcoe-mtu: off [fixed]
tx-nocache-copy: off
loopback: off [fixed]
rx-fcs: off [fixed]
rx-all: off
tx-vlan-stag-hw-insert: off [fixed]
rx-vlan-stag-hw-parse: off [fixed]
rx-vlan-stag-filter: off [fixed]
busy-poll: off [fixed]
tx-gre-csum-segmentation: on
tx-udp_tnl-csum-segmentation: on
tx-gso-partial: on
tx-sctp-segmentation: off [fixed]
rx-gro-hw: off [fixed]
l2-fwd-offload: off
hw-tc-offload: off
rx-udp_tunnel-port-offload: on
命令ethtool -K eth0 tso|gso off|on
可打开或关闭网卡驱动的gso/tso特性,采用-k
得到的缩写generic-receive-offload
是gro
,
$ sudo ethtool -K enp1s0f0 gro off
- 物理网卡不支持GSO时,使用TSO时,TCP分段在驱动处调用硬件做,不使用TSO时,TCP分段在TCP协议处软件做。
- 物理网卡不支持UFO时,使用GSO时,在发送给驱动前一刻做,不使用GSO在IP层软件做。
- 物理网卡不支持TSO时,使用GSO时,在发送给驱动前一刻做,不使用GSO在TCP层软件做。
GSO在分段时会调用TCP或UDP的回调函数(udp4_ufo_fragment)为每个分段都加上IP头,由于分段是通过mss设置的(mss由发送端设置),所以长度仍然可能超过mtu值,所以在IP层还得再分片(代码位于dev_hard_start_xmit)。
TSO
在发送处理方面,提供了tcp-segmentation-offload(TSO),udp-fragmentation-offload(UFO,generic-segmentation-offload(GSO)这3个特性。UFO是专门针对UDP协议的,使用了这个特性,用户层就可以发送大的数据包(最大长度是64K),而不需要由IP协议来分片。 UFO与TSO,GSO没有任何的联系,但是却需要tx-checksumming,scatter-gather这两个特性的支持。遗憾的是,现在还没有看到有网卡支持UFO。所以,默认情况下,该功能的状态是off。当前UFO被应用在虚拟设备(比如虚拟的bridge设备,bond设备)上。TSO是专门针对TCP协议,DCCP[1]协议的,与UFO一样,使用了这个特性,用户层就可以发送大的数据包。TSO的启用不需要GSO的支持,但是却需要tx-checksumming,scatter-gather这两个特性的支持。当前的网卡更多的是针对TCP协议进行功能强化,当前的网卡只支持TSO,而不支持UFO。这3个特性中只有TSO是纯硬件的功能。这个分片和IP分片相比有如下的优势:
- 硬件分片,速度更快;
- 每个分片后的包是一个单独的TCP包,即每个分片包都包含TCP头部。且IP头部不包含任何的与分片相关的值。避免了分片包的丢失,而导致所有的分片包被重传。
- 对于接收端而言,则省去了重组分片的工作。减轻了接收端的工作负荷。
- 减少了sk_buff(内核使用这个来管理数据包)的使用,并降低了CPU的使用率。从tcpdump捕获的数据包中可以看出TSO启用后的效果。一个数据包可以包含11824个字节,远远超过MTU。
以下是TSO和GSO的组合关系:
- GSO开启, TSO开启: 协议栈推迟分段,并直接传递大数据包到网卡,让网卡自动分段
- GSO开启, TSO关闭: 协议栈推迟分段,在最后发送到网卡前才执行分段
- GSO关闭, TSO开启: 同GSO开启, TSO开启
- GSO关闭, TSO关闭: 不推迟分段,在tcp_sendmsg中直接发送MSS大小的数据包
如果想用FPGA来做这个offloading,参考第2种情况来编写FPGA程序。驱动程序在注册网卡设备的时候默认开启GSO:NETIF_F_GSO
,驱动程序会根据网卡硬件是否支持来设置TSO:NETIF_F_TSO
。
reg [15:0]m_axis_tdata_cnt;
reg [ 3:0]toe_gen_state; /*don't support ip_opt, tcp_opt*/
localparam toe_gen_idle = 0; /*1514 = 8*189+2*/
localparam toe_gen_1 = 1; /*remeber header in ram, clk0, total header 48(6clk)+6(3/4clk) Bytes*/ /*https://blog.csdn.net/gemengxia/article/details/108442927*/
localparam toe_gen_2 = 2; /*remeber header in ram, clk1*/
localparam toe_gen_3 = 3; /*remeber header in ram, clk2, if ip len > (mtu+18) change ip len to mtu(1500), tcp data len=1500-20(ip header)-20(tcp header)=1460*/
localparam toe_gen_4 = 4; /*remeber header in ram, clk3, */
localparam toe_gen_5 = 5; /*remeber header in ram, clk4, if fragment number > 1, change tcp_seq[16:31]=start seq+tcp data len*/
localparam toe_gen_6 = 6; /*remeber header in ram, clk5, if fragment number > 1, change tcp_seq[0:15]=start seq+tcp data len*/
/*
fragment 1:
clk 1 2 3 4 5 6 7 ... 189 190
head 8B 8B 8B 8B 8B 6B
2B 8B 8B 2B
fragment 2: if len remain=1/2, keep = 6+remain, 7
clk 1 2 3 4 5 6(clk190)
head 8B 8B 8B 8B 8B 6B
1B/2B
fragment 2: if len remain=3 clk num=(3-2)/8+1, keep=(3-2)%8
clk 1 2 3 4 5 6(clk190) 7(clk191) 189(clk373) 190(clk no need)
head 8B 8B 8B 8B 8B 6B 8B 8B 2B(clk189)
2B data last[2], this clk data[4] data last[2], this clk data[4]
frame into ram circle buf, buf gap=1514/9000, at first, remain len=total len, remain data=0
if (remain len > 1514)
read header
change ip len
if (first)
remember tcp_seq in variable
else
change tcp_seq
read data
if (first)
if (cur len == 1514)
remain len -= 1514
cur len = remain keep
tcp_seq += 1514
else
if (first data clk)
stop read data using arrange {last data} remain keep
else
arrange {last data, data}
else
if (tlast)
if remain keep + last clk remain keep > 8
add a data clk
else
gen new last keep
else
*/
checksum offloading
mac地址字节序,
UDP帧格式,
TCP帧格式,
IP头校验,
原始 csum清零 16bit组合 取反 求和 32bit组合 取反 求和
0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3
45 00 00 7a 45 00 00 7a 0045 7a00 ffba 85ff 7+d161=d168 7a000045 85ffffba 4+7BAA+55BA=d168
3c 52 40 00 3c 52 40 00 523c 0040 adc3 ffbf 0040523c ffbfadc3
40 06 68 d1 40 06 00 00 0640 0000 f9bf ffff 00000640 fffff9bf
c0 a8 0a 08 c0 a8 0a 08 a8c0 080a 573f f7f5 080aa8c0 f7f5573f
c0 a8 0a 02 c0 a8 0a 02 a8c0 020a 573f fdf5 020aa8c0 fdf5573f
TCP,UDP校验,其中UDP报文长度可从报文直接提取,而TCP由IP报文长度减去IP头长度得到,
0000 30 09 f9 20 23 64 00 0a 35 00 00 c8 08 00 45 00
0010 00 3c 59 e2 40 00 40 06 4b 7f c0 a8 0a 08 c0 a8
0020 0a 02 8b 20 1f 90 5f 9b fd 3d 00 00 00 00 a0 02
0030 72 10 94 23 00 00 02 04 05 b4 04 02 08 0a 00 08
0040 a4 df 00 00 00 00 01 03 03 07
伪首部
c0 a8 0a 08 a8c0 080a
c0 a8 0a 02 a8c0 020a
00 06 00 28 0600 2800
TCP
8b 20 1f 90 5f 9b fd 3d 00 00 00 00 a0 02 72 10 208b 901f 9b5f 3dfd 0000 0000 02a0 1072
94 23 00 00 02 04 05 b4 04 02 08 0a 00 08 a4 df 2394(0) 0000 0402 b405 0204 0a08 0800 dfa4
00 00 00 00 01 03 03 07 0000 0000 0301 0703
IP分片
使用iperf3测UDP带宽时,8192字节的包超过MSS,导致IP分片,抓包发现带UDP包头的包在最后,不是《TCP/IP详解》中说的在最前面,或者说,我们需要需要通过off字段来重组,目前本人能想到的设计,接收端需要n个buffer,或者一个ram你自己来寻址,占用资源量又增加了,后面分片重组时,还得考虑流水线的出口的dma需要支持多通道Stream,按照off来把数据搬到正确的内存地址。在计算/检验(发送/接收)checksum的时候,每一片都要计算/检验IP头checksum,累计到最后一个片计算/检验UDP的checksum。显然把UDP包头放在最后一个片有利于计算/检验checksum。