Commit | Line | Data |
---|---|---|
4dba3e78 LB |
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; | |
27a36bc3 | 28 | char ifname[IF_NAMESIZE]; |
4dba3e78 LB |
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[] = | |
8306829b | 61 | "XDP features detection application.\n" |
4dba3e78 LB |
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, arg, DUT_ECHO_PORT, | |
154 | &env.dut_addr, NULL)) { | |
c1cd734c LB |
155 | fprintf(stderr, |
156 | "Invalid address assigned to the Device Under Test: %s\n", | |
157 | arg); | |
4dba3e78 LB |
158 | return ARGP_ERR_UNKNOWN; |
159 | } | |
160 | break; | |
161 | case 'C': | |
162 | if (make_sockaddr(AF_INET6, arg, DUT_CTRL_PORT, | |
163 | &env.dut_ctrl_addr, NULL)) { | |
c1cd734c LB |
164 | fprintf(stderr, |
165 | "Invalid address assigned to the Device Under Test: %s\n", | |
166 | arg); | |
4dba3e78 LB |
167 | return ARGP_ERR_UNKNOWN; |
168 | } | |
169 | break; | |
170 | case 'T': | |
171 | if (make_sockaddr(AF_INET6, arg, 0, &env.tester_addr, NULL)) { | |
c1cd734c LB |
172 | fprintf(stderr, |
173 | "Invalid address assigned to the Tester device: %s\n", | |
174 | arg); | |
4dba3e78 LB |
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); | |
27a36bc3 | 189 | if (!env.ifindex || !if_indextoname(env.ifindex, env.ifname)) { |
4dba3e78 LB |
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; | |
27a36bc3 | 215 | strcpy(env.ifname, "unknown"); |
4dba3e78 LB |
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); | |
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, SOCK_DGRAM, NULL, | |
257 | DUT_ECHO_PORT, 0, 1); | |
258 | if (!sockfd) { | |
27a36bc3 LB |
259 | fprintf(stderr, |
260 | "Failed creating data UDP socket on device %s\n", | |
261 | env.ifname); | |
4dba3e78 LB |
262 | return -errno; |
263 | } | |
264 | ||
265 | /* start echo channel */ | |
266 | err = pthread_create(t, NULL, dut_echo_thread, sockfd); | |
267 | if (err) { | |
27a36bc3 LB |
268 | fprintf(stderr, |
269 | "Failed creating data UDP thread on device %s: %s\n", | |
270 | env.ifname, strerror(-err)); | |
4dba3e78 LB |
271 | free_fds(sockfd, 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) | |
27a36bc3 LB |
334 | fprintf(stderr, "Failed attaching XDP program to device %s\n", |
335 | env.ifname); | |
4dba3e78 LB |
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; | |
925a0157 | 363 | int state, err = 0, *sockfd, ctrl_sockfd, echo_sockfd; |
4dba3e78 | 364 | struct sockaddr_storage ctrl_addr; |
925a0157 | 365 | pthread_t dut_thread = 0; |
4dba3e78 LB |
366 | socklen_t addrlen; |
367 | ||
368 | sockfd = start_reuseport_server(AF_INET6, SOCK_STREAM, NULL, | |
369 | DUT_CTRL_PORT, 0, 1); | |
370 | if (!sockfd) { | |
27a36bc3 LB |
371 | fprintf(stderr, |
372 | "Failed creating control socket on device %s\n", env.ifname); | |
4dba3e78 LB |
373 | return -errno; |
374 | } | |
375 | ||
376 | ctrl_sockfd = accept(*sockfd, (struct sockaddr *)&ctrl_addr, &addrlen); | |
377 | if (ctrl_sockfd < 0) { | |
27a36bc3 LB |
378 | fprintf(stderr, |
379 | "Failed accepting connections on device %s control socket\n", | |
380 | env.ifname); | |
4dba3e78 LB |
381 | free_fds(sockfd, 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(ctrl_sockfd, buf, BUFSIZE, NULL, 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, | |
27a36bc3 LB |
438 | "Failed querying XDP cap for device %s\n", |
439 | env.ifname); | |
4dba3e78 LB |
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) { | |
c1cd734c LB |
463 | fprintf(stderr, |
464 | "bpf_map_lookup_elem failed (%d)\n", err); | |
4dba3e78 LB |
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(sockfd, 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) { | |
c1cd734c | 506 | fprintf(stderr, "bpf_map_lookup_elem failed (%d)\n", err); |
4dba3e78 LB |
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) { | |
27a36bc3 LB |
557 | fprintf(stderr, |
558 | "Failed creating data UDP socket on device %s\n", | |
559 | env.ifname); | |
4dba3e78 LB |
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) { | |
c1cd734c LB |
584 | fprintf(stderr, |
585 | "Failed creating tester service control socket\n"); | |
4dba3e78 LB |
586 | return -errno; |
587 | } | |
588 | ||
589 | if (settimeo(sockfd, 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) { | |
c1cd734c LB |
595 | fprintf(stderr, |
596 | "Failed connecting to the Device Under Test control socket\n"); | |
4dba3e78 LB |
597 | return -errno; |
598 | } | |
599 | ||
600 | err = send_and_recv_msg(sockfd, CMD_GET_XDP_CAP, &advertised_feature, | |
601 | 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) { | |
27a36bc3 LB |
617 | fprintf(stderr, "Failed attaching XDP program to device %s\n", |
618 | env.ifname); | |
4dba3e78 LB |
619 | goto out; |
620 | } | |
621 | ||
622 | err = send_and_recv_msg(sockfd, CMD_START, NULL, 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_GET_STATS, &stats, sizeof(stats)); | |
635 | if (err) | |
636 | goto out; | |
637 | ||
638 | /* stop the test */ | |
639 | err = send_and_recv_msg(sockfd, CMD_STOP, NULL, 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) { | |
27a36bc3 | 674 | fprintf(stderr, "Invalid device name %s\n", env.ifname); |
4dba3e78 LB |
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 */ | |
27a36bc3 LB |
705 | fprintf(stdout, "Starting tester service on device %s\n", |
706 | env.ifname); | |
4dba3e78 LB |
707 | err = tester_run(skel); |
708 | } else { | |
709 | /* DUT */ | |
27a36bc3 | 710 | fprintf(stdout, "Starting test on device %s\n", env.ifname); |
4dba3e78 LB |
711 | err = dut_run(skel); |
712 | } | |
713 | ||
714 | cleanup: | |
715 | xdp_features__destroy(skel); | |
716 | ||
717 | return err < 0 ? -err : 0; | |
718 | } |