selftest: tun: Add test for sending gso packet into tun

The test constructs a raw packet, prepends a virtio_net_hdr,
and writes the result to the TUN device. This mimics the behavior
of a vm forwarding a guest's packet to the host networking stack.

Signed-off-by: Xu Du <xudu@redhat.com>
Link: https://patch.msgid.link/a988dbc9ca109e4f1f0b33858c5035bce8ebede3.1768979440.git.xudu@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Xu Du 2026-01-21 18:04:59 +08:00 committed by Jakub Kicinski
parent 24e59f26ee
commit 400e658aa0

View File

@ -75,7 +75,34 @@ static struct in6_addr param_ipaddr6_inner_src = {
#define UDP_TUNNEL_OUTER_IPV4 (UDP_TUNNEL_GENEVE_4IN4 | UDP_TUNNEL_GENEVE_6IN4)
#define UDP_TUNNEL_INNER_IPV4 (UDP_TUNNEL_GENEVE_4IN4 | UDP_TUNNEL_GENEVE_4IN6)
#define UDP_TUNNEL_GENEVE_4IN4_HDRLEN \
(ETH_HLEN + 2 * sizeof(struct iphdr) + GENEVE_HLEN + \
2 * sizeof(struct udphdr))
#define UDP_TUNNEL_GENEVE_6IN6_HDRLEN \
(ETH_HLEN + 2 * sizeof(struct ipv6hdr) + GENEVE_HLEN + \
2 * sizeof(struct udphdr))
#define UDP_TUNNEL_GENEVE_4IN6_HDRLEN \
(ETH_HLEN + sizeof(struct iphdr) + sizeof(struct ipv6hdr) + \
GENEVE_HLEN + 2 * sizeof(struct udphdr))
#define UDP_TUNNEL_GENEVE_6IN4_HDRLEN \
(ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct iphdr) + \
GENEVE_HLEN + 2 * sizeof(struct udphdr))
#define UDP_TUNNEL_HDRLEN(type) \
((type) == UDP_TUNNEL_GENEVE_4IN4 ? UDP_TUNNEL_GENEVE_4IN4_HDRLEN : \
(type) == UDP_TUNNEL_GENEVE_6IN6 ? UDP_TUNNEL_GENEVE_6IN6_HDRLEN : \
(type) == UDP_TUNNEL_GENEVE_4IN6 ? UDP_TUNNEL_GENEVE_4IN6_HDRLEN : \
(type) == UDP_TUNNEL_GENEVE_6IN4 ? UDP_TUNNEL_GENEVE_6IN4_HDRLEN : \
0)
#define UDP_TUNNEL_MSS(type) (ETH_DATA_LEN - UDP_TUNNEL_HDRLEN(type))
#define UDP_TUNNEL_MAX(type, is_tap) \
(ETH_MAX_MTU - UDP_TUNNEL_HDRLEN(type) - ((is_tap) ? ETH_HLEN : 0))
#define TUN_VNET_TNL_SIZE sizeof(struct virtio_net_hdr_v1_hash_tunnel)
#define MAX_VNET_TUNNEL_PACKET_SZ \
(TUN_VNET_TNL_SIZE + ETH_HLEN + UDP_TUNNEL_GENEVE_6IN6_HDRLEN + \
ETH_MAX_MTU)
struct geneve_setup_config {
int family;
@ -408,15 +435,23 @@ FIXTURE(tun_vnet_udptnl)
FIXTURE_VARIANT(tun_vnet_udptnl)
{
int tunnel_type;
bool is_tap;
int gso_size;
int data_size;
int r_num_mss;
bool is_tap, no_gso;
};
/* clang-format off */
#define TUN_VNET_UDPTNL_VARIANT_ADD(type, desc) \
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##udptnl) { \
FIXTURE_VARIANT_ADD(tun_vnet_udptnl, desc##_1mss) { \
/* send a single MSS: fall back to no GSO */ \
.tunnel_type = type, \
.gso_size = UDP_TUNNEL_MSS(type), \
.data_size = UDP_TUNNEL_MSS(type), \
.r_num_mss = 1, \
.is_tap = true, \
}
.no_gso = true, \
};
/* clang-format on */
TUN_VNET_UDPTNL_VARIANT_ADD(UDP_TUNNEL_GENEVE_4IN4, 4in4);
@ -544,14 +579,105 @@ FIXTURE_TEARDOWN(tun_vnet_udptnl)
EXPECT_EQ(ret, 0);
}
TEST_F(tun_vnet_udptnl, basic)
static int build_gso_packet_into_tun(const FIXTURE_VARIANT(tun_vnet_udptnl) *
variant,
uint8_t *buf)
{
int ret;
char cmd[256] = { 0 };
int pktlen, hlen, proto, inner_family, outer_family;
int tunnel_type = variant->tunnel_type;
int payload_len = variant->data_size;
int gso_size = variant->gso_size;
uint8_t *outer_udph, *cur = buf;
void *sip, *dip, *smac, *dmac;
bool is_tap = variant->is_tap;
sprintf(cmd, "ip addr show %s > /dev/null 2>&1", param_dev_geneve_name);
ret = system(cmd);
ASSERT_EQ(ret, 0);
hlen = (is_tap ? ETH_HLEN : 0) + UDP_TUNNEL_HDRLEN(tunnel_type);
inner_family = (tunnel_type & UDP_TUNNEL_INNER_IPV4) ? AF_INET :
AF_INET6;
outer_family = (tunnel_type & UDP_TUNNEL_OUTER_IPV4) ? AF_INET :
AF_INET6;
cur += build_virtio_net_hdr_v1_hash_tunnel(cur, is_tap, hlen, gso_size,
outer_family, inner_family);
pktlen = hlen + payload_len;
assign_ifaddr_vars(outer_family, 1, &sip, &dip, &smac, &dmac);
if (is_tap) {
proto = outer_family == AF_INET ? ETH_P_IP : ETH_P_IPV6;
pktlen -= ETH_HLEN;
cur += build_eth(cur, proto, dmac, smac);
}
if (outer_family == AF_INET) {
pktlen = pktlen - sizeof(struct iphdr);
cur += build_ipv4_header(cur, IPPROTO_UDP, pktlen, dip, sip);
} else {
pktlen = pktlen - sizeof(struct ipv6hdr);
cur += build_ipv6_header(cur, IPPROTO_UDP, 0, pktlen, dip, sip);
}
outer_udph = cur;
assign_ifaddr_vars(inner_family, 0, &sip, &dip, &smac, &dmac);
pktlen -= sizeof(struct udphdr);
proto = inner_family == AF_INET ? ETH_P_IP : ETH_P_IPV6;
cur += build_udp_header(cur, UDP_SRC_PORT, VN_PORT, pktlen);
cur += build_geneve_header(cur, VN_ID);
cur += build_eth(cur, proto, dmac, smac);
pktlen = sizeof(struct udphdr) + payload_len;
if (inner_family == AF_INET)
cur += build_ipv4_header(cur, IPPROTO_UDP, pktlen, dip, sip);
else
cur += build_ipv6_header(cur, IPPROTO_UDP, 0, pktlen, dip, sip);
cur += build_udp_packet(cur, UDP_DST_PORT, UDP_SRC_PORT, payload_len,
inner_family, false);
build_udp_packet_csum(outer_udph, outer_family, false);
return cur - buf;
}
static int
receive_gso_packet_from_tunnel(FIXTURE_DATA(tun_vnet_udptnl) * self,
const FIXTURE_VARIANT(tun_vnet_udptnl) * variant,
int *r_num_mss)
{
uint8_t packet_buf[MAX_VNET_TUNNEL_PACKET_SZ];
int len, total_len = 0, socket = self->sock;
int payload_len = variant->data_size;
while (total_len < payload_len) {
len = recv(socket, packet_buf, sizeof(packet_buf), 0);
if (len <= 0) {
if (len < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
perror("recv");
break;
}
(*r_num_mss)++;
total_len += len;
}
return total_len;
}
TEST_F(tun_vnet_udptnl, send_gso_packet)
{
uint8_t pkt[MAX_VNET_TUNNEL_PACKET_SZ];
int r_num_mss = 0;
int ret, off;
memset(pkt, 0, sizeof(pkt));
off = build_gso_packet_into_tun(variant, pkt);
ret = write(self->fd, pkt, off);
ASSERT_EQ(ret, off);
ret = receive_gso_packet_from_tunnel(self, variant, &r_num_mss);
ASSERT_EQ(ret, variant->data_size);
ASSERT_EQ(r_num_mss, variant->r_num_mss);
}
TEST_HARNESS_MAIN