VXLAN Tunnel TSO

Overview

overlay网络提供了租户网络东西向连通的能力,但引入了报文封装的代价。常见的网络设备的MTU通常为1500,当虚拟机发送的报文完成overlay封装后的报文长度超过overlay网络的MTU时,就需要将封装后的报文分片后发送。

以VXLAN为例,一个需要分片的VXLAN巨帧将按照MTU被分成若干小于MTU的VXLAN报文,这部分工作传统都是由软件实现的。

通常来说,TSO是一个大多数网卡都支持的卸载功能,可以将TCP报文分段工作卸载到网卡进行。随着技术发展,一些网卡甚至可以支持隧道TSO,将上述隧道TCP巨帧的分段工作offload到网卡实现。

TSO vs UFO

TCP在SYN报文中通过选项协商传输的MSS用于TCP分段。TCP在报文发送时,会将MSS和协议首部信息传递给网卡,网卡再根据这些信息进行TCP分段,即TSO(TCP Segmentation Offload)。如果网卡不支持TSO,以内核协议栈为例,则会进入GSO,即软件分片(GSO同时支持UDP)。

而UDP协议没有自身的分片功能,对于超长报文,依赖IP层的分片功能实现分片,其分片流程就是将payload分为若干份,复制到不同的IP分片,每个IP分片的首部除了长度、分片偏移以外,都是一样的。由于IP协议自身也没有保证可靠性,IP报文出现分片的场景,只要有任意1个分片没有收到,都会造成整个IP报文重组失败,所以一般情况下都需要尽量避免IP分片。

相对于TCP分段而言,UDP的分片实现比较简单,一般是软件实现;而TCP分段不仅每个分段都是单独的IP报文,每个分段自身也需要重新计算校验和,以及设置首部字段等,工作量相对UDP分片而言大很多,所以很多网卡实现了TSO功能。

通常网卡不会实现UFO(UDP Fragment Offload),但并不意味着所有网卡都不会,比如虚拟化场景下,Guest内部进行UDP分片会造成大量的CPU消耗,此时就可以将UDP分段的功能卸载到Host进行,以减少Guest的CPU消耗,如virtio-net的VIRTIO_NET_F_HOST_UFOVIRTIO_NET_F_GUEST_UFO特性。

如果在TCP巨帧的基础上封装了一层UDP隧道后,导致TCP分段需要用软件实现,将会使隧道下的TCP性能大打折扣,所以一些网卡厂商实现了基于隧道的TSO

VXLAN TSO Offload

对于一些支持VXLAN TSO Offload的网卡,可以通过ethtool查看:

1
2
3
# ethtool -k eth9 | grep tnl
tx-udp_tnl-segmentation: on
tx-udp_tnl-csum-segmentation: on

其中tx-udp_tnl-segmentationtx-udp_tnl-csum-segmentation都表示硬件支持基于UDP隧道封装的TCP报文的分段^1

当然,所有卸载功能,都是需要驱动配合的。对DPDK而言,是否支持对于的隧道卸载,需要关注特定PMD的是否支持以下特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define DEV_TX_OFFLOAD_VXLAN_TNL_TSO    0x00000200    /**< Used for tunneling packet. */
#define DEV_TX_OFFLOAD_GRE_TNL_TSO 0x00000400 /**< Used for tunneling packet. */
#define DEV_TX_OFFLOAD_IPIP_TNL_TSO 0x00000800 /**< Used for tunneling packet. */
#define DEV_TX_OFFLOAD_GENEVE_TNL_TSO 0x00001000 /**< Used for tunneling packet. */
......
/**
* Device supports generic UDP tunneled packet TSO.
* Application must set PKT_TX_TUNNEL_UDP and other mbuf fields required
* for tunnel TSO.
*/
#define DEV_TX_OFFLOAD_UDP_TNL_TSO 0x00040000
/**
* Device supports generic IP tunneled packet TSO.
* Application must set PKT_TX_TUNNEL_IP and other mbuf fields required
* for tunnel TSO.
*/
#define DEV_TX_OFFLOAD_IP_TNL_TSO 0x00080000
/** Device supports outer UDP checksum */
#define DEV_TX_OFFLOAD_OUTER_UDP_CKSUM 0x00100000

可以看到DPDK的tunnel offload特性具体到协议类型,对于一些高级的网卡,以mlx5为例,支持任意UDP隧道的TSO卸载,即DEV_TX_OFFLOAD_UDP_TNL_TSO

要使用VXLAN TSO offload,需要调用rte_eth_dev_configureporttx_mode中的offload将对应的卸载属性置位。

其实对所有的offload而言,都是驱动和硬件配合实现的,硬件虽然计算能力很快,但却实现简单,比如对协议的解析,没办法像软件一样做的灵活,所以需要由驱动传递offload所需的metadata。以计算校验和为例,硬件需要知道校验和的起始地址、长度等,再进行校验和计算。

VXLAN TSO offload而言,需要设置如下meatadata

  • mbuf->ol_flags
    • PKT_TX_TUNNEL_VXLAN:表明这是一个VXLAN报文。
    • PKT_TX_TCP_SEG:表明需要对报文做TSO
    • PKT_TX_OUTER_IP_CKSUM:在外层是IPv4时需要置位此flag。因为做了TSO以后外层IPv4的payload长度会变化,需要重新计算外层IPv4的校验和。
  • mbuf->outer_l2_len:隧道外层的L2长度。
  • mbuf->outer_l3_len:隧道外层的L3长度。
  • mbuf->l2_len:隧道外层L4头部到隧道内层L2尾部的长度。
  • mbuf->l3_len:隧道内层L3长度。
  • mbuf->tso_segszTSO分段的大小,为0时不启用TSO

上述metadata中,内外层协议的长度可以为网卡提供校验和计算的依据,而mbuf->tso_segsz指定了每个TCP分段的大小,其值应该满足如下关系:

1
mbuf->tso_segsz <= MTU - (mbuf->outer_l2_len - RTE_ETHER_HDR_LEN) - mbuf->outer_l3_len - mbuf->l2_len - mbuf->l3_len

mbuf->tso_segsz较小可能会导致TCP分段较多,传输效率不高;较大可能会导致最终的隧道报文超过MTU,在链路上无法正常传输。

DPDK还有一个卸载属性DEV_TX_OFFLOAD_OUTER_UDP_CKSUM用于表明网卡可以计算外层的UDP校验和,但是支持的网卡较少。在不支持的该卸载属性时,通常将外层UDP的校验和设置为0,表示不计算校验和(UDP的校验和是可选字段)。

Experiment

test-pmd的csum模式包含了对隧道报文的解析,包括卸载功能,所以用test-pmd配合virtio-user和scapy实现隧道报文的构造和发送(其实隧道相关的功能最好是使用examples/tep_termination ,但它必须对接virtio-net的虚拟机):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
                                   +----------+
+-----+ Test PMD +<-------+
| +----------+ |
| |
v |
+---------+ +----+-----+ +--------+--------+ +-------+
| tcpdump | | pmd_mlx5 | | pmd_virtio_user | | Scapy |
+----+----+ +----+-----+ +--------+--------+ +---+---+
^ | ^ |
| | | v
| | +-----+-----+ +--+--+
| | | vhost net +<------------+ Tap |
| | +-----------+ +-----+
| |
| v
+-------------+ +-----+------+
| Receive Nic | | ConnectX 4 |
+-------------+ +-----+------+
^ |
| +--------+ |
+------+ Switch +<----+
+--------+

上述测试拓扑中,ConnectX-4的为port 0,virtio-user为port 1,需要设置port 1的peer为port 0的mac,这样从port 1接收到的报文会被转发到port 0,同时需要把port 0的peer设置为Receive NIC的mac,这样port 0接收到port 1转发过来的报文后,会将报文转发给Receive NIC。

除此之外,还需要设置port的卸载属性等,以开启VXLAN Tunnel TSO功能:

1
2
3
4
5
6
7
8
9
10
11
set fwd csum
port stop all
set eth-peer 0 78:d4:f1:5a:8d:9d # 设置port 0的peer为Receive NIC
set eth-peer 1 08:c0:eb:6c:7b:c7 # 设置port 1的peer为port 0
port config 0 tx_offload outer_ipv4_cksum on
port config 0 tx_offload vxlan_tnl_tso on
port config 0 tx_offload tcp_tso on
csum parse-tunnel on 0 # 开启csum模式的隧道解析功能
tunnel_tso set 1410 0 # 设置port 0的tunnel_tso为1410以启用tunnel TSO
port start all
set verbose 100

test-pmd设置好了之后,将port 1对应的tap接口的MTU设置为9000,并通过scapy发送一条长度为6000的VXLAN隧道巨帧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python
from scapy.all import *

outer_l2 = Ether(dst='78:d4:f1:5a:8d:9d', src='00:50:56:91:f3:42', type=0x0800)
outer_l3 = IP(src='123.123.123.1', dst='123.123.123.2')
outer_l4 = UDP(dport=4789, sport=54321)
outer_vxlan = VXLAN(vni=1000)
inner_l2 = Ether(dst='11:11:11:11:11:11', src='22:22:22:22:22:22', type=0x0800)
inner_l3 = IP(src='1.1.1.1', dst='1.1.1.2')
inner_l4 = TCP(dport=55555, sport=44444, flags='A', seq=1001, ack=2001)

data = Raw(RandString(size=6000))

pkt = outer_l2 / outer_l3 / outer_l4 / outer_vxlan / inner_l2 / inner_l3 / inner_l4 / data

sendp(pkt, iface='v0')

从test-pmd的发包输出可以看到,port 0通过发包函数发出的是一个长度为6104的巨帧报文:

1
2
3
4
port 0/queue 0: sent 1 packets
restore info: - no tunnel info - no outer header - no miss group
src=08:C0:EB:6C:7B:C7 - dst=78:D4:F1:5A:8D:9D - pool=mb_pool_0 - type=0x0800 - length=6104 - nb_segs=3 - sw ptype: L2_ETHER L3_IPV4 L4_UDP - l2_len=14 - l3_len=20 - l4_len=8 - Send queue=0x0
ol_flags: RTE_MBUF_F_TX_IP_CKSUM RTE_MBUF_F_TX_L4_NO_CKSUM RTE_MBUF_F_TX_TCP_SEG RTE_MBUF_F_TX_IPV4 RTE_MBUF_F_TX_OUTER_IP_CKSUM RTE_MBUF_F_TX_OUTER_IPV4 RTE_MBUF_F_TX_TUNNEL_VXLAN

发送完成后,可以在Receive NIC上抓取到完成了分片的报文:

1
2
3
4
5
6
7
8
9
10
15:52:33.770739 08:c0:eb:6c:7b:c7 > 78:d4:f1:5a:8d:9d, ethertype IPv4 (0x0800), length 1514: (tos 0x0, ttl 64, id 1, offset 0, flags [none], proto UDP (17), length 1500)
123.123.123.1.54321 > 123.123.123.2.4789: UDP, length 1472
15:52:33.770786 08:c0:eb:6c:7b:c7 > 78:d4:f1:5a:8d:9d, ethertype IPv4 (0x0800), length 1514: (tos 0x0, ttl 64, id 2, offset 0, flags [none], proto UDP (17), length 1500)
123.123.123.1.54321 > 123.123.123.2.4789: UDP, length 1472
15:52:33.770787 08:c0:eb:6c:7b:c7 > 78:d4:f1:5a:8d:9d, ethertype IPv4 (0x0800), length 1514: (tos 0x0, ttl 64, id 3, offset 0, flags [none], proto UDP (17), length 1500)
123.123.123.1.54321 > 123.123.123.2.4789: UDP, length 1472
15:52:33.770835 08:c0:eb:6c:7b:c7 > 78:d4:f1:5a:8d:9d, ethertype IPv4 (0x0800), length 1514: (tos 0x0, ttl 64, id 4, offset 0, flags [none], proto UDP (17), length 1500)
123.123.123.1.54321 > 123.123.123.2.4789: UDP, length 1472
15:52:33.770837 08:c0:eb:6c:7b:c7 > 78:d4:f1:5a:8d:9d, ethertype IPv4 (0x0800), length 464: (tos 0x0, ttl 64, id 5, offset 0, flags [none], proto UDP (17), length 450)
123.123.123.1.54321 > 123.123.123.2.4789: UDP, length 422

其中每个隧道报文的负载长度包含了VXLAN头部、内层以太网头部、内层IPv4头部和内层TCP头部,共62字节,每个分片包文的负载长度减掉这些首部长度,再加在一起:1472 * 4 + 422 - 62 * 5正好是6000,即原始负载长度。

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2021-2023 Martzki
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信