1 // SPDX-License-Identifier: GPL-2.0-only 2 /* Copyright (C) 2017 Cavium, Inc. 3 */ 4 #include <linux/bpf.h> 5 #include <linux/netlink.h> 6 #include <linux/rtnetlink.h> 7 #include <assert.h> 8 #include <errno.h> 9 #include <signal.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <string.h> 13 #include <sys/socket.h> 14 #include <unistd.h> 15 #include <bpf/bpf.h> 16 #include <arpa/inet.h> 17 #include <fcntl.h> 18 #include <poll.h> 19 #include <net/if.h> 20 #include <netdb.h> 21 #include <sys/ioctl.h> 22 #include <sys/syscall.h> 23 #include "bpf_util.h" 24 #include <bpf/libbpf.h> 25 #include <sys/resource.h> 26 #include <libgen.h> 27 #include <getopt.h> 28 #include <pthread.h> 29 #include "xdp_sample_user.h" 30 #include "xdp_router_ipv4.skel.h" 31 32 static const char *__doc__ = 33 "XDP IPv4 router implementation\n" 34 "Usage: xdp_router_ipv4 <IFNAME-0> ... <IFNAME-N>\n"; 35 36 static char buf[8192]; 37 static int lpm_map_fd; 38 static int arp_table_map_fd; 39 static int exact_match_map_fd; 40 static int tx_port_map_fd; 41 42 static bool routes_thread_exit; 43 static int interval = 5; 44 45 static int mask = SAMPLE_RX_CNT | SAMPLE_REDIRECT_ERR_MAP_CNT | 46 SAMPLE_DEVMAP_XMIT_CNT_MULTI | SAMPLE_EXCEPTION_CNT; 47 48 DEFINE_SAMPLE_INIT(xdp_router_ipv4); 49 50 static const struct option long_options[] = { 51 { "help", no_argument, NULL, 'h' }, 52 { "skb-mode", no_argument, NULL, 'S' }, 53 { "force", no_argument, NULL, 'F' }, 54 { "interval", required_argument, NULL, 'i' }, 55 { "verbose", no_argument, NULL, 'v' }, 56 { "stats", no_argument, NULL, 's' }, 57 {} 58 }; 59 60 static int get_route_table(int rtm_family); 61 62 static int recv_msg(struct sockaddr_nl sock_addr, int sock) 63 { 64 struct nlmsghdr *nh; 65 int len, nll = 0; 66 char *buf_ptr; 67 68 buf_ptr = buf; 69 while (1) { 70 len = recv(sock, buf_ptr, sizeof(buf) - nll, 0); 71 if (len < 0) 72 return len; 73 74 nh = (struct nlmsghdr *)buf_ptr; 75 76 if (nh->nlmsg_type == NLMSG_DONE) 77 break; 78 buf_ptr += len; 79 nll += len; 80 if ((sock_addr.nl_groups & RTMGRP_NEIGH) == RTMGRP_NEIGH) 81 break; 82 83 if ((sock_addr.nl_groups & RTMGRP_IPV4_ROUTE) == RTMGRP_IPV4_ROUTE) 84 break; 85 } 86 return nll; 87 } 88 89 /* Function to parse the route entry returned by netlink 90 * Updates the route entry related map entries 91 */ 92 static void read_route(struct nlmsghdr *nh, int nll) 93 { 94 char dsts[24], gws[24], ifs[16], dsts_len[24], metrics[24]; 95 struct bpf_lpm_trie_key *prefix_key; 96 struct rtattr *rt_attr; 97 struct rtmsg *rt_msg; 98 int rtm_family; 99 int rtl; 100 int i; 101 struct route_table { 102 int dst_len, iface, metric; 103 __be32 dst, gw; 104 __be64 mac; 105 } route; 106 struct arp_table { 107 __be64 mac; 108 __be32 dst; 109 }; 110 111 struct direct_map { 112 struct arp_table arp; 113 int ifindex; 114 __be64 mac; 115 } direct_entry; 116 117 memset(&route, 0, sizeof(route)); 118 for (; NLMSG_OK(nh, nll); nh = NLMSG_NEXT(nh, nll)) { 119 rt_msg = (struct rtmsg *)NLMSG_DATA(nh); 120 rtm_family = rt_msg->rtm_family; 121 if (rtm_family == AF_INET) 122 if (rt_msg->rtm_table != RT_TABLE_MAIN) 123 continue; 124 rt_attr = (struct rtattr *)RTM_RTA(rt_msg); 125 rtl = RTM_PAYLOAD(nh); 126 127 for (; RTA_OK(rt_attr, rtl); rt_attr = RTA_NEXT(rt_attr, rtl)) { 128 switch (rt_attr->rta_type) { 129 case NDA_DST: 130 sprintf(dsts, "%u", 131 (*((__be32 *)RTA_DATA(rt_attr)))); 132 break; 133 case RTA_GATEWAY: 134 sprintf(gws, "%u", 135 *((__be32 *)RTA_DATA(rt_attr))); 136 break; 137 case RTA_OIF: 138 sprintf(ifs, "%u", 139 *((int *)RTA_DATA(rt_attr))); 140 break; 141 case RTA_METRICS: 142 sprintf(metrics, "%u", 143 *((int *)RTA_DATA(rt_attr))); 144 default: 145 break; 146 } 147 } 148 sprintf(dsts_len, "%d", rt_msg->rtm_dst_len); 149 route.dst = atoi(dsts); 150 route.dst_len = atoi(dsts_len); 151 route.gw = atoi(gws); 152 route.iface = atoi(ifs); 153 route.metric = atoi(metrics); 154 assert(get_mac_addr(route.iface, &route.mac) == 0); 155 assert(bpf_map_update_elem(tx_port_map_fd, 156 &route.iface, &route.iface, 0) == 0); 157 if (rtm_family == AF_INET) { 158 struct trie_value { 159 __u8 prefix[4]; 160 __be64 value; 161 int ifindex; 162 int metric; 163 __be32 gw; 164 } *prefix_value; 165 166 prefix_key = alloca(sizeof(*prefix_key) + 3); 167 prefix_value = alloca(sizeof(*prefix_value)); 168 169 prefix_key->prefixlen = 32; 170 prefix_key->prefixlen = route.dst_len; 171 direct_entry.mac = route.mac & 0xffffffffffff; 172 direct_entry.ifindex = route.iface; 173 direct_entry.arp.mac = 0; 174 direct_entry.arp.dst = 0; 175 if (route.dst_len == 32) { 176 if (nh->nlmsg_type == RTM_DELROUTE) { 177 assert(bpf_map_delete_elem(exact_match_map_fd, 178 &route.dst) == 0); 179 } else { 180 if (bpf_map_lookup_elem(arp_table_map_fd, 181 &route.dst, 182 &direct_entry.arp.mac) == 0) 183 direct_entry.arp.dst = route.dst; 184 assert(bpf_map_update_elem(exact_match_map_fd, 185 &route.dst, 186 &direct_entry, 0) == 0); 187 } 188 } 189 for (i = 0; i < 4; i++) 190 prefix_key->data[i] = (route.dst >> i * 8) & 0xff; 191 192 if (bpf_map_lookup_elem(lpm_map_fd, prefix_key, 193 prefix_value) < 0) { 194 for (i = 0; i < 4; i++) 195 prefix_value->prefix[i] = prefix_key->data[i]; 196 prefix_value->value = route.mac & 0xffffffffffff; 197 prefix_value->ifindex = route.iface; 198 prefix_value->gw = route.gw; 199 prefix_value->metric = route.metric; 200 201 assert(bpf_map_update_elem(lpm_map_fd, 202 prefix_key, 203 prefix_value, 0 204 ) == 0); 205 } else { 206 if (nh->nlmsg_type == RTM_DELROUTE) { 207 assert(bpf_map_delete_elem(lpm_map_fd, 208 prefix_key 209 ) == 0); 210 /* Rereading the route table to check if 211 * there is an entry with the same 212 * prefix but a different metric as the 213 * deleted enty. 214 */ 215 get_route_table(AF_INET); 216 } else if (prefix_key->data[0] == 217 prefix_value->prefix[0] && 218 prefix_key->data[1] == 219 prefix_value->prefix[1] && 220 prefix_key->data[2] == 221 prefix_value->prefix[2] && 222 prefix_key->data[3] == 223 prefix_value->prefix[3] && 224 route.metric >= prefix_value->metric) { 225 continue; 226 } else { 227 for (i = 0; i < 4; i++) 228 prefix_value->prefix[i] = 229 prefix_key->data[i]; 230 prefix_value->value = 231 route.mac & 0xffffffffffff; 232 prefix_value->ifindex = route.iface; 233 prefix_value->gw = route.gw; 234 prefix_value->metric = route.metric; 235 assert(bpf_map_update_elem(lpm_map_fd, 236 prefix_key, 237 prefix_value, 238 0) == 0); 239 } 240 } 241 } 242 memset(&route, 0, sizeof(route)); 243 memset(dsts, 0, sizeof(dsts)); 244 memset(dsts_len, 0, sizeof(dsts_len)); 245 memset(gws, 0, sizeof(gws)); 246 memset(ifs, 0, sizeof(ifs)); 247 memset(&route, 0, sizeof(route)); 248 } 249 } 250 251 /* Function to read the existing route table when the process is launched*/ 252 static int get_route_table(int rtm_family) 253 { 254 struct sockaddr_nl sa; 255 struct nlmsghdr *nh; 256 int sock, seq = 0; 257 struct msghdr msg; 258 struct iovec iov; 259 int ret = 0; 260 int nll; 261 262 struct { 263 struct nlmsghdr nl; 264 struct rtmsg rt; 265 char buf[8192]; 266 } req; 267 268 sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); 269 if (sock < 0) { 270 fprintf(stderr, "open netlink socket: %s\n", strerror(errno)); 271 return -errno; 272 } 273 memset(&sa, 0, sizeof(sa)); 274 sa.nl_family = AF_NETLINK; 275 if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) { 276 fprintf(stderr, "bind netlink socket: %s\n", strerror(errno)); 277 ret = -errno; 278 goto cleanup; 279 } 280 memset(&req, 0, sizeof(req)); 281 req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); 282 req.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; 283 req.nl.nlmsg_type = RTM_GETROUTE; 284 285 req.rt.rtm_family = rtm_family; 286 req.rt.rtm_table = RT_TABLE_MAIN; 287 req.nl.nlmsg_pid = 0; 288 req.nl.nlmsg_seq = ++seq; 289 memset(&msg, 0, sizeof(msg)); 290 iov.iov_base = (void *)&req.nl; 291 iov.iov_len = req.nl.nlmsg_len; 292 msg.msg_iov = &iov; 293 msg.msg_iovlen = 1; 294 ret = sendmsg(sock, &msg, 0); 295 if (ret < 0) { 296 fprintf(stderr, "send to netlink: %s\n", strerror(errno)); 297 ret = -errno; 298 goto cleanup; 299 } 300 memset(buf, 0, sizeof(buf)); 301 nll = recv_msg(sa, sock); 302 if (nll < 0) { 303 fprintf(stderr, "recv from netlink: %s\n", strerror(nll)); 304 ret = nll; 305 goto cleanup; 306 } 307 nh = (struct nlmsghdr *)buf; 308 read_route(nh, nll); 309 cleanup: 310 close(sock); 311 return ret; 312 } 313 314 /* Function to parse the arp entry returned by netlink 315 * Updates the arp entry related map entries 316 */ 317 static void read_arp(struct nlmsghdr *nh, int nll) 318 { 319 struct rtattr *rt_attr; 320 char dsts[24], mac[24]; 321 struct ndmsg *rt_msg; 322 int rtl, ndm_family; 323 324 struct arp_table { 325 __be64 mac; 326 __be32 dst; 327 } arp_entry; 328 struct direct_map { 329 struct arp_table arp; 330 int ifindex; 331 __be64 mac; 332 } direct_entry; 333 334 for (; NLMSG_OK(nh, nll); nh = NLMSG_NEXT(nh, nll)) { 335 rt_msg = (struct ndmsg *)NLMSG_DATA(nh); 336 rt_attr = (struct rtattr *)RTM_RTA(rt_msg); 337 ndm_family = rt_msg->ndm_family; 338 rtl = RTM_PAYLOAD(nh); 339 for (; RTA_OK(rt_attr, rtl); rt_attr = RTA_NEXT(rt_attr, rtl)) { 340 switch (rt_attr->rta_type) { 341 case NDA_DST: 342 sprintf(dsts, "%u", 343 *((__be32 *)RTA_DATA(rt_attr))); 344 break; 345 case NDA_LLADDR: 346 sprintf(mac, "%lld", 347 *((__be64 *)RTA_DATA(rt_attr))); 348 break; 349 default: 350 break; 351 } 352 } 353 arp_entry.dst = atoi(dsts); 354 arp_entry.mac = atol(mac); 355 356 if (ndm_family == AF_INET) { 357 if (bpf_map_lookup_elem(exact_match_map_fd, 358 &arp_entry.dst, 359 &direct_entry) == 0) { 360 if (nh->nlmsg_type == RTM_DELNEIGH) { 361 direct_entry.arp.dst = 0; 362 direct_entry.arp.mac = 0; 363 } else if (nh->nlmsg_type == RTM_NEWNEIGH) { 364 direct_entry.arp.dst = arp_entry.dst; 365 direct_entry.arp.mac = arp_entry.mac; 366 } 367 assert(bpf_map_update_elem(exact_match_map_fd, 368 &arp_entry.dst, 369 &direct_entry, 0 370 ) == 0); 371 memset(&direct_entry, 0, sizeof(direct_entry)); 372 } 373 if (nh->nlmsg_type == RTM_DELNEIGH) { 374 assert(bpf_map_delete_elem(arp_table_map_fd, 375 &arp_entry.dst) == 0); 376 } else if (nh->nlmsg_type == RTM_NEWNEIGH) { 377 assert(bpf_map_update_elem(arp_table_map_fd, 378 &arp_entry.dst, 379 &arp_entry.mac, 0 380 ) == 0); 381 } 382 } 383 memset(&arp_entry, 0, sizeof(arp_entry)); 384 memset(dsts, 0, sizeof(dsts)); 385 } 386 } 387 388 /* Function to read the existing arp table when the process is launched*/ 389 static int get_arp_table(int rtm_family) 390 { 391 struct sockaddr_nl sa; 392 struct nlmsghdr *nh; 393 int sock, seq = 0; 394 struct msghdr msg; 395 struct iovec iov; 396 int ret = 0; 397 int nll; 398 struct { 399 struct nlmsghdr nl; 400 struct ndmsg rt; 401 char buf[8192]; 402 } req; 403 404 sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); 405 if (sock < 0) { 406 fprintf(stderr, "open netlink socket: %s\n", strerror(errno)); 407 return -errno; 408 } 409 memset(&sa, 0, sizeof(sa)); 410 sa.nl_family = AF_NETLINK; 411 if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) { 412 fprintf(stderr, "bind netlink socket: %s\n", strerror(errno)); 413 ret = -errno; 414 goto cleanup; 415 } 416 memset(&req, 0, sizeof(req)); 417 req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); 418 req.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; 419 req.nl.nlmsg_type = RTM_GETNEIGH; 420 req.rt.ndm_state = NUD_REACHABLE; 421 req.rt.ndm_family = rtm_family; 422 req.nl.nlmsg_pid = 0; 423 req.nl.nlmsg_seq = ++seq; 424 memset(&msg, 0, sizeof(msg)); 425 iov.iov_base = (void *)&req.nl; 426 iov.iov_len = req.nl.nlmsg_len; 427 msg.msg_iov = &iov; 428 msg.msg_iovlen = 1; 429 ret = sendmsg(sock, &msg, 0); 430 if (ret < 0) { 431 fprintf(stderr, "send to netlink: %s\n", strerror(errno)); 432 ret = -errno; 433 goto cleanup; 434 } 435 memset(buf, 0, sizeof(buf)); 436 nll = recv_msg(sa, sock); 437 if (nll < 0) { 438 fprintf(stderr, "recv from netlink: %s\n", strerror(nll)); 439 ret = nll; 440 goto cleanup; 441 } 442 nh = (struct nlmsghdr *)buf; 443 read_arp(nh, nll); 444 cleanup: 445 close(sock); 446 return ret; 447 } 448 449 /* Function to keep track and update changes in route and arp table 450 * Give regular statistics of packets forwarded 451 */ 452 static void *monitor_routes_thread(void *arg) 453 { 454 struct pollfd fds_route, fds_arp; 455 struct sockaddr_nl la, lr; 456 int sock, sock_arp, nll; 457 struct nlmsghdr *nh; 458 459 sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); 460 if (sock < 0) { 461 fprintf(stderr, "open netlink socket: %s\n", strerror(errno)); 462 return NULL; 463 } 464 465 fcntl(sock, F_SETFL, O_NONBLOCK); 466 memset(&lr, 0, sizeof(lr)); 467 lr.nl_family = AF_NETLINK; 468 lr.nl_groups = RTMGRP_IPV6_ROUTE | RTMGRP_IPV4_ROUTE | RTMGRP_NOTIFY; 469 if (bind(sock, (struct sockaddr *)&lr, sizeof(lr)) < 0) { 470 fprintf(stderr, "bind netlink socket: %s\n", strerror(errno)); 471 close(sock); 472 return NULL; 473 } 474 475 fds_route.fd = sock; 476 fds_route.events = POLL_IN; 477 478 sock_arp = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); 479 if (sock_arp < 0) { 480 fprintf(stderr, "open netlink socket: %s\n", strerror(errno)); 481 close(sock); 482 return NULL; 483 } 484 485 fcntl(sock_arp, F_SETFL, O_NONBLOCK); 486 memset(&la, 0, sizeof(la)); 487 la.nl_family = AF_NETLINK; 488 la.nl_groups = RTMGRP_NEIGH | RTMGRP_NOTIFY; 489 if (bind(sock_arp, (struct sockaddr *)&la, sizeof(la)) < 0) { 490 fprintf(stderr, "bind netlink socket: %s\n", strerror(errno)); 491 goto cleanup; 492 } 493 494 fds_arp.fd = sock_arp; 495 fds_arp.events = POLL_IN; 496 497 /* dump route and arp tables */ 498 if (get_arp_table(AF_INET) < 0) { 499 fprintf(stderr, "Failed reading arp table\n"); 500 goto cleanup; 501 } 502 503 if (get_route_table(AF_INET) < 0) { 504 fprintf(stderr, "Failed reading route table\n"); 505 goto cleanup; 506 } 507 508 while (!routes_thread_exit) { 509 memset(buf, 0, sizeof(buf)); 510 if (poll(&fds_route, 1, 3) == POLL_IN) { 511 nll = recv_msg(lr, sock); 512 if (nll < 0) { 513 fprintf(stderr, "recv from netlink: %s\n", 514 strerror(nll)); 515 goto cleanup; 516 } 517 518 nh = (struct nlmsghdr *)buf; 519 read_route(nh, nll); 520 } 521 522 memset(buf, 0, sizeof(buf)); 523 if (poll(&fds_arp, 1, 3) == POLL_IN) { 524 nll = recv_msg(la, sock_arp); 525 if (nll < 0) { 526 fprintf(stderr, "recv from netlink: %s\n", 527 strerror(nll)); 528 goto cleanup; 529 } 530 531 nh = (struct nlmsghdr *)buf; 532 read_arp(nh, nll); 533 } 534 535 sleep(interval); 536 } 537 538 cleanup: 539 close(sock_arp); 540 close(sock); 541 return NULL; 542 } 543 544 static void usage(char *argv[], const struct option *long_options, 545 const char *doc, int mask, bool error, 546 struct bpf_object *obj) 547 { 548 sample_usage(argv, long_options, doc, mask, error); 549 } 550 551 int main(int argc, char **argv) 552 { 553 bool error = true, generic = false, force = false; 554 int opt, ret = EXIT_FAIL_BPF; 555 struct xdp_router_ipv4 *skel; 556 int i, total_ifindex = argc - 1; 557 char **ifname_list = argv + 1; 558 pthread_t routes_thread; 559 int longindex = 0; 560 561 if (libbpf_set_strict_mode(LIBBPF_STRICT_ALL) < 0) { 562 fprintf(stderr, "Failed to set libbpf strict mode: %s\n", 563 strerror(errno)); 564 goto end; 565 } 566 567 skel = xdp_router_ipv4__open(); 568 if (!skel) { 569 fprintf(stderr, "Failed to xdp_router_ipv4__open: %s\n", 570 strerror(errno)); 571 goto end; 572 } 573 574 ret = sample_init_pre_load(skel); 575 if (ret < 0) { 576 fprintf(stderr, "Failed to sample_init_pre_load: %s\n", 577 strerror(-ret)); 578 ret = EXIT_FAIL_BPF; 579 goto end_destroy; 580 } 581 582 ret = xdp_router_ipv4__load(skel); 583 if (ret < 0) { 584 fprintf(stderr, "Failed to xdp_router_ipv4__load: %s\n", 585 strerror(errno)); 586 goto end_destroy; 587 } 588 589 ret = sample_init(skel, mask); 590 if (ret < 0) { 591 fprintf(stderr, "Failed to initialize sample: %s\n", strerror(-ret)); 592 ret = EXIT_FAIL; 593 goto end_destroy; 594 } 595 596 while ((opt = getopt_long(argc, argv, "si:SFvh", 597 long_options, &longindex)) != -1) { 598 switch (opt) { 599 case 's': 600 mask |= SAMPLE_REDIRECT_MAP_CNT; 601 total_ifindex--; 602 ifname_list++; 603 break; 604 case 'i': 605 interval = strtoul(optarg, NULL, 0); 606 total_ifindex -= 2; 607 ifname_list += 2; 608 break; 609 case 'S': 610 generic = true; 611 total_ifindex--; 612 ifname_list++; 613 break; 614 case 'F': 615 force = true; 616 total_ifindex--; 617 ifname_list++; 618 break; 619 case 'v': 620 sample_switch_mode(); 621 total_ifindex--; 622 ifname_list++; 623 break; 624 case 'h': 625 error = false; 626 default: 627 usage(argv, long_options, __doc__, mask, error, skel->obj); 628 goto end_destroy; 629 } 630 } 631 632 ret = EXIT_FAIL_OPTION; 633 if (optind == argc) { 634 usage(argv, long_options, __doc__, mask, true, skel->obj); 635 goto end_destroy; 636 } 637 638 lpm_map_fd = bpf_map__fd(skel->maps.lpm_map); 639 if (lpm_map_fd < 0) { 640 fprintf(stderr, "Failed loading lpm_map %s\n", 641 strerror(-lpm_map_fd)); 642 goto end_destroy; 643 } 644 arp_table_map_fd = bpf_map__fd(skel->maps.arp_table); 645 if (arp_table_map_fd < 0) { 646 fprintf(stderr, "Failed loading arp_table_map_fd %s\n", 647 strerror(-arp_table_map_fd)); 648 goto end_destroy; 649 } 650 exact_match_map_fd = bpf_map__fd(skel->maps.exact_match); 651 if (exact_match_map_fd < 0) { 652 fprintf(stderr, "Failed loading exact_match_map_fd %s\n", 653 strerror(-exact_match_map_fd)); 654 goto end_destroy; 655 } 656 tx_port_map_fd = bpf_map__fd(skel->maps.tx_port); 657 if (tx_port_map_fd < 0) { 658 fprintf(stderr, "Failed loading tx_port_map_fd %s\n", 659 strerror(-tx_port_map_fd)); 660 goto end_destroy; 661 } 662 663 ret = EXIT_FAIL_XDP; 664 for (i = 0; i < total_ifindex; i++) { 665 int index = if_nametoindex(ifname_list[i]); 666 667 if (!index) { 668 fprintf(stderr, "Interface %s not found %s\n", 669 ifname_list[i], strerror(-tx_port_map_fd)); 670 goto end_destroy; 671 } 672 if (sample_install_xdp(skel->progs.xdp_router_ipv4_prog, 673 index, generic, force) < 0) 674 goto end_destroy; 675 } 676 677 ret = pthread_create(&routes_thread, NULL, monitor_routes_thread, NULL); 678 if (ret) { 679 fprintf(stderr, "Failed creating routes_thread: %s\n", strerror(-ret)); 680 ret = EXIT_FAIL; 681 goto end_destroy; 682 } 683 684 ret = sample_run(interval, NULL, NULL); 685 routes_thread_exit = true; 686 687 if (ret < 0) { 688 fprintf(stderr, "Failed during sample run: %s\n", strerror(-ret)); 689 ret = EXIT_FAIL; 690 goto end_thread_wait; 691 } 692 ret = EXIT_OK; 693 694 end_thread_wait: 695 pthread_join(routes_thread, NULL); 696 end_destroy: 697 xdp_router_ipv4__destroy(skel); 698 end: 699 sample_exit(ret); 700 } 701