1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <uapi/linux/bpf.h> |
3 | #include <uapi/linux/netdev.h> |
4 | #include <linux/if_link.h> |
5 | #include <signal.h> |
6 | #include <argp.h> |
7 | #include <net/if.h> |
8 | #include <sys/socket.h> |
9 | #include <netinet/in.h> |
10 | #include <netinet/tcp.h> |
11 | #include <unistd.h> |
12 | #include <arpa/inet.h> |
13 | #include <bpf/bpf.h> |
14 | #include <bpf/libbpf.h> |
15 | #include <pthread.h> |
16 | |
17 | #include <network_helpers.h> |
18 | |
19 | #include "xdp_features.skel.h" |
20 | #include "xdp_features.h" |
21 | |
22 | #define RED(str) "\033[0;31m" str "\033[0m" |
23 | #define GREEN(str) "\033[0;32m" str "\033[0m" |
24 | #define YELLOW(str) "\033[0;33m" str "\033[0m" |
25 | |
26 | static struct env { |
27 | bool verbosity; |
28 | char ifname[IF_NAMESIZE]; |
29 | int ifindex; |
30 | bool is_tester; |
31 | struct { |
32 | enum netdev_xdp_act drv_feature; |
33 | enum xdp_action action; |
34 | } feature; |
35 | struct sockaddr_storage dut_ctrl_addr; |
36 | struct sockaddr_storage dut_addr; |
37 | struct sockaddr_storage tester_addr; |
38 | } env; |
39 | |
40 | #define BUFSIZE 128 |
41 | |
42 | void test__fail(void) { /* for network_helpers.c */ } |
43 | |
44 | static int libbpf_print_fn(enum libbpf_print_level level, |
45 | const char *format, va_list args) |
46 | { |
47 | if (level == LIBBPF_DEBUG && !env.verbosity) |
48 | return 0; |
49 | return vfprintf(stderr, format, args); |
50 | } |
51 | |
52 | static volatile bool exiting; |
53 | |
54 | static void sig_handler(int sig) |
55 | { |
56 | exiting = true; |
57 | } |
58 | |
59 | const char *argp_program_version = "xdp-features 0.0" ; |
60 | const char argp_program_doc[] = |
61 | "XDP features detection application.\n" |
62 | "\n" |
63 | "XDP features application checks the XDP advertised features match detected ones.\n" |
64 | "\n" |
65 | "USAGE: ./xdp-features [-vt] [-f <xdp-feature>] [-D <dut-data-ip>] [-T <tester-data-ip>] [-C <dut-ctrl-ip>] <iface-name>\n" |
66 | "\n" |
67 | "dut-data-ip, tester-data-ip, dut-ctrl-ip: IPv6 or IPv4-mapped-IPv6 addresses;\n" |
68 | "\n" |
69 | "XDP features\n:" |
70 | "- XDP_PASS\n" |
71 | "- XDP_DROP\n" |
72 | "- XDP_ABORTED\n" |
73 | "- XDP_REDIRECT\n" |
74 | "- XDP_NDO_XMIT\n" |
75 | "- XDP_TX\n" ; |
76 | |
77 | static const struct argp_option opts[] = { |
78 | { "verbose" , 'v', NULL, 0, "Verbose debug output" }, |
79 | { "tester" , 't', NULL, 0, "Tester mode" }, |
80 | { "feature" , 'f', "XDP-FEATURE" , 0, "XDP feature to test" }, |
81 | { "dut_data_ip" , 'D', "DUT-DATA-IP" , 0, "DUT IP data channel" }, |
82 | { "dut_ctrl_ip" , 'C', "DUT-CTRL-IP" , 0, "DUT IP control channel" }, |
83 | { "tester_data_ip" , 'T', "TESTER-DATA-IP" , 0, "Tester IP data channel" }, |
84 | {}, |
85 | }; |
86 | |
87 | static int get_xdp_feature(const char *arg) |
88 | { |
89 | if (!strcmp(arg, "XDP_PASS" )) { |
90 | env.feature.action = XDP_PASS; |
91 | env.feature.drv_feature = NETDEV_XDP_ACT_BASIC; |
92 | } else if (!strcmp(arg, "XDP_DROP" )) { |
93 | env.feature.drv_feature = NETDEV_XDP_ACT_BASIC; |
94 | env.feature.action = XDP_DROP; |
95 | } else if (!strcmp(arg, "XDP_ABORTED" )) { |
96 | env.feature.drv_feature = NETDEV_XDP_ACT_BASIC; |
97 | env.feature.action = XDP_ABORTED; |
98 | } else if (!strcmp(arg, "XDP_TX" )) { |
99 | env.feature.drv_feature = NETDEV_XDP_ACT_BASIC; |
100 | env.feature.action = XDP_TX; |
101 | } else if (!strcmp(arg, "XDP_REDIRECT" )) { |
102 | env.feature.drv_feature = NETDEV_XDP_ACT_REDIRECT; |
103 | env.feature.action = XDP_REDIRECT; |
104 | } else if (!strcmp(arg, "XDP_NDO_XMIT" )) { |
105 | env.feature.drv_feature = NETDEV_XDP_ACT_NDO_XMIT; |
106 | } else { |
107 | return -EINVAL; |
108 | } |
109 | |
110 | return 0; |
111 | } |
112 | |
113 | static char *get_xdp_feature_str(void) |
114 | { |
115 | switch (env.feature.action) { |
116 | case XDP_PASS: |
117 | return YELLOW("XDP_PASS" ); |
118 | case XDP_DROP: |
119 | return YELLOW("XDP_DROP" ); |
120 | case XDP_ABORTED: |
121 | return YELLOW("XDP_ABORTED" ); |
122 | case XDP_TX: |
123 | return YELLOW("XDP_TX" ); |
124 | case XDP_REDIRECT: |
125 | return YELLOW("XDP_REDIRECT" ); |
126 | default: |
127 | break; |
128 | } |
129 | |
130 | if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT) |
131 | return YELLOW("XDP_NDO_XMIT" ); |
132 | |
133 | return "" ; |
134 | } |
135 | |
136 | static error_t parse_arg(int key, char *arg, struct argp_state *state) |
137 | { |
138 | switch (key) { |
139 | case 'v': |
140 | env.verbosity = true; |
141 | break; |
142 | case 't': |
143 | env.is_tester = true; |
144 | break; |
145 | case 'f': |
146 | if (get_xdp_feature(arg) < 0) { |
147 | fprintf(stderr, "Invalid xdp feature: %s\n" , arg); |
148 | argp_usage(state); |
149 | return ARGP_ERR_UNKNOWN; |
150 | } |
151 | break; |
152 | case 'D': |
153 | if (make_sockaddr(AF_INET6, addr_str: arg, DUT_ECHO_PORT, |
154 | addr: &env.dut_addr, NULL)) { |
155 | fprintf(stderr, |
156 | "Invalid address assigned to the Device Under Test: %s\n" , |
157 | arg); |
158 | return ARGP_ERR_UNKNOWN; |
159 | } |
160 | break; |
161 | case 'C': |
162 | if (make_sockaddr(AF_INET6, addr_str: arg, DUT_CTRL_PORT, |
163 | addr: &env.dut_ctrl_addr, NULL)) { |
164 | fprintf(stderr, |
165 | "Invalid address assigned to the Device Under Test: %s\n" , |
166 | arg); |
167 | return ARGP_ERR_UNKNOWN; |
168 | } |
169 | break; |
170 | case 'T': |
171 | if (make_sockaddr(AF_INET6, addr_str: arg, port: 0, addr: &env.tester_addr, NULL)) { |
172 | fprintf(stderr, |
173 | "Invalid address assigned to the Tester device: %s\n" , |
174 | arg); |
175 | return ARGP_ERR_UNKNOWN; |
176 | } |
177 | break; |
178 | case ARGP_KEY_ARG: |
179 | errno = 0; |
180 | if (strlen(arg) >= IF_NAMESIZE) { |
181 | fprintf(stderr, "Invalid device name: %s\n" , arg); |
182 | argp_usage(state); |
183 | return ARGP_ERR_UNKNOWN; |
184 | } |
185 | |
186 | env.ifindex = if_nametoindex(arg); |
187 | if (!env.ifindex) |
188 | env.ifindex = strtoul(arg, NULL, 0); |
189 | if (!env.ifindex || !if_indextoname(env.ifindex, env.ifname)) { |
190 | fprintf(stderr, |
191 | "Bad interface index or name (%d): %s\n" , |
192 | errno, strerror(errno)); |
193 | argp_usage(state); |
194 | return ARGP_ERR_UNKNOWN; |
195 | } |
196 | break; |
197 | default: |
198 | return ARGP_ERR_UNKNOWN; |
199 | } |
200 | |
201 | return 0; |
202 | } |
203 | |
204 | static const struct argp argp = { |
205 | .options = opts, |
206 | .parser = parse_arg, |
207 | .doc = argp_program_doc, |
208 | }; |
209 | |
210 | static void set_env_default(void) |
211 | { |
212 | env.feature.drv_feature = NETDEV_XDP_ACT_NDO_XMIT; |
213 | env.feature.action = -EINVAL; |
214 | env.ifindex = -ENODEV; |
215 | strcpy(env.ifname, "unknown" ); |
216 | make_sockaddr(AF_INET6, addr_str: "::ffff:127.0.0.1" , DUT_CTRL_PORT, |
217 | addr: &env.dut_ctrl_addr, NULL); |
218 | make_sockaddr(AF_INET6, addr_str: "::ffff:127.0.0.1" , DUT_ECHO_PORT, |
219 | addr: &env.dut_addr, NULL); |
220 | make_sockaddr(AF_INET6, addr_str: "::ffff:127.0.0.1" , port: 0, addr: &env.tester_addr, NULL); |
221 | } |
222 | |
223 | static void *dut_echo_thread(void *arg) |
224 | { |
225 | unsigned char buf[sizeof(struct tlv_hdr)]; |
226 | int sockfd = *(int *)arg; |
227 | |
228 | while (!exiting) { |
229 | struct tlv_hdr *tlv = (struct tlv_hdr *)buf; |
230 | struct sockaddr_storage addr; |
231 | socklen_t addrlen; |
232 | size_t n; |
233 | |
234 | n = recvfrom(sockfd, buf, sizeof(buf), MSG_WAITALL, |
235 | (struct sockaddr *)&addr, &addrlen); |
236 | if (n != ntohs(tlv->len)) |
237 | continue; |
238 | |
239 | if (ntohs(tlv->type) != CMD_ECHO) |
240 | continue; |
241 | |
242 | sendto(sockfd, buf, sizeof(buf), MSG_NOSIGNAL | MSG_CONFIRM, |
243 | (struct sockaddr *)&addr, addrlen); |
244 | } |
245 | |
246 | pthread_exit((void *)0); |
247 | close(sockfd); |
248 | |
249 | return NULL; |
250 | } |
251 | |
252 | static int dut_run_echo_thread(pthread_t *t, int *sockfd) |
253 | { |
254 | int err; |
255 | |
256 | sockfd = start_reuseport_server(AF_INET6, type: SOCK_DGRAM, NULL, |
257 | DUT_ECHO_PORT, timeout_ms: 0, nr_listens: 1); |
258 | if (!sockfd) { |
259 | fprintf(stderr, |
260 | "Failed creating data UDP socket on device %s\n" , |
261 | env.ifname); |
262 | return -errno; |
263 | } |
264 | |
265 | /* start echo channel */ |
266 | err = pthread_create(t, NULL, dut_echo_thread, sockfd); |
267 | if (err) { |
268 | fprintf(stderr, |
269 | "Failed creating data UDP thread on device %s: %s\n" , |
270 | env.ifname, strerror(-err)); |
271 | free_fds(fds: sockfd, nr_close_fds: 1); |
272 | return -EINVAL; |
273 | } |
274 | |
275 | return 0; |
276 | } |
277 | |
278 | static int dut_attach_xdp_prog(struct xdp_features *skel, int flags) |
279 | { |
280 | enum xdp_action action = env.feature.action; |
281 | struct bpf_program *prog; |
282 | unsigned int key = 0; |
283 | int err, fd = 0; |
284 | |
285 | if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT) { |
286 | struct bpf_devmap_val entry = { |
287 | .ifindex = env.ifindex, |
288 | }; |
289 | |
290 | err = bpf_map__update_elem(skel->maps.dev_map, |
291 | &key, sizeof(key), |
292 | &entry, sizeof(entry), 0); |
293 | if (err < 0) |
294 | return err; |
295 | |
296 | fd = bpf_program__fd(skel->progs.xdp_do_redirect_cpumap); |
297 | action = XDP_REDIRECT; |
298 | } |
299 | |
300 | switch (action) { |
301 | case XDP_TX: |
302 | prog = skel->progs.xdp_do_tx; |
303 | break; |
304 | case XDP_DROP: |
305 | prog = skel->progs.xdp_do_drop; |
306 | break; |
307 | case XDP_ABORTED: |
308 | prog = skel->progs.xdp_do_aborted; |
309 | break; |
310 | case XDP_PASS: |
311 | prog = skel->progs.xdp_do_pass; |
312 | break; |
313 | case XDP_REDIRECT: { |
314 | struct bpf_cpumap_val entry = { |
315 | .qsize = 2048, |
316 | .bpf_prog.fd = fd, |
317 | }; |
318 | |
319 | err = bpf_map__update_elem(skel->maps.cpu_map, |
320 | &key, sizeof(key), |
321 | &entry, sizeof(entry), 0); |
322 | if (err < 0) |
323 | return err; |
324 | |
325 | prog = skel->progs.xdp_do_redirect; |
326 | break; |
327 | } |
328 | default: |
329 | return -EINVAL; |
330 | } |
331 | |
332 | err = bpf_xdp_attach(env.ifindex, bpf_program__fd(prog), flags, NULL); |
333 | if (err) |
334 | fprintf(stderr, "Failed attaching XDP program to device %s\n" , |
335 | env.ifname); |
336 | return err; |
337 | } |
338 | |
339 | static int recv_msg(int sockfd, void *buf, size_t bufsize, void *val, |
340 | size_t val_size) |
341 | { |
342 | struct tlv_hdr *tlv = (struct tlv_hdr *)buf; |
343 | size_t len; |
344 | |
345 | len = recv(sockfd, buf, bufsize, 0); |
346 | if (len != ntohs(tlv->len) || len < sizeof(*tlv)) |
347 | return -EINVAL; |
348 | |
349 | if (val) { |
350 | len -= sizeof(*tlv); |
351 | if (len > val_size) |
352 | return -ENOMEM; |
353 | |
354 | memcpy(val, tlv->data, len); |
355 | } |
356 | |
357 | return 0; |
358 | } |
359 | |
360 | static int dut_run(struct xdp_features *skel) |
361 | { |
362 | int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE; |
363 | int state, err = 0, *sockfd, ctrl_sockfd, echo_sockfd; |
364 | struct sockaddr_storage ctrl_addr; |
365 | pthread_t dut_thread = 0; |
366 | socklen_t addrlen; |
367 | |
368 | sockfd = start_reuseport_server(AF_INET6, type: SOCK_STREAM, NULL, |
369 | DUT_CTRL_PORT, timeout_ms: 0, nr_listens: 1); |
370 | if (!sockfd) { |
371 | fprintf(stderr, |
372 | "Failed creating control socket on device %s\n" , env.ifname); |
373 | return -errno; |
374 | } |
375 | |
376 | ctrl_sockfd = accept(*sockfd, (struct sockaddr *)&ctrl_addr, &addrlen); |
377 | if (ctrl_sockfd < 0) { |
378 | fprintf(stderr, |
379 | "Failed accepting connections on device %s control socket\n" , |
380 | env.ifname); |
381 | free_fds(fds: sockfd, nr_close_fds: 1); |
382 | return -errno; |
383 | } |
384 | |
385 | /* CTRL loop */ |
386 | while (!exiting) { |
387 | unsigned char buf[BUFSIZE] = {}; |
388 | struct tlv_hdr *tlv = (struct tlv_hdr *)buf; |
389 | |
390 | err = recv_msg(sockfd: ctrl_sockfd, buf, BUFSIZE, NULL, val_size: 0); |
391 | if (err) |
392 | continue; |
393 | |
394 | switch (ntohs(tlv->type)) { |
395 | case CMD_START: { |
396 | if (state == CMD_START) |
397 | continue; |
398 | |
399 | state = CMD_START; |
400 | /* Load the XDP program on the DUT */ |
401 | err = dut_attach_xdp_prog(skel, flags); |
402 | if (err) |
403 | goto out; |
404 | |
405 | err = dut_run_echo_thread(&dut_thread, &echo_sockfd); |
406 | if (err < 0) |
407 | goto out; |
408 | |
409 | tlv->type = htons(CMD_ACK); |
410 | tlv->len = htons(sizeof(*tlv)); |
411 | err = send(ctrl_sockfd, buf, sizeof(*tlv), 0); |
412 | if (err < 0) |
413 | goto end_thread; |
414 | break; |
415 | } |
416 | case CMD_STOP: |
417 | if (state != CMD_START) |
418 | break; |
419 | |
420 | state = CMD_STOP; |
421 | |
422 | exiting = true; |
423 | bpf_xdp_detach(env.ifindex, flags, NULL); |
424 | |
425 | tlv->type = htons(CMD_ACK); |
426 | tlv->len = htons(sizeof(*tlv)); |
427 | err = send(ctrl_sockfd, buf, sizeof(*tlv), 0); |
428 | goto end_thread; |
429 | case CMD_GET_XDP_CAP: { |
430 | LIBBPF_OPTS(bpf_xdp_query_opts, opts); |
431 | unsigned long long val; |
432 | size_t n; |
433 | |
434 | err = bpf_xdp_query(env.ifindex, XDP_FLAGS_DRV_MODE, |
435 | &opts); |
436 | if (err) { |
437 | fprintf(stderr, |
438 | "Failed querying XDP cap for device %s\n" , |
439 | env.ifname); |
440 | goto end_thread; |
441 | } |
442 | |
443 | tlv->type = htons(CMD_ACK); |
444 | n = sizeof(*tlv) + sizeof(opts.feature_flags); |
445 | tlv->len = htons(n); |
446 | |
447 | val = htobe64(opts.feature_flags); |
448 | memcpy(tlv->data, &val, sizeof(val)); |
449 | |
450 | err = send(ctrl_sockfd, buf, n, 0); |
451 | if (err < 0) |
452 | goto end_thread; |
453 | break; |
454 | } |
455 | case CMD_GET_STATS: { |
456 | unsigned int key = 0, val; |
457 | size_t n; |
458 | |
459 | err = bpf_map__lookup_elem(skel->maps.dut_stats, |
460 | &key, sizeof(key), |
461 | &val, sizeof(val), 0); |
462 | if (err) { |
463 | fprintf(stderr, |
464 | "bpf_map_lookup_elem failed (%d)\n" , err); |
465 | goto end_thread; |
466 | } |
467 | |
468 | tlv->type = htons(CMD_ACK); |
469 | n = sizeof(*tlv) + sizeof(val); |
470 | tlv->len = htons(n); |
471 | |
472 | val = htonl(val); |
473 | memcpy(tlv->data, &val, sizeof(val)); |
474 | |
475 | err = send(ctrl_sockfd, buf, n, 0); |
476 | if (err < 0) |
477 | goto end_thread; |
478 | break; |
479 | } |
480 | default: |
481 | break; |
482 | } |
483 | } |
484 | |
485 | end_thread: |
486 | pthread_join(dut_thread, NULL); |
487 | out: |
488 | bpf_xdp_detach(env.ifindex, flags, NULL); |
489 | close(ctrl_sockfd); |
490 | free_fds(fds: sockfd, nr_close_fds: 1); |
491 | |
492 | return err; |
493 | } |
494 | |
495 | static bool tester_collect_detected_cap(struct xdp_features *skel, |
496 | unsigned int dut_stats) |
497 | { |
498 | unsigned int err, key = 0, val; |
499 | |
500 | if (!dut_stats) |
501 | return false; |
502 | |
503 | err = bpf_map__lookup_elem(skel->maps.stats, &key, sizeof(key), |
504 | &val, sizeof(val), 0); |
505 | if (err) { |
506 | fprintf(stderr, "bpf_map_lookup_elem failed (%d)\n" , err); |
507 | return false; |
508 | } |
509 | |
510 | switch (env.feature.action) { |
511 | case XDP_PASS: |
512 | case XDP_TX: |
513 | case XDP_REDIRECT: |
514 | return val > 0; |
515 | case XDP_DROP: |
516 | case XDP_ABORTED: |
517 | return val == 0; |
518 | default: |
519 | break; |
520 | } |
521 | |
522 | if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT) |
523 | return val > 0; |
524 | |
525 | return false; |
526 | } |
527 | |
528 | static int send_and_recv_msg(int sockfd, enum test_commands cmd, void *val, |
529 | size_t val_size) |
530 | { |
531 | unsigned char buf[BUFSIZE] = {}; |
532 | struct tlv_hdr *tlv = (struct tlv_hdr *)buf; |
533 | int err; |
534 | |
535 | tlv->type = htons(cmd); |
536 | tlv->len = htons(sizeof(*tlv)); |
537 | |
538 | err = send(sockfd, buf, sizeof(*tlv), 0); |
539 | if (err < 0) |
540 | return err; |
541 | |
542 | err = recv_msg(sockfd, buf, BUFSIZE, val, val_size); |
543 | if (err < 0) |
544 | return err; |
545 | |
546 | return ntohs(tlv->type) == CMD_ACK ? 0 : -EINVAL; |
547 | } |
548 | |
549 | static int send_echo_msg(void) |
550 | { |
551 | unsigned char buf[sizeof(struct tlv_hdr)]; |
552 | struct tlv_hdr *tlv = (struct tlv_hdr *)buf; |
553 | int sockfd, n; |
554 | |
555 | sockfd = socket(AF_INET6, SOCK_DGRAM, 0); |
556 | if (sockfd < 0) { |
557 | fprintf(stderr, |
558 | "Failed creating data UDP socket on device %s\n" , |
559 | env.ifname); |
560 | return -errno; |
561 | } |
562 | |
563 | tlv->type = htons(CMD_ECHO); |
564 | tlv->len = htons(sizeof(*tlv)); |
565 | |
566 | n = sendto(sockfd, buf, sizeof(*tlv), MSG_NOSIGNAL | MSG_CONFIRM, |
567 | (struct sockaddr *)&env.dut_addr, sizeof(env.dut_addr)); |
568 | close(sockfd); |
569 | |
570 | return n == ntohs(tlv->len) ? 0 : -EINVAL; |
571 | } |
572 | |
573 | static int tester_run(struct xdp_features *skel) |
574 | { |
575 | int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE; |
576 | unsigned long long advertised_feature; |
577 | struct bpf_program *prog; |
578 | unsigned int stats; |
579 | int i, err, sockfd; |
580 | bool detected_cap; |
581 | |
582 | sockfd = socket(AF_INET6, SOCK_STREAM, 0); |
583 | if (sockfd < 0) { |
584 | fprintf(stderr, |
585 | "Failed creating tester service control socket\n" ); |
586 | return -errno; |
587 | } |
588 | |
589 | if (settimeo(fd: sockfd, timeout_ms: 1000) < 0) |
590 | return -EINVAL; |
591 | |
592 | err = connect(sockfd, (struct sockaddr *)&env.dut_ctrl_addr, |
593 | sizeof(env.dut_ctrl_addr)); |
594 | if (err) { |
595 | fprintf(stderr, |
596 | "Failed connecting to the Device Under Test control socket\n" ); |
597 | return -errno; |
598 | } |
599 | |
600 | err = send_and_recv_msg(sockfd, cmd: CMD_GET_XDP_CAP, val: &advertised_feature, |
601 | val_size: sizeof(advertised_feature)); |
602 | if (err < 0) { |
603 | close(sockfd); |
604 | return err; |
605 | } |
606 | |
607 | advertised_feature = be64toh(advertised_feature); |
608 | |
609 | if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT || |
610 | env.feature.action == XDP_TX) |
611 | prog = skel->progs.xdp_tester_check_tx; |
612 | else |
613 | prog = skel->progs.xdp_tester_check_rx; |
614 | |
615 | err = bpf_xdp_attach(env.ifindex, bpf_program__fd(prog), flags, NULL); |
616 | if (err) { |
617 | fprintf(stderr, "Failed attaching XDP program to device %s\n" , |
618 | env.ifname); |
619 | goto out; |
620 | } |
621 | |
622 | err = send_and_recv_msg(sockfd, cmd: CMD_START, NULL, val_size: 0); |
623 | if (err) |
624 | goto out; |
625 | |
626 | for (i = 0; i < 10 && !exiting; i++) { |
627 | err = send_echo_msg(); |
628 | if (err < 0) |
629 | goto out; |
630 | |
631 | sleep(1); |
632 | } |
633 | |
634 | err = send_and_recv_msg(sockfd, cmd: CMD_GET_STATS, val: &stats, val_size: sizeof(stats)); |
635 | if (err) |
636 | goto out; |
637 | |
638 | /* stop the test */ |
639 | err = send_and_recv_msg(sockfd, cmd: CMD_STOP, NULL, val_size: 0); |
640 | /* send a new echo message to wake echo thread of the dut */ |
641 | send_echo_msg(); |
642 | |
643 | detected_cap = tester_collect_detected_cap(skel, ntohl(stats)); |
644 | |
645 | fprintf(stdout, "Feature %s: [%s][%s]\n" , get_xdp_feature_str(), |
646 | detected_cap ? GREEN("DETECTED" ) : RED("NOT DETECTED" ), |
647 | env.feature.drv_feature & advertised_feature ? GREEN("ADVERTISED" ) |
648 | : RED("NOT ADVERTISED" )); |
649 | out: |
650 | bpf_xdp_detach(env.ifindex, flags, NULL); |
651 | close(sockfd); |
652 | return err < 0 ? err : 0; |
653 | } |
654 | |
655 | int main(int argc, char **argv) |
656 | { |
657 | struct xdp_features *skel; |
658 | int err; |
659 | |
660 | libbpf_set_strict_mode(LIBBPF_STRICT_ALL); |
661 | libbpf_set_print(libbpf_print_fn); |
662 | |
663 | signal(SIGINT, sig_handler); |
664 | signal(SIGTERM, sig_handler); |
665 | |
666 | set_env_default(); |
667 | |
668 | /* Parse command line arguments */ |
669 | err = argp_parse(&argp, argc, argv, 0, NULL, NULL); |
670 | if (err) |
671 | return err; |
672 | |
673 | if (env.ifindex < 0) { |
674 | fprintf(stderr, "Invalid device name %s\n" , env.ifname); |
675 | return -ENODEV; |
676 | } |
677 | |
678 | /* Load and verify BPF application */ |
679 | skel = xdp_features__open(); |
680 | if (!skel) { |
681 | fprintf(stderr, "Failed to open and load BPF skeleton\n" ); |
682 | return -EINVAL; |
683 | } |
684 | |
685 | skel->rodata->tester_addr = |
686 | ((struct sockaddr_in6 *)&env.tester_addr)->sin6_addr; |
687 | skel->rodata->dut_addr = |
688 | ((struct sockaddr_in6 *)&env.dut_addr)->sin6_addr; |
689 | |
690 | /* Load & verify BPF programs */ |
691 | err = xdp_features__load(skel); |
692 | if (err) { |
693 | fprintf(stderr, "Failed to load and verify BPF skeleton\n" ); |
694 | goto cleanup; |
695 | } |
696 | |
697 | err = xdp_features__attach(skel); |
698 | if (err) { |
699 | fprintf(stderr, "Failed to attach BPF skeleton\n" ); |
700 | goto cleanup; |
701 | } |
702 | |
703 | if (env.is_tester) { |
704 | /* Tester */ |
705 | fprintf(stdout, "Starting tester service on device %s\n" , |
706 | env.ifname); |
707 | err = tester_run(skel); |
708 | } else { |
709 | /* DUT */ |
710 | fprintf(stdout, "Starting test on device %s\n" , env.ifname); |
711 | err = dut_run(skel); |
712 | } |
713 | |
714 | cleanup: |
715 | xdp_features__destroy(skel); |
716 | |
717 | return err < 0 ? -err : 0; |
718 | } |
719 | |