[PATCH bpf-next 2/4] selftests/bpf: integrate test_tc_edt into test_progs

Alexis Lothoré (eBPF Foundation) posted 4 patches 3 months, 1 week ago
There is a newer version of this series
[PATCH bpf-next 2/4] selftests/bpf: integrate test_tc_edt into test_progs
Posted by Alexis Lothoré (eBPF Foundation) 3 months, 1 week ago
test_tc_edt.sh uses a pair of veth and a BPF program attached to the TX
veth to shape the traffic to 5Mbps. It then checks that the amount of
received bytes (at interface level), compared to the TX duration, indeed
matches 5Mbps.

Convert this test script to the test_progs framework:
- keep the double veth setup, isolated in two veths
- run a small tcp server, and connect client to server
- start pushing bytes continuously, but for a shorter period of time
  than the original test (to keep CI run duration tolerable)
- measure the number or received bytes through /proc/net/dev, compute
  the resulting rate
- ensure that this rate is in a 2% error margin around the target rate

This two percent value, while being tight, is hopefully large enough to
not make the test too flaky in CI, while also turning it into a small
example of BPF-based shaping.

Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>
---
 .../testing/selftests/bpf/prog_tests/test_tc_edt.c | 273 +++++++++++++++++++++
 1 file changed, 273 insertions(+)

diff --git a/tools/testing/selftests/bpf/prog_tests/test_tc_edt.c b/tools/testing/selftests/bpf/prog_tests/test_tc_edt.c
new file mode 100644
index 000000000000..a77e48fdf4e4
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/test_tc_edt.c
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+/*
+ * BPF-based flow shaping
+ *
+ * The test brings up two veth in two isolated namespaces, attach some flow
+ * shaping program onto it, and ensures that a manual speedtest maximum
+ * value matches the rate set in the BPF shapers.
+ */
+
+#include <asm-generic/socket.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <bpf/libbpf.h>
+#include <pthread.h>
+#include "test_progs.h"
+#include "network_helpers.h"
+#include "test_tc_edt.skel.h"
+
+#define SERVER_NS "tc-edt-server-ns"
+#define CLIENT_NS "tc-edt-client-ns"
+#define IP4_ADDR_VETH1 "192.168.1.1"
+#define IP4_ADDR_VETH2 "192.168.1.2"
+#define IP4_ADDR_VETH2_HEX 0xC0A80102
+
+#define BUFFER_LEN		500
+#define TIMEOUT_MS		2000
+#define TEST_PORT		9000
+#define TARGET_RATE_MBPS	5.0
+#define RATE_ERROR_PERCENT	2.0
+
+struct connection {
+	int server_listen_fd;
+	int server_conn_fd;
+	int client_conn_fd;
+};
+
+static char tx_buffer[BUFFER_LEN], rx_buffer[BUFFER_LEN];
+static bool tx_timeout;
+
+static int start_server_listen(void)
+{
+	struct nstoken *nstoken = open_netns(SERVER_NS);
+	int server_fd;
+
+	if (!ASSERT_OK_PTR(nstoken, "enter server ns"))
+		return -1;
+
+	server_fd = start_server_str(AF_INET, SOCK_STREAM, IP4_ADDR_VETH2,
+				     TEST_PORT, NULL);
+	close_netns(nstoken);
+	return server_fd;
+}
+
+static struct connection *setup_connection(void)
+{
+	int server_listen_fd, server_conn_fd, client_conn_fd;
+	struct nstoken *nstoken;
+	struct connection *conn;
+
+	conn = malloc(sizeof(struct connection));
+	if (!ASSERT_OK_PTR(conn, "allocate connection"))
+		goto fail;
+	server_listen_fd = start_server_listen();
+	if (!ASSERT_OK_FD(server_listen_fd, "start server"))
+		goto fail_free_conn;
+
+	nstoken = open_netns(CLIENT_NS);
+	if (!ASSERT_OK_PTR(nstoken, "enter client ns"))
+		goto fail_close_server;
+
+	client_conn_fd = connect_to_addr_str(AF_INET, SOCK_STREAM,
+					     IP4_ADDR_VETH2, TEST_PORT, NULL);
+	close_netns(nstoken);
+	if (!ASSERT_OK_FD(client_conn_fd, "connect client"))
+		goto fail_close_server;
+
+	server_conn_fd = accept(server_listen_fd, NULL, NULL);
+	if (!ASSERT_OK_FD(server_conn_fd, "accept client connection"))
+		goto fail_close_client;
+
+	conn->server_listen_fd = server_listen_fd;
+	conn->server_conn_fd = server_conn_fd;
+	conn->client_conn_fd = client_conn_fd;
+	return conn;
+
+fail_close_client:
+	close(client_conn_fd);
+fail_close_server:
+	close(server_listen_fd);
+fail_free_conn:
+	free(conn);
+fail:
+	return NULL;
+}
+
+static void cleanup_connection(struct connection *conn)
+{
+	if (!conn)
+		return;
+	close(conn->client_conn_fd);
+	close(conn->server_conn_fd);
+	close(conn->server_listen_fd);
+	free(conn);
+}
+
+static void *run_server(void *arg)
+{
+	int *fd = (int *)arg;
+	int ret;
+
+	while (!tx_timeout)
+		ret = recv(*fd, rx_buffer, BUFFER_LEN, 0);
+
+	return NULL;
+}
+
+static int read_rx_bytes(__u64 *result)
+{
+	struct nstoken *nstoken = open_netns(SERVER_NS);
+	char line[512];
+	FILE *fp;
+
+	if (!ASSERT_OK_PTR(nstoken, "open server ns"))
+		return -1;
+
+	fp = fopen("/proc/net/dev", "r");
+	if (!ASSERT_OK_PTR(fp, "open /proc/net/dev")) {
+		close_netns(nstoken);
+		return -1;
+	}
+
+	/* Skip the first two header lines */
+	fgets(line, sizeof(line), fp);
+	fgets(line, sizeof(line), fp);
+
+	while (fgets(line, sizeof(line), fp)) {
+		char name[32];
+		__u64 rx_bytes = 0;
+
+		if (sscanf(line, " %31[^:]: %llu", name, &rx_bytes) != 2)
+			continue;
+
+		if (strcmp(name, "veth2") == 0) {
+			fclose(fp);
+			close_netns(nstoken);
+			*result = rx_bytes;
+			return 0;
+		}
+	}
+
+	fclose(fp);
+	close_netns(nstoken);
+	return -1;
+}
+static int setup(struct test_tc_edt *skel)
+{
+	struct nstoken *nstoken_client, *nstoken_server;
+	int ret;
+
+	if (!ASSERT_OK(make_netns(CLIENT_NS), "create client ns"))
+		goto fail;
+	if (!ASSERT_OK(make_netns(SERVER_NS), "create server ns"))
+		goto fail_delete_client_ns;
+
+	nstoken_client = open_netns(CLIENT_NS);
+	if (!ASSERT_OK_PTR(nstoken_client, "open client ns"))
+		goto fail_delete_server_ns;
+	SYS(fail_close_client_ns, "ip link add veth1 type veth peer name %s",
+	    "veth2 netns " SERVER_NS);
+	SYS(fail_close_client_ns, "ip -4 addr add " IP4_ADDR_VETH1 "/24 dev veth1");
+	SYS(fail_close_client_ns, "ip link set veth1 up");
+	SYS(fail_close_client_ns, "tc qdisc add dev veth1 root fq");
+	ret = tc_prog_attach("veth1", -1, bpf_program__fd(skel->progs.tc_prog));
+	if (!ASSERT_OK(ret, "attach bpf prog"))
+		goto fail_close_client_ns;
+
+	nstoken_server = open_netns(SERVER_NS);
+	if (!ASSERT_OK_PTR(nstoken_server, "enter server ns"))
+		goto fail_close_client_ns;
+	SYS(fail_close_server_ns, "ip -4 addr add " IP4_ADDR_VETH2 "/24 dev veth2");
+	SYS(fail_close_server_ns, "ip link set veth2 up");
+	close_netns(nstoken_server);
+	close_netns(nstoken_client);
+
+	return 0;
+
+fail_close_server_ns:
+	close_netns(nstoken_server);
+fail_close_client_ns:
+	close_netns(nstoken_client);
+fail_delete_server_ns:
+	remove_netns(SERVER_NS);
+fail_delete_client_ns:
+	remove_netns(CLIENT_NS);
+fail:
+	return -1;
+}
+
+static void cleanup(void)
+{
+	remove_netns(CLIENT_NS);
+	remove_netns(SERVER_NS);
+}
+
+static void run_test(void)
+{
+	__u64 rx_bytes_start, rx_bytes_end;
+	double rate_mbps, rate_error;
+	pthread_t server_thread = 0;
+	struct connection *conn;
+	__u64 ts_start, ts_end;
+	int ret;
+
+
+	conn = setup_connection();
+	if (!ASSERT_OK_PTR(conn, "setup client and server connection"))
+		return;
+
+	ret = pthread_create(&server_thread, NULL, run_server,
+			     (void *)(&conn->server_conn_fd));
+	if (!ASSERT_OK(ret, "start server rx thread"))
+		goto end_cleanup_conn;
+	if (!ASSERT_OK(read_rx_bytes(&rx_bytes_start), "read rx_bytes"))
+		goto end_kill_thread;
+	ts_start = get_time_ns();
+	while (true) {
+		send(conn->client_conn_fd, (void *)tx_buffer, BUFFER_LEN, 0);
+		ts_end = get_time_ns();
+		if ((ts_end - ts_start)/100000 >= TIMEOUT_MS) {
+			tx_timeout = true;
+			ret = read_rx_bytes(&rx_bytes_end);
+			if (!ASSERT_OK(ret, "read_rx_bytes"))
+				goto end_cleanup_conn;
+			break;
+		}
+	}
+
+	rate_mbps = (rx_bytes_end - rx_bytes_start) /
+		    ((ts_end - ts_start) / 1000.0);
+	rate_error =
+		fabs((rate_mbps - TARGET_RATE_MBPS) * 100.0 / TARGET_RATE_MBPS);
+	fprintf(stderr, "Rate:\t%f\nError:\t%f\n", rate_mbps, rate_error);
+
+	ASSERT_LE(rate_error, RATE_ERROR_PERCENT,
+		  "rate error is lower than threshold");
+
+end_kill_thread:
+	tx_timeout = true;
+end_cleanup_conn:
+	cleanup_connection(conn);
+}
+
+void test_tc_edt(void)
+{
+	struct test_tc_edt *skel;
+
+	skel = test_tc_edt__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "skel open and load"))
+		return;
+
+	if (!ASSERT_OK(setup(skel), "global setup"))
+		return;
+
+	run_test();
+
+	cleanup();
+	test_tc_edt__destroy(skel);
+}

-- 
2.51.1.dirty

Re: [PATCH bpf-next 2/4] selftests/bpf: integrate test_tc_edt into test_progs
Posted by bot+bpf-ci@kernel.org 3 months, 1 week ago
```
commit fb0c5cfad37eaad30f584caaf53fe9824026bc92
Author: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com>

selftests/bpf: integrate test_tc_edt into test_progs

This patch converts test_tc_edt.sh to the test_progs framework,
creating a new test that measures BPF-based traffic shaping to verify
that the resulting rate matches 5Mbps within a 2% error margin.

> diff --git a/tools/testing/selftests/bpf/prog_tests/test_tc_edt.c b/tools/testing/selftests/bpf/prog_tests/test_tc_edt.c
> new file mode 100644
> index 000000000..a77e48fdf
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/test_tc_edt.c

[ ... ]

> +#define TARGET_RATE_MBPS	5.0
> +#define RATE_ERROR_PERCENT	2.0

[ ... ]

> +static void run_test(void)
> +{
> +	__u64 rx_bytes_start, rx_bytes_end;
> +	double rate_mbps, rate_error;
> +	pthread_t server_thread = 0;
> +	struct connection *conn;
> +	__u64 ts_start, ts_end;
> +	int ret;
> +
> +
> +	conn = setup_connection();
> +	if (!ASSERT_OK_PTR(conn, "setup client and server connection"))
> +		return;
> +
> +	ret = pthread_create(&server_thread, NULL, run_server,
> +			     (void *)(&conn->server_conn_fd));
> +	if (!ASSERT_OK(ret, "start server rx thread"))
> +		goto end_cleanup_conn;
> +	if (!ASSERT_OK(read_rx_bytes(&rx_bytes_start), "read rx_bytes"))
> +		goto end_kill_thread;
> +	ts_start = get_time_ns();
> +	while (true) {
> +		send(conn->client_conn_fd, (void *)tx_buffer, BUFFER_LEN, 0);
> +		ts_end = get_time_ns();
> +		if ((ts_end - ts_start)/100000 >= TIMEOUT_MS) {
                                         ^^^^^^

Does this time conversion use the correct divisor? The timeout check
appears to divide nanoseconds by 100000, but TIMEOUT_MS is 2000
milliseconds. Converting nanoseconds to milliseconds requires dividing
by 1000000, not 100000. With the current calculation, the timeout would
trigger after 200 milliseconds rather than 2000 milliseconds.

> +			tx_timeout = true;
> +			ret = read_rx_bytes(&rx_bytes_end);
> +			if (!ASSERT_OK(ret, "read_rx_bytes"))
> +				goto end_cleanup_conn;
> +			break;
> +		}
> +	}
> +
> +	rate_mbps = (rx_bytes_end - rx_bytes_start) /
> +		    ((ts_end - ts_start) / 1000.0);
                                       ^^^^^^

Can this rate calculation produce the correct units? The numerator is in
bytes, and the denominator (nanoseconds / 1000.0) is in microseconds,
giving bytes per microsecond which equals megabytes per second (MB/s).
However, the commit message states the test verifies "5Mbps" (megabits
per second), and TARGET_RATE_MBPS suggests the same. Converting MB/s to
Mbps requires multiplying by 8 (bits per byte), which appears to be
missing. Is the test measuring in the wrong units, or is there a naming
inconsistency?

> +	rate_error =
> +		fabs((rate_mbps - TARGET_RATE_MBPS) * 100.0 / TARGET_RATE_MBPS);
> +	fprintf(stderr, "Rate:\t%f\nError:\t%f\n", rate_mbps, rate_error);
> +
> +	ASSERT_LE(rate_error, RATE_ERROR_PERCENT,
> +		  "rate error is lower than threshold");
> +
> +end_kill_thread:
> +	tx_timeout = true;
> +end_cleanup_conn:
> +	cleanup_connection(conn);
> +}


```

---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

In-Reply-To-Subject: `selftests/bpf: integrate test_tc_edt into test_progs`
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/18978170972
Re: [PATCH bpf-next 2/4] selftests/bpf: integrate test_tc_edt into test_progs
Posted by Martin KaFai Lau 3 months, 1 week ago
On 10/31/25 9:20 AM, bot+bpf-ci@kernel.org wrote:
>> +static void run_test(void)
>> +{
>> +	__u64 rx_bytes_start, rx_bytes_end;
>> +	double rate_mbps, rate_error;
>> +	pthread_t server_thread = 0;
>> +	struct connection *conn;
>> +	__u64 ts_start, ts_end;
>> +	int ret;
>> +
>> +
>> +	conn = setup_connection();
>> +	if (!ASSERT_OK_PTR(conn, "setup client and server connection"))
>> +		return;
>> +
>> +	ret = pthread_create(&server_thread, NULL, run_server,
>> +			     (void *)(&conn->server_conn_fd));
>> +	if (!ASSERT_OK(ret, "start server rx thread"))
>> +		goto end_cleanup_conn;
>> +	if (!ASSERT_OK(read_rx_bytes(&rx_bytes_start), "read rx_bytes"))
>> +		goto end_kill_thread;
>> +	ts_start = get_time_ns();
>> +	while (true) {
>> +		send(conn->client_conn_fd, (void *)tx_buffer, BUFFER_LEN, 0);
>> +		ts_end = get_time_ns();
>> +		if ((ts_end - ts_start)/100000 >= TIMEOUT_MS) {
>                                           ^^^^^^
> 
> Does this time conversion use the correct divisor? The timeout check
> appears to divide nanoseconds by 100000, but TIMEOUT_MS is 2000
> milliseconds. Converting nanoseconds to milliseconds requires dividing
> by 1000000, not 100000. With the current calculation, the timeout would
> trigger after 200 milliseconds rather than 2000 milliseconds.

The report is correct, there is a typo in the denominator.

Use the send_recv_data() helper in network_helpers.c. It should simplify 
this test and no need to pthread_create, while loop, ....etc. 
send_recv_data limits by the number of bytes instead of the length of 
time. There is a target rate in this test, so it should be easy to 
convert from time limit to byte limit and reuse the send_recv_data.

pw-bot: cr
Re: [PATCH bpf-next 2/4] selftests/bpf: integrate test_tc_edt into test_progs
Posted by Alexis Lothoré 3 months, 1 week ago
On Fri Oct 31, 2025 at 8:28 PM CET, Martin KaFai Lau wrote:
> On 10/31/25 9:20 AM, bot+bpf-ci@kernel.org wrote:

[...]

>>> +	while (true) {
>>> +		send(conn->client_conn_fd, (void *)tx_buffer, BUFFER_LEN, 0);
>>> +		ts_end = get_time_ns();
>>> +		if ((ts_end - ts_start)/100000 >= TIMEOUT_MS) {
>>                                           ^^^^^^
>> 
>> Does this time conversion use the correct divisor? The timeout check
>> appears to divide nanoseconds by 100000, but TIMEOUT_MS is 2000
>> milliseconds. Converting nanoseconds to milliseconds requires dividing
>> by 1000000, not 100000. With the current calculation, the timeout would
>> trigger after 200 milliseconds rather than 2000 milliseconds.
>
> The report is correct, there is a typo in the denominator.

Gaaaah, that's one stupid mistake, and so I possibly got too enthusiastic
about the initial results. I'll redo some more tests with this point fixed.

> Use the send_recv_data() helper in network_helpers.c. It should simplify 
> this test and no need to pthread_create, while loop, ....etc. 
> send_recv_data limits by the number of bytes instead of the length of 
> time. There is a target rate in this test, so it should be easy to 
> convert from time limit to byte limit and reuse the send_recv_data.

Nice, thanks for the hint, I'll then simplify the whole test by using this
helper.

Alexis



-- 
Alexis Lothoré, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com