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>
8 #include <sys/socket.h>
9 #include <netinet/in.h>
10 #include <netinet/tcp.h>
12 #include <arpa/inet.h>
14 #include <bpf/libbpf.h>
17 #include <network_helpers.h>
19 #include "xdp_features.skel.h"
20 #include "xdp_features.h"
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"
28 char ifname
[IF_NAMESIZE
];
32 enum netdev_xdp_act drv_feature
;
33 enum xdp_action action
;
35 struct sockaddr_storage dut_ctrl_addr
;
36 struct sockaddr_storage dut_addr
;
37 struct sockaddr_storage tester_addr
;
42 void test__fail(void) { /* for network_helpers.c */ }
44 static int libbpf_print_fn(enum libbpf_print_level level
,
45 const char *format
, va_list args
)
47 if (level
== LIBBPF_DEBUG
&& !env
.verbosity
)
49 return vfprintf(stderr
, format
, args
);
52 static volatile bool exiting
;
54 static void sig_handler(int sig
)
59 const char *argp_program_version
= "xdp-features 0.0";
60 const char argp_program_doc
[] =
61 "XDP features detection application.\n"
63 "XDP features application checks the XDP advertised features match detected ones.\n"
65 "USAGE: ./xdp-features [-vt] [-f <xdp-feature>] [-D <dut-data-ip>] [-T <tester-data-ip>] [-C <dut-ctrl-ip>] <iface-name>\n"
67 "dut-data-ip, tester-data-ip, dut-ctrl-ip: IPv6 or IPv4-mapped-IPv6 addresses;\n"
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" },
87 static int get_xdp_feature(const char *arg
)
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
;
113 static char *get_xdp_feature_str(void)
115 switch (env
.feature
.action
) {
117 return YELLOW("XDP_PASS");
119 return YELLOW("XDP_DROP");
121 return YELLOW("XDP_ABORTED");
123 return YELLOW("XDP_TX");
125 return YELLOW("XDP_REDIRECT");
130 if (env
.feature
.drv_feature
== NETDEV_XDP_ACT_NDO_XMIT
)
131 return YELLOW("XDP_NDO_XMIT");
136 static error_t
parse_arg(int key
, char *arg
, struct argp_state
*state
)
140 env
.verbosity
= true;
143 env
.is_tester
= true;
146 if (get_xdp_feature(arg
) < 0) {
147 fprintf(stderr
, "Invalid xdp feature: %s\n", arg
);
149 return ARGP_ERR_UNKNOWN
;
153 if (make_sockaddr(AF_INET6
, arg
, DUT_ECHO_PORT
,
154 &env
.dut_addr
, NULL
)) {
156 "Invalid address assigned to the Device Under Test: %s\n",
158 return ARGP_ERR_UNKNOWN
;
162 if (make_sockaddr(AF_INET6
, arg
, DUT_CTRL_PORT
,
163 &env
.dut_ctrl_addr
, NULL
)) {
165 "Invalid address assigned to the Device Under Test: %s\n",
167 return ARGP_ERR_UNKNOWN
;
171 if (make_sockaddr(AF_INET6
, arg
, 0, &env
.tester_addr
, NULL
)) {
173 "Invalid address assigned to the Tester device: %s\n",
175 return ARGP_ERR_UNKNOWN
;
180 if (strlen(arg
) >= IF_NAMESIZE
) {
181 fprintf(stderr
, "Invalid device name: %s\n", arg
);
183 return ARGP_ERR_UNKNOWN
;
186 env
.ifindex
= if_nametoindex(arg
);
188 env
.ifindex
= strtoul(arg
, NULL
, 0);
189 if (!env
.ifindex
|| !if_indextoname(env
.ifindex
, env
.ifname
)) {
191 "Bad interface index or name (%d): %s\n",
192 errno
, strerror(errno
));
194 return ARGP_ERR_UNKNOWN
;
198 return ARGP_ERR_UNKNOWN
;
204 static const struct argp argp
= {
207 .doc
= argp_program_doc
,
210 static void set_env_default(void)
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
, "::ffff:127.0.0.1", DUT_CTRL_PORT
,
217 &env
.dut_ctrl_addr
, NULL
);
218 make_sockaddr(AF_INET6
, "::ffff:127.0.0.1", DUT_ECHO_PORT
,
219 &env
.dut_addr
, NULL
);
220 make_sockaddr(AF_INET6
, "::ffff:127.0.0.1", 0, &env
.tester_addr
, NULL
);
223 static void *dut_echo_thread(void *arg
)
225 unsigned char buf
[sizeof(struct tlv_hdr
)];
226 int sockfd
= *(int *)arg
;
229 struct tlv_hdr
*tlv
= (struct tlv_hdr
*)buf
;
230 struct sockaddr_storage addr
;
234 n
= recvfrom(sockfd
, buf
, sizeof(buf
), MSG_WAITALL
,
235 (struct sockaddr
*)&addr
, &addrlen
);
236 if (n
!= ntohs(tlv
->len
))
239 if (ntohs(tlv
->type
) != CMD_ECHO
)
242 sendto(sockfd
, buf
, sizeof(buf
), MSG_NOSIGNAL
| MSG_CONFIRM
,
243 (struct sockaddr
*)&addr
, addrlen
);
246 pthread_exit((void *)0);
252 static int dut_run_echo_thread(pthread_t
*t
, int *sockfd
)
256 sockfd
= start_reuseport_server(AF_INET6
, SOCK_DGRAM
, NULL
,
257 DUT_ECHO_PORT
, 0, 1);
260 "Failed creating data UDP socket on device %s\n",
265 /* start echo channel */
266 err
= pthread_create(t
, NULL
, dut_echo_thread
, sockfd
);
269 "Failed creating data UDP thread on device %s: %s\n",
270 env
.ifname
, strerror(-err
));
278 static int dut_attach_xdp_prog(struct xdp_features
*skel
, int flags
)
280 enum xdp_action action
= env
.feature
.action
;
281 struct bpf_program
*prog
;
282 unsigned int key
= 0;
285 if (env
.feature
.drv_feature
== NETDEV_XDP_ACT_NDO_XMIT
) {
286 struct bpf_devmap_val entry
= {
287 .ifindex
= env
.ifindex
,
290 err
= bpf_map__update_elem(skel
->maps
.dev_map
,
292 &entry
, sizeof(entry
), 0);
296 fd
= bpf_program__fd(skel
->progs
.xdp_do_redirect_cpumap
);
297 action
= XDP_REDIRECT
;
302 prog
= skel
->progs
.xdp_do_tx
;
305 prog
= skel
->progs
.xdp_do_drop
;
308 prog
= skel
->progs
.xdp_do_aborted
;
311 prog
= skel
->progs
.xdp_do_pass
;
314 struct bpf_cpumap_val entry
= {
319 err
= bpf_map__update_elem(skel
->maps
.cpu_map
,
321 &entry
, sizeof(entry
), 0);
325 prog
= skel
->progs
.xdp_do_redirect
;
332 err
= bpf_xdp_attach(env
.ifindex
, bpf_program__fd(prog
), flags
, NULL
);
334 fprintf(stderr
, "Failed attaching XDP program to device %s\n",
339 static int recv_msg(int sockfd
, void *buf
, size_t bufsize
, void *val
,
342 struct tlv_hdr
*tlv
= (struct tlv_hdr
*)buf
;
345 len
= recv(sockfd
, buf
, bufsize
, 0);
346 if (len
!= ntohs(tlv
->len
) || len
< sizeof(*tlv
))
354 memcpy(val
, tlv
->data
, len
);
360 static int dut_run(struct xdp_features
*skel
)
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;
368 sockfd
= start_reuseport_server(AF_INET6
, SOCK_STREAM
, NULL
,
369 DUT_CTRL_PORT
, 0, 1);
372 "Failed creating control socket on device %s\n", env
.ifname
);
376 ctrl_sockfd
= accept(*sockfd
, (struct sockaddr
*)&ctrl_addr
, &addrlen
);
377 if (ctrl_sockfd
< 0) {
379 "Failed accepting connections on device %s control socket\n",
387 unsigned char buf
[BUFSIZE
] = {};
388 struct tlv_hdr
*tlv
= (struct tlv_hdr
*)buf
;
390 err
= recv_msg(ctrl_sockfd
, buf
, BUFSIZE
, NULL
, 0);
394 switch (ntohs(tlv
->type
)) {
396 if (state
== CMD_START
)
400 /* Load the XDP program on the DUT */
401 err
= dut_attach_xdp_prog(skel
, flags
);
405 err
= dut_run_echo_thread(&dut_thread
, &echo_sockfd
);
409 tlv
->type
= htons(CMD_ACK
);
410 tlv
->len
= htons(sizeof(*tlv
));
411 err
= send(ctrl_sockfd
, buf
, sizeof(*tlv
), 0);
417 if (state
!= CMD_START
)
423 bpf_xdp_detach(env
.ifindex
, flags
, NULL
);
425 tlv
->type
= htons(CMD_ACK
);
426 tlv
->len
= htons(sizeof(*tlv
));
427 err
= send(ctrl_sockfd
, buf
, sizeof(*tlv
), 0);
429 case CMD_GET_XDP_CAP
: {
430 LIBBPF_OPTS(bpf_xdp_query_opts
, opts
);
431 unsigned long long val
;
434 err
= bpf_xdp_query(env
.ifindex
, XDP_FLAGS_DRV_MODE
,
438 "Failed querying XDP cap for device %s\n",
443 tlv
->type
= htons(CMD_ACK
);
444 n
= sizeof(*tlv
) + sizeof(opts
.feature_flags
);
447 val
= htobe64(opts
.feature_flags
);
448 memcpy(tlv
->data
, &val
, sizeof(val
));
450 err
= send(ctrl_sockfd
, buf
, n
, 0);
455 case CMD_GET_STATS
: {
456 unsigned int key
= 0, val
;
459 err
= bpf_map__lookup_elem(skel
->maps
.dut_stats
,
461 &val
, sizeof(val
), 0);
464 "bpf_map_lookup_elem failed (%d)\n", err
);
468 tlv
->type
= htons(CMD_ACK
);
469 n
= sizeof(*tlv
) + sizeof(val
);
473 memcpy(tlv
->data
, &val
, sizeof(val
));
475 err
= send(ctrl_sockfd
, buf
, n
, 0);
486 pthread_join(dut_thread
, NULL
);
488 bpf_xdp_detach(env
.ifindex
, flags
, NULL
);
495 static bool tester_collect_detected_cap(struct xdp_features
*skel
,
496 unsigned int dut_stats
)
498 unsigned int err
, key
= 0, val
;
503 err
= bpf_map__lookup_elem(skel
->maps
.stats
, &key
, sizeof(key
),
504 &val
, sizeof(val
), 0);
506 fprintf(stderr
, "bpf_map_lookup_elem failed (%d)\n", err
);
510 switch (env
.feature
.action
) {
522 if (env
.feature
.drv_feature
== NETDEV_XDP_ACT_NDO_XMIT
)
528 static int send_and_recv_msg(int sockfd
, enum test_commands cmd
, void *val
,
531 unsigned char buf
[BUFSIZE
] = {};
532 struct tlv_hdr
*tlv
= (struct tlv_hdr
*)buf
;
535 tlv
->type
= htons(cmd
);
536 tlv
->len
= htons(sizeof(*tlv
));
538 err
= send(sockfd
, buf
, sizeof(*tlv
), 0);
542 err
= recv_msg(sockfd
, buf
, BUFSIZE
, val
, val_size
);
546 return ntohs(tlv
->type
) == CMD_ACK
? 0 : -EINVAL
;
549 static int send_echo_msg(void)
551 unsigned char buf
[sizeof(struct tlv_hdr
)];
552 struct tlv_hdr
*tlv
= (struct tlv_hdr
*)buf
;
555 sockfd
= socket(AF_INET6
, SOCK_DGRAM
, 0);
558 "Failed creating data UDP socket on device %s\n",
563 tlv
->type
= htons(CMD_ECHO
);
564 tlv
->len
= htons(sizeof(*tlv
));
566 n
= sendto(sockfd
, buf
, sizeof(*tlv
), MSG_NOSIGNAL
| MSG_CONFIRM
,
567 (struct sockaddr
*)&env
.dut_addr
, sizeof(env
.dut_addr
));
570 return n
== ntohs(tlv
->len
) ? 0 : -EINVAL
;
573 static int tester_run(struct xdp_features
*skel
)
575 int flags
= XDP_FLAGS_UPDATE_IF_NOEXIST
| XDP_FLAGS_DRV_MODE
;
576 unsigned long long advertised_feature
;
577 struct bpf_program
*prog
;
582 sockfd
= socket(AF_INET6
, SOCK_STREAM
, 0);
585 "Failed creating tester service control socket\n");
589 if (settimeo(sockfd
, 1000) < 0)
592 err
= connect(sockfd
, (struct sockaddr
*)&env
.dut_ctrl_addr
,
593 sizeof(env
.dut_ctrl_addr
));
596 "Failed connecting to the Device Under Test control socket\n");
600 err
= send_and_recv_msg(sockfd
, CMD_GET_XDP_CAP
, &advertised_feature
,
601 sizeof(advertised_feature
));
607 advertised_feature
= be64toh(advertised_feature
);
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
;
613 prog
= skel
->progs
.xdp_tester_check_rx
;
615 err
= bpf_xdp_attach(env
.ifindex
, bpf_program__fd(prog
), flags
, NULL
);
617 fprintf(stderr
, "Failed attaching XDP program to device %s\n",
622 err
= send_and_recv_msg(sockfd
, CMD_START
, NULL
, 0);
626 for (i
= 0; i
< 10 && !exiting
; i
++) {
627 err
= send_echo_msg();
634 err
= send_and_recv_msg(sockfd
, CMD_GET_STATS
, &stats
, sizeof(stats
));
639 err
= send_and_recv_msg(sockfd
, CMD_STOP
, NULL
, 0);
640 /* send a new echo message to wake echo thread of the dut */
643 detected_cap
= tester_collect_detected_cap(skel
, ntohl(stats
));
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"));
650 bpf_xdp_detach(env
.ifindex
, flags
, NULL
);
652 return err
< 0 ? err
: 0;
655 int main(int argc
, char **argv
)
657 struct xdp_features
*skel
;
660 libbpf_set_strict_mode(LIBBPF_STRICT_ALL
);
661 libbpf_set_print(libbpf_print_fn
);
663 signal(SIGINT
, sig_handler
);
664 signal(SIGTERM
, sig_handler
);
668 /* Parse command line arguments */
669 err
= argp_parse(&argp
, argc
, argv
, 0, NULL
, NULL
);
673 if (env
.ifindex
< 0) {
674 fprintf(stderr
, "Invalid device name %s\n", env
.ifname
);
678 /* Load and verify BPF application */
679 skel
= xdp_features__open();
681 fprintf(stderr
, "Failed to open and load BPF skeleton\n");
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
;
690 /* Load & verify BPF programs */
691 err
= xdp_features__load(skel
);
693 fprintf(stderr
, "Failed to load and verify BPF skeleton\n");
697 err
= xdp_features__attach(skel
);
699 fprintf(stderr
, "Failed to attach BPF skeleton\n");
705 fprintf(stdout
, "Starting tester service on device %s\n",
707 err
= tester_run(skel
);
710 fprintf(stdout
, "Starting test on device %s\n", env
.ifname
);
715 xdp_features__destroy(skel
);
717 return err
< 0 ? -err
: 0;