[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <c52450d30c60b20253a4d195dc66f4d4c0a02603.1763345426.git.xudu@redhat.com>
Date: Mon, 17 Nov 2025 14:25:01 +0800
From: xu du <xudu@...hat.com>
To: davem@...emloft.net,
edumazet@...gle.com,
kuba@...nel.org,
pabeni@...hat.com
Cc: netdev@...r.kernel.org
Subject: [PATCH net-next 5/8] selftest: tun: Add helpers for GSO over UDP tunnel
In preparation for testing GSO over UDP tunnels, enhance the test
infrastructure to support a more complex data path involving a TUN
device and a VXLAN tunnel.
This patch introduces a dedicated setup/teardown topology that creates
both a VXLAN tunnel interface and a TUN interface. The TUN device acts
as the VTEP (Virtual Tunnel Endpoint), allowing it to send and receive
virtio-net packets. This setup effectively tests the kernel's data path
for encapsulated traffic.
Additionally, a new data structure is defined to manage test parameters.
This structure is designed to be extensible, allowing different test
data and configurations to be easily added in subsequent patches.
Signed-off-by: xu du <xudu@...hat.com>
---
tools/testing/selftests/net/tun.c | 391 ++++++++++++++++++++++++++++++
1 file changed, 391 insertions(+)
diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c
index 2ed439cce423..8f0188ccb9fb 100644
--- a/tools/testing/selftests/net/tun.c
+++ b/tools/testing/selftests/net/tun.c
@@ -15,6 +15,81 @@
#include "../kselftest_harness.h"
#include "tuntap_helpers.h"
+static const char param_dev_vxlan_name[] = "vxlan1";
+static unsigned char param_hwaddr_outer_dst[] = { 0x00, 0xfe, 0x98,
+ 0x14, 0x22, 0x42 };
+static unsigned char param_hwaddr_outer_src[] = { 0x00, 0xfe, 0x98,
+ 0x94, 0xd2, 0x43 };
+static unsigned char param_hwaddr_inner_dst[] = { 0x00, 0xfe, 0x98,
+ 0x94, 0x22, 0xcc };
+static unsigned char param_hwaddr_inner_src[] = { 0x00, 0xfe, 0x98,
+ 0x94, 0xd2, 0xdd };
+
+static struct in_addr param_ipaddr4_outer_dst = {
+ __constant_htonl(0xac100001),
+};
+
+static struct in_addr param_ipaddr4_outer_src = {
+ __constant_htonl(0xac100002),
+};
+
+static struct in_addr param_ipaddr4_inner_dst = {
+ __constant_htonl(0xac100101),
+};
+
+static struct in_addr param_ipaddr4_inner_src = {
+ __constant_htonl(0xac100102),
+};
+
+static struct in6_addr param_ipaddr6_outer_dst = {
+ { { 0x20, 0x02, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } },
+};
+
+static struct in6_addr param_ipaddr6_outer_src = {
+ { { 0x20, 0x02, 0x0d, 0xb8, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } },
+};
+
+static struct in6_addr param_ipaddr6_inner_dst = {
+ { { 0x20, 0x02, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } },
+};
+
+static struct in6_addr param_ipaddr6_inner_src = {
+ { { 0x20, 0x02, 0x0d, 0xb8, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 } },
+};
+
+#define VN_ID 1
+#define VN_PORT 4789
+#define UDP_SRC_PORT 22
+#define UDP_DST_PORT 48878
+#define IPPREFIX_LEN 24
+#define IP6PREFIX_LEN 64
+#define TIMEOUT_SEC 3
+
+#define UDP_TUNNEL_VXLAN_4IN4 0x01
+#define UDP_TUNNEL_VXLAN_6IN4 0x02
+#define UDP_TUNNEL_VXLAN_4IN6 0x04
+#define UDP_TUNNEL_VXLAN_6IN6 0x08
+
+#define UDP_TUNNEL_OUTER_IPV4 (UDP_TUNNEL_VXLAN_4IN4 | UDP_TUNNEL_VXLAN_6IN4)
+#define UDP_TUNNEL_INNER_IPV4 (UDP_TUNNEL_VXLAN_4IN4 | UDP_TUNNEL_VXLAN_4IN6)
+
+#define TUN_VNET_TNL_SIZE sizeof(struct virtio_net_hdr_v1_hash_tunnel)
+
+union vxlan_addr {
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+};
+
+struct vxlan_setup_config {
+ union vxlan_addr local_ip;
+ union vxlan_addr remote_ip;
+ __be32 vni;
+ int remote_ifindex;
+ __be16 dst_port;
+ unsigned char hwaddr[6];
+ uint8_t csum;
+};
+
static int tun_attach(int fd, char *dev)
{
struct ifreq ifr;
@@ -67,6 +142,177 @@ static int tun_delete(char *dev)
return dev_delete(dev);
}
+static size_t sockaddr_len(int family)
+{
+ return (family == AF_INET) ? sizeof(struct sockaddr_in) :
+ sizeof(struct sockaddr_in6);
+}
+
+static int tun_open(char *dev, const int flags, const int hdrlen,
+ const int features, const unsigned char *mac_addr)
+{
+ struct ifreq ifr = { 0 };
+ int fd, sk = -1;
+
+ fd = open("/dev/net/tun", O_RDWR);
+ if (fd < 0) {
+ perror("open");
+ return -1;
+ }
+
+ ifr.ifr_flags = flags;
+ if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0) {
+ perror("ioctl(TUNSETIFF)");
+ goto err;
+ }
+ strcpy(dev, ifr.ifr_name);
+
+ if (hdrlen > 0) {
+ if (ioctl(fd, TUNSETVNETHDRSZ, &hdrlen) < 0) {
+ perror("ioctl(TUNSETVNETHDRSZ)");
+ goto err;
+ }
+ }
+
+ if (features) {
+ if (ioctl(fd, TUNSETOFFLOAD, features) < 0) {
+ perror("ioctl(TUNSETOFFLOAD)");
+ goto err;
+ }
+ }
+
+ sk = socket(PF_INET, SOCK_DGRAM, 0);
+ if (sk < 0) {
+ perror("socket");
+ goto err;
+ }
+
+ if (ioctl(sk, SIOCGIFFLAGS, &ifr) < 0) {
+ perror("ioctl(SIOCGIFFLAGS)");
+ goto err;
+ }
+
+ ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);
+ if (ioctl(sk, SIOCSIFFLAGS, &ifr) < 0) {
+ perror("ioctl(SIOCSIFFLAGS)");
+ goto err;
+ }
+
+ if (mac_addr && flags & IFF_TAP) {
+ ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
+ memcpy(ifr.ifr_hwaddr.sa_data, mac_addr, ETH_ALEN);
+
+ if (ioctl(sk, SIOCSIFHWADDR, &ifr) < 0) {
+ perror("ioctl(SIOCSIFHWADDR)");
+ goto err;
+ }
+ }
+
+out:
+ if (sk >= 0)
+ close(sk);
+ return fd;
+
+err:
+ close(fd);
+ fd = -1;
+ goto out;
+}
+
+static int vxlan_fill_rtattr(struct nlmsghdr *nh, void *data)
+{
+ struct vxlan_setup_config *vxlan = data;
+
+ rtattr_add_any(nh, IFLA_ADDRESS, vxlan->hwaddr, ETH_ALEN);
+ return 0;
+}
+
+static int vxlan_fill_info_data(struct nlmsghdr *nh, void *data)
+{
+ struct vxlan_setup_config *vxlan = data;
+
+ rtattr_add_any(nh, IFLA_VXLAN_LINK, &vxlan->remote_ifindex,
+ sizeof(vxlan->remote_ifindex));
+ rtattr_add_any(nh, IFLA_VXLAN_ID, &vxlan->vni, sizeof(vxlan->vni));
+ rtattr_add_any(nh, IFLA_VXLAN_PORT, &vxlan->dst_port,
+ sizeof(vxlan->dst_port));
+ rtattr_add_any(nh, IFLA_VXLAN_UDP_CSUM, &vxlan->csum,
+ sizeof(vxlan->csum));
+
+ if (vxlan->remote_ip.sin.sin_family == AF_INET) {
+ rtattr_add_any(nh, IFLA_VXLAN_GROUP,
+ &vxlan->remote_ip.sin.sin_addr,
+ sizeof(struct in_addr));
+ rtattr_add_any(nh, IFLA_VXLAN_LOCAL,
+ &vxlan->local_ip.sin.sin_addr,
+ sizeof(struct in_addr));
+ } else {
+ rtattr_add_any(nh, IFLA_VXLAN_GROUP6,
+ &vxlan->remote_ip.sin6.sin6_addr,
+ sizeof(struct in6_addr));
+ rtattr_add_any(nh, IFLA_VXLAN_LOCAL6,
+ &vxlan->local_ip.sin6.sin6_addr,
+ sizeof(struct in6_addr));
+ }
+
+ return 0;
+}
+
+static int set_pmtu_discover(int fd, bool is_ipv4)
+{
+ int level, name, val;
+
+ if (is_ipv4) {
+ level = SOL_IP;
+ name = IP_MTU_DISCOVER;
+ val = IP_PMTUDISC_DO;
+ } else {
+ level = SOL_IPV6;
+ name = IPV6_MTU_DISCOVER;
+ val = IPV6_PMTUDISC_DO;
+ }
+
+ return setsockopt(fd, level, name, &val, sizeof(val));
+}
+
+static int udp_socket_open(struct sockaddr_storage *sockaddr, bool can_frag)
+{
+ struct timeval timeout = { .tv_sec = TIMEOUT_SEC };
+ int family = sockaddr->ss_family;
+ int sockfd, alen;
+
+ sockfd = socket(family, SOCK_DGRAM, 0);
+ if (sockfd < 0) {
+ perror("socket");
+ return -1;
+ }
+
+ alen = sockaddr_len(family);
+ if (bind(sockfd, (struct sockaddr *)sockaddr, alen) < 0) {
+ perror("bind");
+ goto err;
+ }
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout,
+ sizeof(timeout)) < 0) {
+ perror("setsockopt(SO_RCVTIMEO)");
+ goto err;
+ }
+
+ if (!can_frag) {
+ if (set_pmtu_discover(sockfd, family == AF_INET) < 0) {
+ perror("set_pmtu_discover");
+ goto err;
+ }
+ }
+
+ return sockfd;
+
+err:
+ close(sockfd);
+ return -1;
+}
+
FIXTURE(tun)
{
char ifname[IFNAMSIZ];
@@ -129,4 +375,149 @@ TEST_F(tun, reattach_close_delete)
EXPECT_EQ(tun_delete(self->ifname), 0);
}
+FIXTURE(tun_vnet_udptnl)
+{
+ char ifname[IFNAMSIZ];
+ int fd, sock;
+};
+
+FIXTURE_VARIANT(tun_vnet_udptnl)
+{
+ int tunnel_type;
+ bool is_tap;
+};
+
+/* clang-format off */
+#define TUN_VNET_UDPTNL_VARIANT_ADD(type, desc) \
+ FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##udptnl) { \
+ .tunnel_type = type, \
+ .is_tap = true, \
+ }
+/* clang-format on */
+
+TUN_VNET_UDPTNL_VARIANT_ADD(UDP_TUNNEL_VXLAN_4IN4, 4in4);
+TUN_VNET_UDPTNL_VARIANT_ADD(UDP_TUNNEL_VXLAN_6IN4, 6in4);
+TUN_VNET_UDPTNL_VARIANT_ADD(UDP_TUNNEL_VXLAN_4IN6, 4in6);
+TUN_VNET_UDPTNL_VARIANT_ADD(UDP_TUNNEL_VXLAN_6IN6, 6in6);
+
+FIXTURE_SETUP(tun_vnet_udptnl)
+{
+ const int flags = (variant->is_tap ? IFF_TAP : IFF_TUN) | IFF_VNET_HDR |
+ IFF_MULTI_QUEUE | IFF_NO_PI;
+ const int features = TUN_F_CSUM | TUN_F_UDP_TUNNEL_GSO |
+ TUN_F_UDP_TUNNEL_GSO_CSUM | TUN_F_USO4 |
+ TUN_F_USO6;
+ const int hdrlen = TUN_VNET_TNL_SIZE;
+ int tunnel_type = variant->tunnel_type;
+ struct sockaddr_storage sockaddr;
+ struct vxlan_setup_config vxlan;
+ int ret, family;
+
+ self->fd = tun_open(self->ifname, flags, hdrlen, features,
+ param_hwaddr_outer_src);
+ ASSERT_GE(self->fd, 0);
+
+ family = (tunnel_type & UDP_TUNNEL_OUTER_IPV4) ? AF_INET : AF_INET6;
+ if (family == AF_INET) {
+ ret = ip_addr_add(self->ifname, AF_INET,
+ (void *)¶m_ipaddr4_outer_src,
+ IPPREFIX_LEN);
+ ret += ip_neigh_add(self->ifname, AF_INET,
+ (void *)¶m_ipaddr4_outer_dst,
+ param_hwaddr_outer_dst);
+ } else {
+ ret = ip_addr_add(self->ifname, AF_INET6,
+ (void *)¶m_ipaddr6_outer_src,
+ IP6PREFIX_LEN);
+ ret += ip_neigh_add(self->ifname, AF_INET6,
+ (void *)¶m_ipaddr6_outer_dst,
+ param_hwaddr_outer_dst);
+ }
+ ASSERT_EQ(ret, 0);
+
+ memset(&vxlan, 0, sizeof(vxlan));
+ vxlan.vni = VN_ID;
+ vxlan.dst_port = htons(VN_PORT);
+ vxlan.csum = 1;
+ vxlan.remote_ifindex = if_nametoindex(self->ifname);
+ memcpy(vxlan.hwaddr, param_hwaddr_inner_src, ETH_ALEN);
+
+ if (tunnel_type & UDP_TUNNEL_OUTER_IPV4) {
+ vxlan.remote_ip.sin.sin_family = AF_INET;
+ vxlan.remote_ip.sin.sin_addr = param_ipaddr4_outer_dst;
+ vxlan.local_ip.sin.sin_family = AF_INET;
+ vxlan.local_ip.sin.sin_addr = param_ipaddr4_outer_src;
+ } else {
+ vxlan.remote_ip.sin6.sin6_family = AF_INET6;
+ vxlan.remote_ip.sin6.sin6_addr = param_ipaddr6_outer_dst;
+ vxlan.local_ip.sin6.sin6_family = AF_INET6;
+ vxlan.local_ip.sin6.sin6_addr = param_ipaddr6_outer_src;
+ }
+
+ ret = dev_create(param_dev_vxlan_name, "vxlan", vxlan_fill_rtattr,
+ vxlan_fill_info_data, (void *)&vxlan);
+ ASSERT_EQ(ret, 0);
+
+ family = (tunnel_type & UDP_TUNNEL_INNER_IPV4) ? AF_INET : AF_INET6;
+ if (family == AF_INET) {
+ ret = ip_addr_add(param_dev_vxlan_name, AF_INET,
+ (void *)¶m_ipaddr4_inner_src,
+ IPPREFIX_LEN);
+ ret += ip_neigh_add(param_dev_vxlan_name, AF_INET,
+ (void *)¶m_ipaddr4_inner_dst,
+ param_hwaddr_inner_dst);
+ } else {
+ ret = ip_addr_add(param_dev_vxlan_name, AF_INET6,
+ (void *)¶m_ipaddr6_inner_src,
+ IP6PREFIX_LEN);
+ ret += ip_neigh_add(param_dev_vxlan_name, AF_INET6,
+ (void *)¶m_ipaddr6_inner_dst,
+ param_hwaddr_inner_dst);
+ }
+ ASSERT_EQ(ret, 0);
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ sockaddr.ss_family = family;
+ if (family == AF_INET) {
+ struct sockaddr_in *addr4 = (struct sockaddr_in *)&sockaddr;
+
+ addr4->sin_addr = param_ipaddr4_inner_src;
+ addr4->sin_port = htons(UDP_SRC_PORT);
+ } else {
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&sockaddr;
+
+ addr6->sin6_addr = param_ipaddr6_inner_src;
+ addr6->sin6_port = htons(UDP_SRC_PORT);
+ }
+ self->sock = udp_socket_open(&sockaddr, false);
+ ASSERT_GE(self->sock, 0);
+
+ /* give 1000us delay to ensure the interface address is ready */
+ usleep(1000);
+}
+
+FIXTURE_TEARDOWN(tun_vnet_udptnl)
+{
+ int ret;
+
+ if (self->sock != -1)
+ close(self->sock);
+
+ ret = dev_delete(param_dev_vxlan_name);
+ EXPECT_EQ(ret, 0);
+
+ ret = tun_delete(self->ifname);
+ EXPECT_EQ(ret, 0);
+}
+
+TEST_F(tun_vnet_udptnl, basic)
+{
+ int ret;
+ char cmd[256] = { 0 };
+
+ sprintf(cmd, "ip addr show %s > /dev/null 2>&1", param_dev_vxlan_name);
+ ret = system(cmd);
+ ASSERT_EQ(ret, 0);
+}
+
TEST_HARNESS_MAIN
--
2.49.0
Powered by blists - more mailing lists