Re: [PATCH net 3/3] selftests: net: tun: test outer protocol of UDP tunnel GSO packets
- From
- Willem de Bruijn <willemdebruijn.kernel@gmail.com>
- Date
- 2026-11-04 02:19:51
- Also in
- virtualization
- Reply to
- Eric Dumazet, 2026-11-03
Eric Dumazet wrote:
quoted hunk ↗ jump to hunk
Add a tun_vnet_udptnl_proto fixture writing single 4in4 Geneve GSO frames to a tap device with UDP tunnel offloads enabled: - outer_proto_ok: an outer IPv4 header, with or without an 802.1Q tag, is accepted. - outer_proto_bad: an outer ethertype that does not match the declared tunnel type (IPv6 on an IPv4 tunnel, or NSH, with or without a VLAN tag) is rejected with EINVAL. Without the previous virtio_net patch, outer_proto_bad fails because the kernel accepts the frames. Signed-off-by: Eric Dumazet <edumazet@kernel.org> --- tools/testing/selftests/net/tun.c | 130 ++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+)diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c index 34de2580e32e..47a24b9c477b 100644 --- a/tools/testing/selftests/net/tun.c +++ b/tools/testing/selftests/net/tun.c@@ -683,6 +683,136 @@ TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum_64k) ASSERT_EQ(ret, len); } +#ifndef ETH_P_NSH +#define ETH_P_NSH 0x894F +#endif + +FIXTURE(tun_vnet_udptnl_proto) +{ + char ifname[IFNAMSIZ]; + uint8_t *pkt; + int fd; +}; + +FIXTURE_SETUP(tun_vnet_udptnl_proto) +{ + int flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; + int features = TUN_F_CSUM | TUN_F_UDP_TUNNEL_GSO | + TUN_F_UDP_TUNNEL_GSO_CSUM | TUN_F_USO4 | TUN_F_USO6; + + self->fd = -1; + if (unshare(CLONE_NEWNET)) + SKIP(return, "Cannot create network namespace: %s", + strerror(errno)); + + self->pkt = calloc(1, MAX_VNET_TUNNEL_PACKET_SZ); + ASSERT_NE(self->pkt, NULL); + + memset(self->ifname, 0, sizeof(self->ifname)); + self->fd = tun_open(self->ifname, flags, TUN_VNET_TNL_SIZE, features, + param_hwaddr_outer_src); + ASSERT_GE(self->fd, 0); +} + +FIXTURE_TEARDOWN(tun_vnet_udptnl_proto) +{ + if (self->fd >= 0) + close(self->fd); + free(self->pkt); +} + +/* Build a 4in4 Geneve GSO frame whose outer ethertype is @l3proto, + * optionally behind an 802.1Q tag. + */ +static int build_udptnl_frame(uint8_t *buf, bool vlan, uint16_t l3proto, + int payload_len) +{ + struct virtio_net_hdr_v1_hash_tunnel *vh = (void *)buf; + int vlan_len = vlan ? 4 : 0; + int hlen = ETH_HLEN + vlan_len + UDP_TUNNEL_GENEVE_4IN4_HDRLEN; + uint8_t *outer_udph, *cur = buf; + int pktlen; +
zero buffer. It is zeroed by calloc, but reused for multiple packets.
+ cur += build_virtio_net_hdr_v1_hash_tunnel(cur, true, hlen, 1400,
+ AF_INET, AF_INET);
+ vh->outer_th_offset += vlan_len;
+ vh->inner_nh_offset += vlan_len;
+ vh->hash_hdr.hdr.csum_start += vlan_len;
+
+ if (vlan) {
+ uint16_t vlan_tag[2] = { htons(100), htons(l3proto) };
+
+ cur += build_eth(cur, ETH_P_8021Q, param_hwaddr_outer_dst,
+ param_hwaddr_outer_src);
+ memcpy(cur, vlan_tag, sizeof(vlan_tag));
+ cur += sizeof(vlan_tag);
+ } else {
+ cur += build_eth(cur, l3proto, param_hwaddr_outer_dst,
+ param_hwaddr_outer_src);
+ }
+
+ pktlen = UDP_TUNNEL_GENEVE_4IN4_HDRLEN - sizeof(struct iphdr) +
+ payload_len;
+ cur += build_ipv4_header(cur, IPPROTO_UDP, pktlen,
+ ¶m_ipaddr4_outer_dst,
+ ¶m_ipaddr4_outer_src);
+
+ outer_udph = cur;
+ cur += build_udp_header(cur, UDP_SRC_PORT, VN_PORT,
+ pktlen - sizeof(struct udphdr));
+ cur += build_geneve_header(cur, VN_ID);
+ cur += build_eth(cur, ETH_P_IP, param_hwaddr_inner_dst,
+ param_hwaddr_inner_src);
+ cur += build_ipv4_header(cur, IPPROTO_UDP,
+ sizeof(struct udphdr) + payload_len,
+ ¶m_ipaddr4_inner_dst,
+ ¶m_ipaddr4_inner_src);
+ cur += build_udp_packet(cur, UDP_DST_PORT, UDP_SRC_PORT, payload_len,
+ AF_INET, false);
+ build_udp_packet_csum(outer_udph, AF_INET, false);
+
+ return cur - buf;
+}
+
+TEST_F(tun_vnet_udptnl_proto, outer_proto_ok)
+{
+ uint8_t *pkt = self->pkt;
+ int len, ret;
+
+ len = build_udptnl_frame(pkt, false, ETH_P_IP, 2800);
+ ret = write(self->fd, pkt, len);
+ ASSERT_EQ(ret, len);
+
+ /* The outer IPv4 header can follow a VLAN tag. */
+ len = build_udptnl_frame(pkt, true, ETH_P_IP, 2800);
+ ret = write(self->fd, pkt, len);
+ ASSERT_EQ(ret, len);
+}
+
+TEST_F(tun_vnet_udptnl_proto, outer_proto_bad)
+{
+ uint8_t *pkt = self->pkt;
+ int len, ret;
+
+ /* Outer ethertype does not match the declared IPv4 tunnel. */
+ len = build_udptnl_frame(pkt, false, ETH_P_IPV6, 2800);
+ ret = write(self->fd, pkt, len);
+ ASSERT_EQ(ret, -1);
+ ASSERT_EQ(errno, EINVAL);
+
+ /* Outer header is not IP at all. */
+ len = build_udptnl_frame(pkt, false, ETH_P_NSH, 2800);
+ ret = write(self->fd, pkt, len);
+ ASSERT_EQ(ret, -1);
+ ASSERT_EQ(errno, EINVAL);
+
+ /* Same, behind a VLAN tag. */
+ len = build_udptnl_frame(pkt, true, ETH_P_NSH, 2800);
+ ret = write(self->fd, pkt, len);
+ ASSERT_EQ(ret, -1);
+ ASSERT_EQ(errno, EINVAL);
+}
+
FIXTURE(tun_vnet_udptnl)
{
char ifname[IFNAMSIZ];
--
2.53.0