On 10/17/25 7:29 AM, Alexis Lothoré (eBPF Foundation) wrote:
The test_tc_tunnel.sh script checks that a large variety of tunneling
mechanisms handled by the kernel can be handled as well by eBPF
programs. While this test shares similarities with test_tunnel.c (which
is already integrated in test_progs), those are testing slightly
different things:
- test_tunnel.c creates a tunnel interface, and then get and set tunnel
keys in packet metadata, from BPF programs.
- test_tc_tunnels.sh manually parses/crafts packets content
Bring the tests covered by test_tc_tunnel.sh into the test_progs
framework, by creating a dedicated test_tc_tunnel.sh. This new test
defines a "generic" runner which, for each test configuration:
- will bring the relevant veth pair, each of those isolated in a
dedicated namespace
- will check that traffic will fail if there is only an encapsulating
program attached to one veth egress
- will check that traffic succeed if we enable some decapsulation module
on kernel side
- will check that traffic still succeeds if we replace the kernel
decapsulation with some eBPF ingress decapsulation.
Example of the new test execution:
# ./test_progs -a tc_tunnel
#447/1 tc_tunnel/ipip_none:OK
#447/2 tc_tunnel/ipip6_none:OK
#447/3 tc_tunnel/ip6tnl_none:OK
#447/4 tc_tunnel/sit_none:OK
#447/5 tc_tunnel/vxlan_eth:OK
#447/6 tc_tunnel/ip6vxlan_eth:OK
#447/7 tc_tunnel/gre_none:OK
#447/8 tc_tunnel/gre_eth:OK
#447/9 tc_tunnel/gre_mpls:OK
#447/10 tc_tunnel/ip6gre_none:OK
#447/11 tc_tunnel/ip6gre_eth:OK
#447/12 tc_tunnel/ip6gre_mpls:OK
#447/13 tc_tunnel/udp_none:OK
#447/14 tc_tunnel/udp_eth:OK
#447/15 tc_tunnel/udp_mpls:OK
#447/16 tc_tunnel/ip6udp_none:OK
#447/17 tc_tunnel/ip6udp_eth:OK
#447/18 tc_tunnel/ip6udp_mpls:OK
#447 tc_tunnel:OK
Summary: 1/18 PASSED, 0 SKIPPED, 0 FAILED
Thanks for working on this!
One high level comment is to minimize switching netns to make the test
easier to follow.
Some ideas...
+static void stop_server(struct subtest_cfg *cfg)
+{
+ struct nstoken *nstoken = open_netns(SERVER_NS);
+
+ close(*cfg->server_fd);
+ cfg->server_fd = NULL;
+ close_netns(nstoken);
+}
+
+static int check_server_rx_data(struct subtest_cfg *cfg,
+ struct connection *conn, int len)
+{
+ struct nstoken *nstoken = open_netns(SERVER_NS);
+ int err;
+
+ memset(rx_buffer, 0, BUFFER_LEN);
+ err = recv(conn->server_fd, rx_buffer, len, 0);
+ close_netns(nstoken);
+ if (!ASSERT_EQ(err, len, "check rx data len"))
+ return 1;
+ if (!ASSERT_MEMEQ(tx_buffer, rx_buffer, len, "check received data"))
+ return 1;
+ return 0;
+}
+
+static struct connection *connect_client_to_server(struct subtest_cfg *cfg)
+{
+ struct network_helper_opts opts = {.timeout_ms = 500};
+ int family = cfg->ipproto == 6 ? AF_INET6 : AF_INET;
+ struct nstoken *nstoken = open_netns(CLIENT_NS);
+ struct connection *conn = NULL;
+ int client_fd, server_fd;
+
+ client_fd = connect_to_addr_str(family, SOCK_STREAM, cfg->server_addr,
+ TEST_PORT, &opts);
+ close_netns(nstoken);
+
+ if (client_fd < 0)
+ return NULL;
+
+ nstoken = open_netns(SERVER_NS);
Understood that the server is in another netns but I don't think it
needs to switch back to SERVER_NS to use its fd like accept(server_fd).
It can be done in client_ns. Please check.
The same for the above check_server_rx_data and stop_server.
> + server_fd = accept(*cfg->server_fd, NULL, NULL);
+ close_netns(nstoken);
+ if (server_fd < 0)
+ return NULL;
+
+ conn = malloc(sizeof(struct connection));
+ if (conn) {
+ conn->server_fd = server_fd;
+ conn->client_fd = client_fd;
+ }
+
+ return conn;
+}
+
+static void disconnect_client_from_server(struct subtest_cfg *cfg,
+ struct connection *conn)
+{
+ struct nstoken *nstoken;
+
+ nstoken = open_netns(SERVER_NS);
same here.
+ close(conn->server_fd);
+ close_netns(nstoken);
+ nstoken = open_netns(CLIENT_NS);
and here.
+ close(conn->client_fd);
+ close_netns(nstoken);
+ free(conn);
+}
+
+static int send_and_test_data(struct subtest_cfg *cfg, bool must_succeed)
See if this whole function can work in client_ns alone or may be the
caller run_test() can stay with the CLIENT_NS instead of...
+{
+ struct nstoken *nstoken = NULL;
+ struct connection *conn;
+ int err, res = -1;
+
+ conn = connect_client_to_server(cfg);
+ if (!must_succeed && !ASSERT_EQ(conn, NULL, "connection that must
fail"))
+ goto end;
+ else if (!must_succeed)
+ return 0;
+
+ if (!ASSERT_NEQ(conn, NULL, "connection that must succeed"))
+ return 1;
+
+ nstoken = open_netns(CLIENT_NS);
switching here...
+ err = send(conn->client_fd, tx_buffer, DEFAULT_TEST_DATA_SIZE, 0);
+ close_netns(nstoken);
+ if (!ASSERT_EQ(err, DEFAULT_TEST_DATA_SIZE, "send data from client"))
+ goto end;
+ if (check_server_rx_data(cfg, conn, DEFAULT_TEST_DATA_SIZE))
+ goto end;
+
+ if (!cfg->test_gso) {
+ res = 0;
+ goto end;
+ }
+
+ nstoken = open_netns(CLIENT_NS);
and here.
+static void run_test(struct subtest_cfg *cfg)
+{
See if it can open_netns(CLIENT_NS) once at the beginning.
+ if (!ASSERT_OK(run_server(cfg), "run server"))
The run_server and configure_* can open/close SERVER_NS when needed.
open_netns should have saved the previous netns (i.e. CLIENT_NS) such
that it knows which one to restore during close_netns(). I don't think I
have tried that though but should work. Please check.
+ goto fail;
+
+ // Basic communication must work
Consistent comment style. Stay with /* */
+ if (!ASSERT_OK(send_and_test_data(cfg, true), "connect without any
encap"))
+ goto fail;
+
+ // Attach encapsulation program to client, communication must fail
+ if (!ASSERT_OK(configure_encapsulation(cfg), "configure encapsulation"))
+ return;
+ if (!ASSERT_OK(send_and_test_data(cfg, false), "connect with encap prog
only"))
+ goto fail;
+
+ /* Insert kernel decap module, connection must succeed */
+ if (!ASSERT_OK(configure_kernel_decapsulation(cfg), "configure kernel
decapsulation"))
+ goto fail;
+ if (!ASSERT_OK(send_and_test_data(cfg, !cfg->expect_kern_decap_failure),
+ "connect with encap prog and kern decap"))
+ goto fail;
+
+ // Replace kernel module with BPF decap, test must pass
+ if (!ASSERT_OK(configure_ebpf_decapsulation(cfg), "configure ebpf
decapsulation"))
+ goto fail;
+ ASSERT_OK(send_and_test_data(cfg, true), "connect with encap and decap
progs");
+
+fail:
+ stop_server(cfg);
+}
struct subtest_cfg subtests_cfg[] = {
static
+int subtests_count = sizeof(subtests_cfg)/sizeof(struct subtest_cfg);
ARRAY_SIZE(subtests_cfg)
pw-bot: cr