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


Reply via email to