|
| 1 | +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
| 2 | + |
| 3 | +/* |
| 4 | + * BPF-based flow shaping |
| 5 | + * |
| 6 | + * The test brings up two veth in two isolated namespaces, attach some flow |
| 7 | + * shaping program onto it, and ensures that a manual speedtest maximum |
| 8 | + * value matches the rate set in the BPF shapers. |
| 9 | + */ |
| 10 | + |
| 11 | +#include <asm-generic/socket.h> |
| 12 | +#include <stdio.h> |
| 13 | +#include <unistd.h> |
| 14 | +#include <fcntl.h> |
| 15 | +#include <math.h> |
| 16 | +#include <sys/time.h> |
| 17 | +#include <sys/socket.h> |
| 18 | +#include <bpf/libbpf.h> |
| 19 | +#include <pthread.h> |
| 20 | +#include "test_progs.h" |
| 21 | +#include "network_helpers.h" |
| 22 | +#include "test_tc_edt.skel.h" |
| 23 | + |
| 24 | +#define SERVER_NS "tc-edt-server-ns" |
| 25 | +#define CLIENT_NS "tc-edt-client-ns" |
| 26 | +#define IP4_ADDR_VETH1 "192.168.1.1" |
| 27 | +#define IP4_ADDR_VETH2 "192.168.1.2" |
| 28 | +#define IP4_ADDR_VETH2_HEX 0xC0A80102 |
| 29 | + |
| 30 | +#define BUFFER_LEN 500 |
| 31 | +#define TIMEOUT_MS 2000 |
| 32 | +#define TEST_PORT 9000 |
| 33 | +#define TARGET_RATE_MBPS 5.0 |
| 34 | +#define RATE_ERROR_PERCENT 2.0 |
| 35 | + |
| 36 | +struct connection { |
| 37 | + int server_listen_fd; |
| 38 | + int server_conn_fd; |
| 39 | + int client_conn_fd; |
| 40 | +}; |
| 41 | + |
| 42 | +static char tx_buffer[BUFFER_LEN], rx_buffer[BUFFER_LEN]; |
| 43 | +static bool tx_timeout; |
| 44 | + |
| 45 | +static int start_server_listen(void) |
| 46 | +{ |
| 47 | + struct nstoken *nstoken = open_netns(SERVER_NS); |
| 48 | + int server_fd; |
| 49 | + |
| 50 | + if (!ASSERT_OK_PTR(nstoken, "enter server ns")) |
| 51 | + return -1; |
| 52 | + |
| 53 | + server_fd = start_server_str(AF_INET, SOCK_STREAM, IP4_ADDR_VETH2, |
| 54 | + TEST_PORT, NULL); |
| 55 | + close_netns(nstoken); |
| 56 | + return server_fd; |
| 57 | +} |
| 58 | + |
| 59 | +static struct connection *setup_connection(void) |
| 60 | +{ |
| 61 | + int server_listen_fd, server_conn_fd, client_conn_fd; |
| 62 | + struct nstoken *nstoken; |
| 63 | + struct connection *conn; |
| 64 | + |
| 65 | + conn = malloc(sizeof(struct connection)); |
| 66 | + if (!ASSERT_OK_PTR(conn, "allocate connection")) |
| 67 | + goto fail; |
| 68 | + server_listen_fd = start_server_listen(); |
| 69 | + if (!ASSERT_OK_FD(server_listen_fd, "start server")) |
| 70 | + goto fail_free_conn; |
| 71 | + |
| 72 | + nstoken = open_netns(CLIENT_NS); |
| 73 | + if (!ASSERT_OK_PTR(nstoken, "enter client ns")) |
| 74 | + goto fail_close_server; |
| 75 | + |
| 76 | + client_conn_fd = connect_to_addr_str(AF_INET, SOCK_STREAM, |
| 77 | + IP4_ADDR_VETH2, TEST_PORT, NULL); |
| 78 | + close_netns(nstoken); |
| 79 | + if (!ASSERT_OK_FD(client_conn_fd, "connect client")) |
| 80 | + goto fail_close_server; |
| 81 | + |
| 82 | + server_conn_fd = accept(server_listen_fd, NULL, NULL); |
| 83 | + if (!ASSERT_OK_FD(server_conn_fd, "accept client connection")) |
| 84 | + goto fail_close_client; |
| 85 | + |
| 86 | + conn->server_listen_fd = server_listen_fd; |
| 87 | + conn->server_conn_fd = server_conn_fd; |
| 88 | + conn->client_conn_fd = client_conn_fd; |
| 89 | + return conn; |
| 90 | + |
| 91 | +fail_close_client: |
| 92 | + close(client_conn_fd); |
| 93 | +fail_close_server: |
| 94 | + close(server_listen_fd); |
| 95 | +fail_free_conn: |
| 96 | + free(conn); |
| 97 | +fail: |
| 98 | + return NULL; |
| 99 | +} |
| 100 | + |
| 101 | +static void cleanup_connection(struct connection *conn) |
| 102 | +{ |
| 103 | + if (!conn) |
| 104 | + return; |
| 105 | + close(conn->client_conn_fd); |
| 106 | + close(conn->server_conn_fd); |
| 107 | + close(conn->server_listen_fd); |
| 108 | + free(conn); |
| 109 | +} |
| 110 | + |
| 111 | +static void *run_server(void *arg) |
| 112 | +{ |
| 113 | + int *fd = (int *)arg; |
| 114 | + int ret; |
| 115 | + |
| 116 | + while (!tx_timeout) |
| 117 | + ret = recv(*fd, rx_buffer, BUFFER_LEN, 0); |
| 118 | + |
| 119 | + return NULL; |
| 120 | +} |
| 121 | + |
| 122 | +static int read_rx_bytes(__u64 *result) |
| 123 | +{ |
| 124 | + struct nstoken *nstoken = open_netns(SERVER_NS); |
| 125 | + char line[512]; |
| 126 | + FILE *fp; |
| 127 | + |
| 128 | + if (!ASSERT_OK_PTR(nstoken, "open server ns")) |
| 129 | + return -1; |
| 130 | + |
| 131 | + fp = fopen("/proc/net/dev", "r"); |
| 132 | + if (!ASSERT_OK_PTR(fp, "open /proc/net/dev")) { |
| 133 | + close_netns(nstoken); |
| 134 | + return -1; |
| 135 | + } |
| 136 | + |
| 137 | + /* Skip the first two header lines */ |
| 138 | + fgets(line, sizeof(line), fp); |
| 139 | + fgets(line, sizeof(line), fp); |
| 140 | + |
| 141 | + while (fgets(line, sizeof(line), fp)) { |
| 142 | + char name[32]; |
| 143 | + __u64 rx_bytes = 0; |
| 144 | + |
| 145 | + if (sscanf(line, " %31[^:]: %llu", name, &rx_bytes) != 2) |
| 146 | + continue; |
| 147 | + |
| 148 | + if (strcmp(name, "veth2") == 0) { |
| 149 | + fclose(fp); |
| 150 | + close_netns(nstoken); |
| 151 | + *result = rx_bytes; |
| 152 | + return 0; |
| 153 | + } |
| 154 | + } |
| 155 | + |
| 156 | + fclose(fp); |
| 157 | + close_netns(nstoken); |
| 158 | + return -1; |
| 159 | +} |
| 160 | +static int setup(struct test_tc_edt *skel) |
| 161 | +{ |
| 162 | + struct nstoken *nstoken_client, *nstoken_server; |
| 163 | + int ret; |
| 164 | + |
| 165 | + if (!ASSERT_OK(make_netns(CLIENT_NS), "create client ns")) |
| 166 | + goto fail; |
| 167 | + if (!ASSERT_OK(make_netns(SERVER_NS), "create server ns")) |
| 168 | + goto fail_delete_client_ns; |
| 169 | + |
| 170 | + nstoken_client = open_netns(CLIENT_NS); |
| 171 | + if (!ASSERT_OK_PTR(nstoken_client, "open client ns")) |
| 172 | + goto fail_delete_server_ns; |
| 173 | + SYS(fail_close_client_ns, "ip link add veth1 type veth peer name %s", |
| 174 | + "veth2 netns " SERVER_NS); |
| 175 | + SYS(fail_close_client_ns, "ip -4 addr add " IP4_ADDR_VETH1 "/24 dev veth1"); |
| 176 | + SYS(fail_close_client_ns, "ip link set veth1 up"); |
| 177 | + SYS(fail_close_client_ns, "tc qdisc add dev veth1 root fq"); |
| 178 | + ret = tc_prog_attach("veth1", -1, bpf_program__fd(skel->progs.tc_prog)); |
| 179 | + if (!ASSERT_OK(ret, "attach bpf prog")) |
| 180 | + goto fail_close_client_ns; |
| 181 | + |
| 182 | + nstoken_server = open_netns(SERVER_NS); |
| 183 | + if (!ASSERT_OK_PTR(nstoken_server, "enter server ns")) |
| 184 | + goto fail_close_client_ns; |
| 185 | + SYS(fail_close_server_ns, "ip -4 addr add " IP4_ADDR_VETH2 "/24 dev veth2"); |
| 186 | + SYS(fail_close_server_ns, "ip link set veth2 up"); |
| 187 | + close_netns(nstoken_server); |
| 188 | + close_netns(nstoken_client); |
| 189 | + |
| 190 | + return 0; |
| 191 | + |
| 192 | +fail_close_server_ns: |
| 193 | + close_netns(nstoken_server); |
| 194 | +fail_close_client_ns: |
| 195 | + close_netns(nstoken_client); |
| 196 | +fail_delete_server_ns: |
| 197 | + remove_netns(SERVER_NS); |
| 198 | +fail_delete_client_ns: |
| 199 | + remove_netns(CLIENT_NS); |
| 200 | +fail: |
| 201 | + return -1; |
| 202 | +} |
| 203 | + |
| 204 | +static void cleanup(void) |
| 205 | +{ |
| 206 | + remove_netns(CLIENT_NS); |
| 207 | + remove_netns(SERVER_NS); |
| 208 | +} |
| 209 | + |
| 210 | +static void run_test(void) |
| 211 | +{ |
| 212 | + __u64 rx_bytes_start, rx_bytes_end; |
| 213 | + double rate_mbps, rate_error; |
| 214 | + pthread_t server_thread = 0; |
| 215 | + struct connection *conn; |
| 216 | + __u64 ts_start, ts_end; |
| 217 | + int ret; |
| 218 | + |
| 219 | + |
| 220 | + conn = setup_connection(); |
| 221 | + if (!ASSERT_OK_PTR(conn, "setup client and server connection")) |
| 222 | + return; |
| 223 | + |
| 224 | + ret = pthread_create(&server_thread, NULL, run_server, |
| 225 | + (void *)(&conn->server_conn_fd)); |
| 226 | + if (!ASSERT_OK(ret, "start server rx thread")) |
| 227 | + goto end_cleanup_conn; |
| 228 | + if (!ASSERT_OK(read_rx_bytes(&rx_bytes_start), "read rx_bytes")) |
| 229 | + goto end_kill_thread; |
| 230 | + ts_start = get_time_ns(); |
| 231 | + while (true) { |
| 232 | + send(conn->client_conn_fd, (void *)tx_buffer, BUFFER_LEN, 0); |
| 233 | + ts_end = get_time_ns(); |
| 234 | + if ((ts_end - ts_start)/100000 >= TIMEOUT_MS) { |
| 235 | + tx_timeout = true; |
| 236 | + ret = read_rx_bytes(&rx_bytes_end); |
| 237 | + if (!ASSERT_OK(ret, "read_rx_bytes")) |
| 238 | + goto end_cleanup_conn; |
| 239 | + break; |
| 240 | + } |
| 241 | + } |
| 242 | + |
| 243 | + rate_mbps = (rx_bytes_end - rx_bytes_start) / |
| 244 | + ((ts_end - ts_start) / 1000.0); |
| 245 | + rate_error = |
| 246 | + fabs((rate_mbps - TARGET_RATE_MBPS) * 100.0 / TARGET_RATE_MBPS); |
| 247 | + fprintf(stderr, "Rate:\t%f\nError:\t%f\n", rate_mbps, rate_error); |
| 248 | + |
| 249 | + ASSERT_LE(rate_error, RATE_ERROR_PERCENT, |
| 250 | + "rate error is lower than threshold"); |
| 251 | + |
| 252 | +end_kill_thread: |
| 253 | + tx_timeout = true; |
| 254 | +end_cleanup_conn: |
| 255 | + cleanup_connection(conn); |
| 256 | +} |
| 257 | + |
| 258 | +void test_tc_edt(void) |
| 259 | +{ |
| 260 | + struct test_tc_edt *skel; |
| 261 | + |
| 262 | + skel = test_tc_edt__open_and_load(); |
| 263 | + if (!ASSERT_OK_PTR(skel, "skel open and load")) |
| 264 | + return; |
| 265 | + |
| 266 | + if (!ASSERT_OK(setup(skel), "global setup")) |
| 267 | + return; |
| 268 | + |
| 269 | + run_test(); |
| 270 | + |
| 271 | + cleanup(); |
| 272 | + test_tc_edt__destroy(skel); |
| 273 | +} |
0 commit comments