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