1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2 3 /* 4 * This test sets up 3 netns (src <-> fwd <-> dst). There is no direct veth link 5 * between src and dst. The netns fwd has veth links to each src and dst. The 6 * client is in src and server in dst. The test installs a TC BPF program to each 7 * host facing veth in fwd which calls into i) bpf_redirect_neigh() to perform the 8 * neigh addr population and redirect or ii) bpf_redirect_peer() for namespace 9 * switch from ingress side; it also installs a checker prog on the egress side 10 * to drop unexpected traffic. 11 */ 12 13 #include <arpa/inet.h> 14 #include <linux/if_tun.h> 15 #include <linux/limits.h> 16 #include <linux/sysctl.h> 17 #include <linux/time_types.h> 18 #include <linux/net_tstamp.h> 19 #include <net/if.h> 20 #include <stdbool.h> 21 #include <stdio.h> 22 #include <sys/stat.h> 23 #include <unistd.h> 24 25 #include "test_progs.h" 26 #include "network_helpers.h" 27 #include "test_tc_neigh_fib.skel.h" 28 #include "test_tc_neigh.skel.h" 29 #include "test_tc_peer.skel.h" 30 #include "test_tc_dtime.skel.h" 31 32 #ifndef TCP_TX_DELAY 33 #define TCP_TX_DELAY 37 34 #endif 35 36 #define NS_SRC "ns_src" 37 #define NS_FWD "ns_fwd" 38 #define NS_DST "ns_dst" 39 40 #define IP4_SRC "172.16.1.100" 41 #define IP4_DST "172.16.2.100" 42 #define IP4_TUN_SRC "172.17.1.100" 43 #define IP4_TUN_FWD "172.17.1.200" 44 #define IP4_PORT 9004 45 46 #define IP6_SRC "0::1:dead:beef:cafe" 47 #define IP6_DST "0::2:dead:beef:cafe" 48 #define IP6_TUN_SRC "1::1:dead:beef:cafe" 49 #define IP6_TUN_FWD "1::2:dead:beef:cafe" 50 #define IP6_PORT 9006 51 52 #define IP4_SLL "169.254.0.1" 53 #define IP4_DLL "169.254.0.2" 54 #define IP4_NET "169.254.0.0" 55 56 #define MAC_DST_FWD "00:11:22:33:44:55" 57 #define MAC_DST "00:22:33:44:55:66" 58 59 #define IFADDR_STR_LEN 18 60 #define PING_ARGS "-i 0.2 -c 3 -w 10 -q" 61 62 #define TIMEOUT_MILLIS 10000 63 #define NSEC_PER_SEC 1000000000ULL 64 65 #define log_err(MSG, ...) \ 66 fprintf(stderr, "(%s:%d: errno: %s) " MSG "\n", \ 67 __FILE__, __LINE__, strerror(errno), ##__VA_ARGS__) 68 69 static const char * const namespaces[] = {NS_SRC, NS_FWD, NS_DST, NULL}; 70 71 static int write_file(const char *path, const char *newval) 72 { 73 FILE *f; 74 75 f = fopen(path, "r+"); 76 if (!f) 77 return -1; 78 if (fwrite(newval, strlen(newval), 1, f) != 1) { 79 log_err("writing to %s failed", path); 80 fclose(f); 81 return -1; 82 } 83 fclose(f); 84 return 0; 85 } 86 87 static int netns_setup_namespaces(const char *verb) 88 { 89 const char * const *ns = namespaces; 90 char cmd[128]; 91 92 while (*ns) { 93 snprintf(cmd, sizeof(cmd), "ip netns %s %s", verb, *ns); 94 if (!ASSERT_OK(system(cmd), cmd)) 95 return -1; 96 ns++; 97 } 98 return 0; 99 } 100 101 static void netns_setup_namespaces_nofail(const char *verb) 102 { 103 const char * const *ns = namespaces; 104 char cmd[128]; 105 106 while (*ns) { 107 snprintf(cmd, sizeof(cmd), "ip netns %s %s > /dev/null 2>&1", verb, *ns); 108 system(cmd); 109 ns++; 110 } 111 } 112 113 struct netns_setup_result { 114 int ifindex_veth_src; 115 int ifindex_veth_src_fwd; 116 int ifindex_veth_dst; 117 int ifindex_veth_dst_fwd; 118 }; 119 120 static int get_ifaddr(const char *name, char *ifaddr) 121 { 122 char path[PATH_MAX]; 123 FILE *f; 124 int ret; 125 126 snprintf(path, PATH_MAX, "/sys/class/net/%s/address", name); 127 f = fopen(path, "r"); 128 if (!ASSERT_OK_PTR(f, path)) 129 return -1; 130 131 ret = fread(ifaddr, 1, IFADDR_STR_LEN, f); 132 if (!ASSERT_EQ(ret, IFADDR_STR_LEN, "fread ifaddr")) { 133 fclose(f); 134 return -1; 135 } 136 fclose(f); 137 return 0; 138 } 139 140 #define SYS(fmt, ...) \ 141 ({ \ 142 char cmd[1024]; \ 143 snprintf(cmd, sizeof(cmd), fmt, ##__VA_ARGS__); \ 144 if (!ASSERT_OK(system(cmd), cmd)) \ 145 goto fail; \ 146 }) 147 148 static int netns_setup_links_and_routes(struct netns_setup_result *result) 149 { 150 struct nstoken *nstoken = NULL; 151 char veth_src_fwd_addr[IFADDR_STR_LEN+1] = {}; 152 153 SYS("ip link add veth_src type veth peer name veth_src_fwd"); 154 SYS("ip link add veth_dst type veth peer name veth_dst_fwd"); 155 156 SYS("ip link set veth_dst_fwd address " MAC_DST_FWD); 157 SYS("ip link set veth_dst address " MAC_DST); 158 159 if (get_ifaddr("veth_src_fwd", veth_src_fwd_addr)) 160 goto fail; 161 162 result->ifindex_veth_src = if_nametoindex("veth_src"); 163 if (!ASSERT_GT(result->ifindex_veth_src, 0, "ifindex_veth_src")) 164 goto fail; 165 166 result->ifindex_veth_src_fwd = if_nametoindex("veth_src_fwd"); 167 if (!ASSERT_GT(result->ifindex_veth_src_fwd, 0, "ifindex_veth_src_fwd")) 168 goto fail; 169 170 result->ifindex_veth_dst = if_nametoindex("veth_dst"); 171 if (!ASSERT_GT(result->ifindex_veth_dst, 0, "ifindex_veth_dst")) 172 goto fail; 173 174 result->ifindex_veth_dst_fwd = if_nametoindex("veth_dst_fwd"); 175 if (!ASSERT_GT(result->ifindex_veth_dst_fwd, 0, "ifindex_veth_dst_fwd")) 176 goto fail; 177 178 SYS("ip link set veth_src netns " NS_SRC); 179 SYS("ip link set veth_src_fwd netns " NS_FWD); 180 SYS("ip link set veth_dst_fwd netns " NS_FWD); 181 SYS("ip link set veth_dst netns " NS_DST); 182 183 /** setup in 'src' namespace */ 184 nstoken = open_netns(NS_SRC); 185 if (!ASSERT_OK_PTR(nstoken, "setns src")) 186 goto fail; 187 188 SYS("ip addr add " IP4_SRC "/32 dev veth_src"); 189 SYS("ip addr add " IP6_SRC "/128 dev veth_src nodad"); 190 SYS("ip link set dev veth_src up"); 191 192 SYS("ip route add " IP4_DST "/32 dev veth_src scope global"); 193 SYS("ip route add " IP4_NET "/16 dev veth_src scope global"); 194 SYS("ip route add " IP6_DST "/128 dev veth_src scope global"); 195 196 SYS("ip neigh add " IP4_DST " dev veth_src lladdr %s", 197 veth_src_fwd_addr); 198 SYS("ip neigh add " IP6_DST " dev veth_src lladdr %s", 199 veth_src_fwd_addr); 200 201 close_netns(nstoken); 202 203 /** setup in 'fwd' namespace */ 204 nstoken = open_netns(NS_FWD); 205 if (!ASSERT_OK_PTR(nstoken, "setns fwd")) 206 goto fail; 207 208 /* The fwd netns automatically gets a v6 LL address / routes, but also 209 * needs v4 one in order to start ARP probing. IP4_NET route is added 210 * to the endpoints so that the ARP processing will reply. 211 */ 212 SYS("ip addr add " IP4_SLL "/32 dev veth_src_fwd"); 213 SYS("ip addr add " IP4_DLL "/32 dev veth_dst_fwd"); 214 SYS("ip link set dev veth_src_fwd up"); 215 SYS("ip link set dev veth_dst_fwd up"); 216 217 SYS("ip route add " IP4_SRC "/32 dev veth_src_fwd scope global"); 218 SYS("ip route add " IP6_SRC "/128 dev veth_src_fwd scope global"); 219 SYS("ip route add " IP4_DST "/32 dev veth_dst_fwd scope global"); 220 SYS("ip route add " IP6_DST "/128 dev veth_dst_fwd scope global"); 221 222 close_netns(nstoken); 223 224 /** setup in 'dst' namespace */ 225 nstoken = open_netns(NS_DST); 226 if (!ASSERT_OK_PTR(nstoken, "setns dst")) 227 goto fail; 228 229 SYS("ip addr add " IP4_DST "/32 dev veth_dst"); 230 SYS("ip addr add " IP6_DST "/128 dev veth_dst nodad"); 231 SYS("ip link set dev veth_dst up"); 232 233 SYS("ip route add " IP4_SRC "/32 dev veth_dst scope global"); 234 SYS("ip route add " IP4_NET "/16 dev veth_dst scope global"); 235 SYS("ip route add " IP6_SRC "/128 dev veth_dst scope global"); 236 237 SYS("ip neigh add " IP4_SRC " dev veth_dst lladdr " MAC_DST_FWD); 238 SYS("ip neigh add " IP6_SRC " dev veth_dst lladdr " MAC_DST_FWD); 239 240 close_netns(nstoken); 241 242 return 0; 243 fail: 244 if (nstoken) 245 close_netns(nstoken); 246 return -1; 247 } 248 249 static int qdisc_clsact_create(struct bpf_tc_hook *qdisc_hook, int ifindex) 250 { 251 char err_str[128], ifname[16]; 252 int err; 253 254 qdisc_hook->ifindex = ifindex; 255 qdisc_hook->attach_point = BPF_TC_INGRESS | BPF_TC_EGRESS; 256 err = bpf_tc_hook_create(qdisc_hook); 257 snprintf(err_str, sizeof(err_str), 258 "qdisc add dev %s clsact", 259 if_indextoname(qdisc_hook->ifindex, ifname) ? : "<unknown_iface>"); 260 err_str[sizeof(err_str) - 1] = 0; 261 ASSERT_OK(err, err_str); 262 263 return err; 264 } 265 266 static int xgress_filter_add(struct bpf_tc_hook *qdisc_hook, 267 enum bpf_tc_attach_point xgress, 268 const struct bpf_program *prog, int priority) 269 { 270 LIBBPF_OPTS(bpf_tc_opts, tc_attach); 271 char err_str[128], ifname[16]; 272 int err; 273 274 qdisc_hook->attach_point = xgress; 275 tc_attach.prog_fd = bpf_program__fd(prog); 276 tc_attach.priority = priority; 277 err = bpf_tc_attach(qdisc_hook, &tc_attach); 278 snprintf(err_str, sizeof(err_str), 279 "filter add dev %s %s prio %d bpf da %s", 280 if_indextoname(qdisc_hook->ifindex, ifname) ? : "<unknown_iface>", 281 xgress == BPF_TC_INGRESS ? "ingress" : "egress", 282 priority, bpf_program__name(prog)); 283 err_str[sizeof(err_str) - 1] = 0; 284 ASSERT_OK(err, err_str); 285 286 return err; 287 } 288 289 #define QDISC_CLSACT_CREATE(qdisc_hook, ifindex) ({ \ 290 if ((err = qdisc_clsact_create(qdisc_hook, ifindex))) \ 291 goto fail; \ 292 }) 293 294 #define XGRESS_FILTER_ADD(qdisc_hook, xgress, prog, priority) ({ \ 295 if ((err = xgress_filter_add(qdisc_hook, xgress, prog, priority))) \ 296 goto fail; \ 297 }) 298 299 static int netns_load_bpf(const struct bpf_program *src_prog, 300 const struct bpf_program *dst_prog, 301 const struct bpf_program *chk_prog, 302 const struct netns_setup_result *setup_result) 303 { 304 LIBBPF_OPTS(bpf_tc_hook, qdisc_veth_src_fwd); 305 LIBBPF_OPTS(bpf_tc_hook, qdisc_veth_dst_fwd); 306 int err; 307 308 /* tc qdisc add dev veth_src_fwd clsact */ 309 QDISC_CLSACT_CREATE(&qdisc_veth_src_fwd, setup_result->ifindex_veth_src_fwd); 310 /* tc filter add dev veth_src_fwd ingress bpf da src_prog */ 311 XGRESS_FILTER_ADD(&qdisc_veth_src_fwd, BPF_TC_INGRESS, src_prog, 0); 312 /* tc filter add dev veth_src_fwd egress bpf da chk_prog */ 313 XGRESS_FILTER_ADD(&qdisc_veth_src_fwd, BPF_TC_EGRESS, chk_prog, 0); 314 315 /* tc qdisc add dev veth_dst_fwd clsact */ 316 QDISC_CLSACT_CREATE(&qdisc_veth_dst_fwd, setup_result->ifindex_veth_dst_fwd); 317 /* tc filter add dev veth_dst_fwd ingress bpf da dst_prog */ 318 XGRESS_FILTER_ADD(&qdisc_veth_dst_fwd, BPF_TC_INGRESS, dst_prog, 0); 319 /* tc filter add dev veth_dst_fwd egress bpf da chk_prog */ 320 XGRESS_FILTER_ADD(&qdisc_veth_dst_fwd, BPF_TC_EGRESS, chk_prog, 0); 321 322 return 0; 323 fail: 324 return -1; 325 } 326 327 static void test_tcp(int family, const char *addr, __u16 port) 328 { 329 int listen_fd = -1, accept_fd = -1, client_fd = -1; 330 char buf[] = "testing testing"; 331 int n; 332 struct nstoken *nstoken; 333 334 nstoken = open_netns(NS_DST); 335 if (!ASSERT_OK_PTR(nstoken, "setns dst")) 336 return; 337 338 listen_fd = start_server(family, SOCK_STREAM, addr, port, 0); 339 if (!ASSERT_GE(listen_fd, 0, "listen")) 340 goto done; 341 342 close_netns(nstoken); 343 nstoken = open_netns(NS_SRC); 344 if (!ASSERT_OK_PTR(nstoken, "setns src")) 345 goto done; 346 347 client_fd = connect_to_fd(listen_fd, TIMEOUT_MILLIS); 348 if (!ASSERT_GE(client_fd, 0, "connect_to_fd")) 349 goto done; 350 351 accept_fd = accept(listen_fd, NULL, NULL); 352 if (!ASSERT_GE(accept_fd, 0, "accept")) 353 goto done; 354 355 if (!ASSERT_OK(settimeo(accept_fd, TIMEOUT_MILLIS), "settimeo")) 356 goto done; 357 358 n = write(client_fd, buf, sizeof(buf)); 359 if (!ASSERT_EQ(n, sizeof(buf), "send to server")) 360 goto done; 361 362 n = read(accept_fd, buf, sizeof(buf)); 363 ASSERT_EQ(n, sizeof(buf), "recv from server"); 364 365 done: 366 if (nstoken) 367 close_netns(nstoken); 368 if (listen_fd >= 0) 369 close(listen_fd); 370 if (accept_fd >= 0) 371 close(accept_fd); 372 if (client_fd >= 0) 373 close(client_fd); 374 } 375 376 static int test_ping(int family, const char *addr) 377 { 378 SYS("ip netns exec " NS_SRC " %s " PING_ARGS " %s > /dev/null", ping_command(family), addr); 379 return 0; 380 fail: 381 return -1; 382 } 383 384 static void test_connectivity(void) 385 { 386 test_tcp(AF_INET, IP4_DST, IP4_PORT); 387 test_ping(AF_INET, IP4_DST); 388 test_tcp(AF_INET6, IP6_DST, IP6_PORT); 389 test_ping(AF_INET6, IP6_DST); 390 } 391 392 static int set_forwarding(bool enable) 393 { 394 int err; 395 396 err = write_file("/proc/sys/net/ipv4/ip_forward", enable ? "1" : "0"); 397 if (!ASSERT_OK(err, "set ipv4.ip_forward=0")) 398 return err; 399 400 err = write_file("/proc/sys/net/ipv6/conf/all/forwarding", enable ? "1" : "0"); 401 if (!ASSERT_OK(err, "set ipv6.forwarding=0")) 402 return err; 403 404 return 0; 405 } 406 407 static void rcv_tstamp(int fd, const char *expected, size_t s) 408 { 409 struct __kernel_timespec pkt_ts = {}; 410 char ctl[CMSG_SPACE(sizeof(pkt_ts))]; 411 struct timespec now_ts; 412 struct msghdr msg = {}; 413 __u64 now_ns, pkt_ns; 414 struct cmsghdr *cmsg; 415 struct iovec iov; 416 char data[32]; 417 int ret; 418 419 iov.iov_base = data; 420 iov.iov_len = sizeof(data); 421 msg.msg_iov = &iov; 422 msg.msg_iovlen = 1; 423 msg.msg_control = &ctl; 424 msg.msg_controllen = sizeof(ctl); 425 426 ret = recvmsg(fd, &msg, 0); 427 if (!ASSERT_EQ(ret, s, "recvmsg")) 428 return; 429 ASSERT_STRNEQ(data, expected, s, "expected rcv data"); 430 431 cmsg = CMSG_FIRSTHDR(&msg); 432 if (cmsg && cmsg->cmsg_level == SOL_SOCKET && 433 cmsg->cmsg_type == SO_TIMESTAMPNS_NEW) 434 memcpy(&pkt_ts, CMSG_DATA(cmsg), sizeof(pkt_ts)); 435 436 pkt_ns = pkt_ts.tv_sec * NSEC_PER_SEC + pkt_ts.tv_nsec; 437 ASSERT_NEQ(pkt_ns, 0, "pkt rcv tstamp"); 438 439 ret = clock_gettime(CLOCK_REALTIME, &now_ts); 440 ASSERT_OK(ret, "clock_gettime"); 441 now_ns = now_ts.tv_sec * NSEC_PER_SEC + now_ts.tv_nsec; 442 443 if (ASSERT_GE(now_ns, pkt_ns, "check rcv tstamp")) 444 ASSERT_LT(now_ns - pkt_ns, 5 * NSEC_PER_SEC, 445 "check rcv tstamp"); 446 } 447 448 static void snd_tstamp(int fd, char *b, size_t s) 449 { 450 struct sock_txtime opt = { .clockid = CLOCK_TAI }; 451 char ctl[CMSG_SPACE(sizeof(__u64))]; 452 struct timespec now_ts; 453 struct msghdr msg = {}; 454 struct cmsghdr *cmsg; 455 struct iovec iov; 456 __u64 now_ns; 457 int ret; 458 459 ret = clock_gettime(CLOCK_TAI, &now_ts); 460 ASSERT_OK(ret, "clock_get_time(CLOCK_TAI)"); 461 now_ns = now_ts.tv_sec * NSEC_PER_SEC + now_ts.tv_nsec; 462 463 iov.iov_base = b; 464 iov.iov_len = s; 465 msg.msg_iov = &iov; 466 msg.msg_iovlen = 1; 467 msg.msg_control = &ctl; 468 msg.msg_controllen = sizeof(ctl); 469 470 cmsg = CMSG_FIRSTHDR(&msg); 471 cmsg->cmsg_level = SOL_SOCKET; 472 cmsg->cmsg_type = SCM_TXTIME; 473 cmsg->cmsg_len = CMSG_LEN(sizeof(now_ns)); 474 *(__u64 *)CMSG_DATA(cmsg) = now_ns; 475 476 ret = setsockopt(fd, SOL_SOCKET, SO_TXTIME, &opt, sizeof(opt)); 477 ASSERT_OK(ret, "setsockopt(SO_TXTIME)"); 478 479 ret = sendmsg(fd, &msg, 0); 480 ASSERT_EQ(ret, s, "sendmsg"); 481 } 482 483 static void test_inet_dtime(int family, int type, const char *addr, __u16 port) 484 { 485 int opt = 1, accept_fd = -1, client_fd = -1, listen_fd, err; 486 char buf[] = "testing testing"; 487 struct nstoken *nstoken; 488 489 nstoken = open_netns(NS_DST); 490 if (!ASSERT_OK_PTR(nstoken, "setns dst")) 491 return; 492 listen_fd = start_server(family, type, addr, port, 0); 493 close_netns(nstoken); 494 495 if (!ASSERT_GE(listen_fd, 0, "listen")) 496 return; 497 498 /* Ensure the kernel puts the (rcv) timestamp for all skb */ 499 err = setsockopt(listen_fd, SOL_SOCKET, SO_TIMESTAMPNS_NEW, 500 &opt, sizeof(opt)); 501 if (!ASSERT_OK(err, "setsockopt(SO_TIMESTAMPNS_NEW)")) 502 goto done; 503 504 if (type == SOCK_STREAM) { 505 /* Ensure the kernel set EDT when sending out rst/ack 506 * from the kernel's ctl_sk. 507 */ 508 err = setsockopt(listen_fd, SOL_TCP, TCP_TX_DELAY, &opt, 509 sizeof(opt)); 510 if (!ASSERT_OK(err, "setsockopt(TCP_TX_DELAY)")) 511 goto done; 512 } 513 514 nstoken = open_netns(NS_SRC); 515 if (!ASSERT_OK_PTR(nstoken, "setns src")) 516 goto done; 517 client_fd = connect_to_fd(listen_fd, TIMEOUT_MILLIS); 518 close_netns(nstoken); 519 520 if (!ASSERT_GE(client_fd, 0, "connect_to_fd")) 521 goto done; 522 523 if (type == SOCK_STREAM) { 524 int n; 525 526 accept_fd = accept(listen_fd, NULL, NULL); 527 if (!ASSERT_GE(accept_fd, 0, "accept")) 528 goto done; 529 530 n = write(client_fd, buf, sizeof(buf)); 531 if (!ASSERT_EQ(n, sizeof(buf), "send to server")) 532 goto done; 533 rcv_tstamp(accept_fd, buf, sizeof(buf)); 534 } else { 535 snd_tstamp(client_fd, buf, sizeof(buf)); 536 rcv_tstamp(listen_fd, buf, sizeof(buf)); 537 } 538 539 done: 540 close(listen_fd); 541 if (accept_fd != -1) 542 close(accept_fd); 543 if (client_fd != -1) 544 close(client_fd); 545 } 546 547 static int netns_load_dtime_bpf(struct test_tc_dtime *skel, 548 const struct netns_setup_result *setup_result) 549 { 550 LIBBPF_OPTS(bpf_tc_hook, qdisc_veth_src_fwd); 551 LIBBPF_OPTS(bpf_tc_hook, qdisc_veth_dst_fwd); 552 LIBBPF_OPTS(bpf_tc_hook, qdisc_veth_src); 553 LIBBPF_OPTS(bpf_tc_hook, qdisc_veth_dst); 554 struct nstoken *nstoken; 555 int err; 556 557 /* setup ns_src tc progs */ 558 nstoken = open_netns(NS_SRC); 559 if (!ASSERT_OK_PTR(nstoken, "setns " NS_SRC)) 560 return -1; 561 /* tc qdisc add dev veth_src clsact */ 562 QDISC_CLSACT_CREATE(&qdisc_veth_src, setup_result->ifindex_veth_src); 563 /* tc filter add dev veth_src ingress bpf da ingress_host */ 564 XGRESS_FILTER_ADD(&qdisc_veth_src, BPF_TC_INGRESS, skel->progs.ingress_host, 0); 565 /* tc filter add dev veth_src egress bpf da egress_host */ 566 XGRESS_FILTER_ADD(&qdisc_veth_src, BPF_TC_EGRESS, skel->progs.egress_host, 0); 567 close_netns(nstoken); 568 569 /* setup ns_dst tc progs */ 570 nstoken = open_netns(NS_DST); 571 if (!ASSERT_OK_PTR(nstoken, "setns " NS_DST)) 572 return -1; 573 /* tc qdisc add dev veth_dst clsact */ 574 QDISC_CLSACT_CREATE(&qdisc_veth_dst, setup_result->ifindex_veth_dst); 575 /* tc filter add dev veth_dst ingress bpf da ingress_host */ 576 XGRESS_FILTER_ADD(&qdisc_veth_dst, BPF_TC_INGRESS, skel->progs.ingress_host, 0); 577 /* tc filter add dev veth_dst egress bpf da egress_host */ 578 XGRESS_FILTER_ADD(&qdisc_veth_dst, BPF_TC_EGRESS, skel->progs.egress_host, 0); 579 close_netns(nstoken); 580 581 /* setup ns_fwd tc progs */ 582 nstoken = open_netns(NS_FWD); 583 if (!ASSERT_OK_PTR(nstoken, "setns " NS_FWD)) 584 return -1; 585 /* tc qdisc add dev veth_dst_fwd clsact */ 586 QDISC_CLSACT_CREATE(&qdisc_veth_dst_fwd, setup_result->ifindex_veth_dst_fwd); 587 /* tc filter add dev veth_dst_fwd ingress prio 100 bpf da ingress_fwdns_prio100 */ 588 XGRESS_FILTER_ADD(&qdisc_veth_dst_fwd, BPF_TC_INGRESS, 589 skel->progs.ingress_fwdns_prio100, 100); 590 /* tc filter add dev veth_dst_fwd ingress prio 101 bpf da ingress_fwdns_prio101 */ 591 XGRESS_FILTER_ADD(&qdisc_veth_dst_fwd, BPF_TC_INGRESS, 592 skel->progs.ingress_fwdns_prio101, 101); 593 /* tc filter add dev veth_dst_fwd egress prio 100 bpf da egress_fwdns_prio100 */ 594 XGRESS_FILTER_ADD(&qdisc_veth_dst_fwd, BPF_TC_EGRESS, 595 skel->progs.egress_fwdns_prio100, 100); 596 /* tc filter add dev veth_dst_fwd egress prio 101 bpf da egress_fwdns_prio101 */ 597 XGRESS_FILTER_ADD(&qdisc_veth_dst_fwd, BPF_TC_EGRESS, 598 skel->progs.egress_fwdns_prio101, 101); 599 600 /* tc qdisc add dev veth_src_fwd clsact */ 601 QDISC_CLSACT_CREATE(&qdisc_veth_src_fwd, setup_result->ifindex_veth_src_fwd); 602 /* tc filter add dev veth_src_fwd ingress prio 100 bpf da ingress_fwdns_prio100 */ 603 XGRESS_FILTER_ADD(&qdisc_veth_src_fwd, BPF_TC_INGRESS, 604 skel->progs.ingress_fwdns_prio100, 100); 605 /* tc filter add dev veth_src_fwd ingress prio 101 bpf da ingress_fwdns_prio101 */ 606 XGRESS_FILTER_ADD(&qdisc_veth_src_fwd, BPF_TC_INGRESS, 607 skel->progs.ingress_fwdns_prio101, 101); 608 /* tc filter add dev veth_src_fwd egress prio 100 bpf da egress_fwdns_prio100 */ 609 XGRESS_FILTER_ADD(&qdisc_veth_src_fwd, BPF_TC_EGRESS, 610 skel->progs.egress_fwdns_prio100, 100); 611 /* tc filter add dev veth_src_fwd egress prio 101 bpf da egress_fwdns_prio101 */ 612 XGRESS_FILTER_ADD(&qdisc_veth_src_fwd, BPF_TC_EGRESS, 613 skel->progs.egress_fwdns_prio101, 101); 614 close_netns(nstoken); 615 return 0; 616 617 fail: 618 close_netns(nstoken); 619 return err; 620 } 621 622 enum { 623 INGRESS_FWDNS_P100, 624 INGRESS_FWDNS_P101, 625 EGRESS_FWDNS_P100, 626 EGRESS_FWDNS_P101, 627 INGRESS_ENDHOST, 628 EGRESS_ENDHOST, 629 SET_DTIME, 630 __MAX_CNT, 631 }; 632 633 const char *cnt_names[] = { 634 "ingress_fwdns_p100", 635 "ingress_fwdns_p101", 636 "egress_fwdns_p100", 637 "egress_fwdns_p101", 638 "ingress_endhost", 639 "egress_endhost", 640 "set_dtime", 641 }; 642 643 enum { 644 TCP_IP6_CLEAR_DTIME, 645 TCP_IP4, 646 TCP_IP6, 647 UDP_IP4, 648 UDP_IP6, 649 TCP_IP4_RT_FWD, 650 TCP_IP6_RT_FWD, 651 UDP_IP4_RT_FWD, 652 UDP_IP6_RT_FWD, 653 UKN_TEST, 654 __NR_TESTS, 655 }; 656 657 const char *test_names[] = { 658 "tcp ip6 clear dtime", 659 "tcp ip4", 660 "tcp ip6", 661 "udp ip4", 662 "udp ip6", 663 "tcp ip4 rt fwd", 664 "tcp ip6 rt fwd", 665 "udp ip4 rt fwd", 666 "udp ip6 rt fwd", 667 }; 668 669 static const char *dtime_cnt_str(int test, int cnt) 670 { 671 static char name[64]; 672 673 snprintf(name, sizeof(name), "%s %s", test_names[test], cnt_names[cnt]); 674 675 return name; 676 } 677 678 static const char *dtime_err_str(int test, int cnt) 679 { 680 static char name[64]; 681 682 snprintf(name, sizeof(name), "%s %s errs", test_names[test], 683 cnt_names[cnt]); 684 685 return name; 686 } 687 688 static void test_tcp_clear_dtime(struct test_tc_dtime *skel) 689 { 690 int i, t = TCP_IP6_CLEAR_DTIME; 691 __u32 *dtimes = skel->bss->dtimes[t]; 692 __u32 *errs = skel->bss->errs[t]; 693 694 skel->bss->test = t; 695 test_inet_dtime(AF_INET6, SOCK_STREAM, IP6_DST, 50000 + t); 696 697 ASSERT_EQ(dtimes[INGRESS_FWDNS_P100], 0, 698 dtime_cnt_str(t, INGRESS_FWDNS_P100)); 699 ASSERT_EQ(dtimes[INGRESS_FWDNS_P101], 0, 700 dtime_cnt_str(t, INGRESS_FWDNS_P101)); 701 ASSERT_GT(dtimes[EGRESS_FWDNS_P100], 0, 702 dtime_cnt_str(t, EGRESS_FWDNS_P100)); 703 ASSERT_EQ(dtimes[EGRESS_FWDNS_P101], 0, 704 dtime_cnt_str(t, EGRESS_FWDNS_P101)); 705 ASSERT_GT(dtimes[EGRESS_ENDHOST], 0, 706 dtime_cnt_str(t, EGRESS_ENDHOST)); 707 ASSERT_GT(dtimes[INGRESS_ENDHOST], 0, 708 dtime_cnt_str(t, INGRESS_ENDHOST)); 709 710 for (i = INGRESS_FWDNS_P100; i < __MAX_CNT; i++) 711 ASSERT_EQ(errs[i], 0, dtime_err_str(t, i)); 712 } 713 714 static void test_tcp_dtime(struct test_tc_dtime *skel, int family, bool bpf_fwd) 715 { 716 __u32 *dtimes, *errs; 717 const char *addr; 718 int i, t; 719 720 if (family == AF_INET) { 721 t = bpf_fwd ? TCP_IP4 : TCP_IP4_RT_FWD; 722 addr = IP4_DST; 723 } else { 724 t = bpf_fwd ? TCP_IP6 : TCP_IP6_RT_FWD; 725 addr = IP6_DST; 726 } 727 728 dtimes = skel->bss->dtimes[t]; 729 errs = skel->bss->errs[t]; 730 731 skel->bss->test = t; 732 test_inet_dtime(family, SOCK_STREAM, addr, 50000 + t); 733 734 /* fwdns_prio100 prog does not read delivery_time_type, so 735 * kernel puts the (rcv) timetamp in __sk_buff->tstamp 736 */ 737 ASSERT_EQ(dtimes[INGRESS_FWDNS_P100], 0, 738 dtime_cnt_str(t, INGRESS_FWDNS_P100)); 739 for (i = INGRESS_FWDNS_P101; i < SET_DTIME; i++) 740 ASSERT_GT(dtimes[i], 0, dtime_cnt_str(t, i)); 741 742 for (i = INGRESS_FWDNS_P100; i < __MAX_CNT; i++) 743 ASSERT_EQ(errs[i], 0, dtime_err_str(t, i)); 744 } 745 746 static void test_udp_dtime(struct test_tc_dtime *skel, int family, bool bpf_fwd) 747 { 748 __u32 *dtimes, *errs; 749 const char *addr; 750 int i, t; 751 752 if (family == AF_INET) { 753 t = bpf_fwd ? UDP_IP4 : UDP_IP4_RT_FWD; 754 addr = IP4_DST; 755 } else { 756 t = bpf_fwd ? UDP_IP6 : UDP_IP6_RT_FWD; 757 addr = IP6_DST; 758 } 759 760 dtimes = skel->bss->dtimes[t]; 761 errs = skel->bss->errs[t]; 762 763 skel->bss->test = t; 764 test_inet_dtime(family, SOCK_DGRAM, addr, 50000 + t); 765 766 ASSERT_EQ(dtimes[INGRESS_FWDNS_P100], 0, 767 dtime_cnt_str(t, INGRESS_FWDNS_P100)); 768 /* non mono delivery time is not forwarded */ 769 ASSERT_EQ(dtimes[INGRESS_FWDNS_P101], 0, 770 dtime_cnt_str(t, INGRESS_FWDNS_P101)); 771 for (i = EGRESS_FWDNS_P100; i < SET_DTIME; i++) 772 ASSERT_GT(dtimes[i], 0, dtime_cnt_str(t, i)); 773 774 for (i = INGRESS_FWDNS_P100; i < __MAX_CNT; i++) 775 ASSERT_EQ(errs[i], 0, dtime_err_str(t, i)); 776 } 777 778 static void test_tc_redirect_dtime(struct netns_setup_result *setup_result) 779 { 780 struct test_tc_dtime *skel; 781 struct nstoken *nstoken; 782 int err; 783 784 skel = test_tc_dtime__open(); 785 if (!ASSERT_OK_PTR(skel, "test_tc_dtime__open")) 786 return; 787 788 skel->rodata->IFINDEX_SRC = setup_result->ifindex_veth_src_fwd; 789 skel->rodata->IFINDEX_DST = setup_result->ifindex_veth_dst_fwd; 790 791 err = test_tc_dtime__load(skel); 792 if (!ASSERT_OK(err, "test_tc_dtime__load")) 793 goto done; 794 795 if (netns_load_dtime_bpf(skel, setup_result)) 796 goto done; 797 798 nstoken = open_netns(NS_FWD); 799 if (!ASSERT_OK_PTR(nstoken, "setns fwd")) 800 goto done; 801 err = set_forwarding(false); 802 close_netns(nstoken); 803 if (!ASSERT_OK(err, "disable forwarding")) 804 goto done; 805 806 test_tcp_clear_dtime(skel); 807 808 test_tcp_dtime(skel, AF_INET, true); 809 test_tcp_dtime(skel, AF_INET6, true); 810 test_udp_dtime(skel, AF_INET, true); 811 test_udp_dtime(skel, AF_INET6, true); 812 813 /* Test the kernel ip[6]_forward path instead 814 * of bpf_redirect_neigh(). 815 */ 816 nstoken = open_netns(NS_FWD); 817 if (!ASSERT_OK_PTR(nstoken, "setns fwd")) 818 goto done; 819 err = set_forwarding(true); 820 close_netns(nstoken); 821 if (!ASSERT_OK(err, "enable forwarding")) 822 goto done; 823 824 test_tcp_dtime(skel, AF_INET, false); 825 test_tcp_dtime(skel, AF_INET6, false); 826 test_udp_dtime(skel, AF_INET, false); 827 test_udp_dtime(skel, AF_INET6, false); 828 829 done: 830 test_tc_dtime__destroy(skel); 831 } 832 833 static void test_tc_redirect_neigh_fib(struct netns_setup_result *setup_result) 834 { 835 struct nstoken *nstoken = NULL; 836 struct test_tc_neigh_fib *skel = NULL; 837 838 nstoken = open_netns(NS_FWD); 839 if (!ASSERT_OK_PTR(nstoken, "setns fwd")) 840 return; 841 842 skel = test_tc_neigh_fib__open(); 843 if (!ASSERT_OK_PTR(skel, "test_tc_neigh_fib__open")) 844 goto done; 845 846 if (!ASSERT_OK(test_tc_neigh_fib__load(skel), "test_tc_neigh_fib__load")) 847 goto done; 848 849 if (netns_load_bpf(skel->progs.tc_src, skel->progs.tc_dst, 850 skel->progs.tc_chk, setup_result)) 851 goto done; 852 853 /* bpf_fib_lookup() checks if forwarding is enabled */ 854 if (!ASSERT_OK(set_forwarding(true), "enable forwarding")) 855 goto done; 856 857 test_connectivity(); 858 859 done: 860 if (skel) 861 test_tc_neigh_fib__destroy(skel); 862 close_netns(nstoken); 863 } 864 865 static void test_tc_redirect_neigh(struct netns_setup_result *setup_result) 866 { 867 struct nstoken *nstoken = NULL; 868 struct test_tc_neigh *skel = NULL; 869 int err; 870 871 nstoken = open_netns(NS_FWD); 872 if (!ASSERT_OK_PTR(nstoken, "setns fwd")) 873 return; 874 875 skel = test_tc_neigh__open(); 876 if (!ASSERT_OK_PTR(skel, "test_tc_neigh__open")) 877 goto done; 878 879 skel->rodata->IFINDEX_SRC = setup_result->ifindex_veth_src_fwd; 880 skel->rodata->IFINDEX_DST = setup_result->ifindex_veth_dst_fwd; 881 882 err = test_tc_neigh__load(skel); 883 if (!ASSERT_OK(err, "test_tc_neigh__load")) 884 goto done; 885 886 if (netns_load_bpf(skel->progs.tc_src, skel->progs.tc_dst, 887 skel->progs.tc_chk, setup_result)) 888 goto done; 889 890 if (!ASSERT_OK(set_forwarding(false), "disable forwarding")) 891 goto done; 892 893 test_connectivity(); 894 895 done: 896 if (skel) 897 test_tc_neigh__destroy(skel); 898 close_netns(nstoken); 899 } 900 901 static void test_tc_redirect_peer(struct netns_setup_result *setup_result) 902 { 903 struct nstoken *nstoken; 904 struct test_tc_peer *skel; 905 int err; 906 907 nstoken = open_netns(NS_FWD); 908 if (!ASSERT_OK_PTR(nstoken, "setns fwd")) 909 return; 910 911 skel = test_tc_peer__open(); 912 if (!ASSERT_OK_PTR(skel, "test_tc_peer__open")) 913 goto done; 914 915 skel->rodata->IFINDEX_SRC = setup_result->ifindex_veth_src_fwd; 916 skel->rodata->IFINDEX_DST = setup_result->ifindex_veth_dst_fwd; 917 918 err = test_tc_peer__load(skel); 919 if (!ASSERT_OK(err, "test_tc_peer__load")) 920 goto done; 921 922 if (netns_load_bpf(skel->progs.tc_src, skel->progs.tc_dst, 923 skel->progs.tc_chk, setup_result)) 924 goto done; 925 926 if (!ASSERT_OK(set_forwarding(false), "disable forwarding")) 927 goto done; 928 929 test_connectivity(); 930 931 done: 932 if (skel) 933 test_tc_peer__destroy(skel); 934 close_netns(nstoken); 935 } 936 937 static int tun_open(char *name) 938 { 939 struct ifreq ifr; 940 int fd, err; 941 942 fd = open("/dev/net/tun", O_RDWR); 943 if (!ASSERT_GE(fd, 0, "open /dev/net/tun")) 944 return -1; 945 946 memset(&ifr, 0, sizeof(ifr)); 947 948 ifr.ifr_flags = IFF_TUN | IFF_NO_PI; 949 if (*name) 950 strncpy(ifr.ifr_name, name, IFNAMSIZ); 951 952 err = ioctl(fd, TUNSETIFF, &ifr); 953 if (!ASSERT_OK(err, "ioctl TUNSETIFF")) 954 goto fail; 955 956 SYS("ip link set dev %s up", name); 957 958 return fd; 959 fail: 960 close(fd); 961 return -1; 962 } 963 964 enum { 965 SRC_TO_TARGET = 0, 966 TARGET_TO_SRC = 1, 967 }; 968 969 static int tun_relay_loop(int src_fd, int target_fd) 970 { 971 fd_set rfds, wfds; 972 973 FD_ZERO(&rfds); 974 FD_ZERO(&wfds); 975 976 for (;;) { 977 char buf[1500]; 978 int direction, nread, nwrite; 979 980 FD_SET(src_fd, &rfds); 981 FD_SET(target_fd, &rfds); 982 983 if (select(1 + MAX(src_fd, target_fd), &rfds, NULL, NULL, NULL) < 0) { 984 log_err("select failed"); 985 return 1; 986 } 987 988 direction = FD_ISSET(src_fd, &rfds) ? SRC_TO_TARGET : TARGET_TO_SRC; 989 990 nread = read(direction == SRC_TO_TARGET ? src_fd : target_fd, buf, sizeof(buf)); 991 if (nread < 0) { 992 log_err("read failed"); 993 return 1; 994 } 995 996 nwrite = write(direction == SRC_TO_TARGET ? target_fd : src_fd, buf, nread); 997 if (nwrite != nread) { 998 log_err("write failed"); 999 return 1; 1000 } 1001 } 1002 } 1003 1004 static void test_tc_redirect_peer_l3(struct netns_setup_result *setup_result) 1005 { 1006 LIBBPF_OPTS(bpf_tc_hook, qdisc_tun_fwd); 1007 LIBBPF_OPTS(bpf_tc_hook, qdisc_veth_dst_fwd); 1008 struct test_tc_peer *skel = NULL; 1009 struct nstoken *nstoken = NULL; 1010 int err; 1011 int tunnel_pid = -1; 1012 int src_fd, target_fd = -1; 1013 int ifindex; 1014 1015 /* Start a L3 TUN/TAP tunnel between the src and dst namespaces. 1016 * This test is using TUN/TAP instead of e.g. IPIP or GRE tunnel as those 1017 * expose the L2 headers encapsulating the IP packet to BPF and hence 1018 * don't have skb in suitable state for this test. Alternative to TUN/TAP 1019 * would be e.g. Wireguard which would appear as a pure L3 device to BPF, 1020 * but that requires much more complicated setup. 1021 */ 1022 nstoken = open_netns(NS_SRC); 1023 if (!ASSERT_OK_PTR(nstoken, "setns " NS_SRC)) 1024 return; 1025 1026 src_fd = tun_open("tun_src"); 1027 if (!ASSERT_GE(src_fd, 0, "tun_open tun_src")) 1028 goto fail; 1029 1030 close_netns(nstoken); 1031 1032 nstoken = open_netns(NS_FWD); 1033 if (!ASSERT_OK_PTR(nstoken, "setns " NS_FWD)) 1034 goto fail; 1035 1036 target_fd = tun_open("tun_fwd"); 1037 if (!ASSERT_GE(target_fd, 0, "tun_open tun_fwd")) 1038 goto fail; 1039 1040 tunnel_pid = fork(); 1041 if (!ASSERT_GE(tunnel_pid, 0, "fork tun_relay_loop")) 1042 goto fail; 1043 1044 if (tunnel_pid == 0) 1045 exit(tun_relay_loop(src_fd, target_fd)); 1046 1047 skel = test_tc_peer__open(); 1048 if (!ASSERT_OK_PTR(skel, "test_tc_peer__open")) 1049 goto fail; 1050 1051 ifindex = if_nametoindex("tun_fwd"); 1052 if (!ASSERT_GT(ifindex, 0, "if_indextoname tun_fwd")) 1053 goto fail; 1054 1055 skel->rodata->IFINDEX_SRC = ifindex; 1056 skel->rodata->IFINDEX_DST = setup_result->ifindex_veth_dst_fwd; 1057 1058 err = test_tc_peer__load(skel); 1059 if (!ASSERT_OK(err, "test_tc_peer__load")) 1060 goto fail; 1061 1062 /* Load "tc_src_l3" to the tun_fwd interface to redirect packets 1063 * towards dst, and "tc_dst" to redirect packets 1064 * and "tc_chk" on veth_dst_fwd to drop non-redirected packets. 1065 */ 1066 /* tc qdisc add dev tun_fwd clsact */ 1067 QDISC_CLSACT_CREATE(&qdisc_tun_fwd, ifindex); 1068 /* tc filter add dev tun_fwd ingress bpf da tc_src_l3 */ 1069 XGRESS_FILTER_ADD(&qdisc_tun_fwd, BPF_TC_INGRESS, skel->progs.tc_src_l3, 0); 1070 1071 /* tc qdisc add dev veth_dst_fwd clsact */ 1072 QDISC_CLSACT_CREATE(&qdisc_veth_dst_fwd, setup_result->ifindex_veth_dst_fwd); 1073 /* tc filter add dev veth_dst_fwd ingress bpf da tc_dst_l3 */ 1074 XGRESS_FILTER_ADD(&qdisc_veth_dst_fwd, BPF_TC_INGRESS, skel->progs.tc_dst_l3, 0); 1075 /* tc filter add dev veth_dst_fwd egress bpf da tc_chk */ 1076 XGRESS_FILTER_ADD(&qdisc_veth_dst_fwd, BPF_TC_EGRESS, skel->progs.tc_chk, 0); 1077 1078 /* Setup route and neigh tables */ 1079 SYS("ip -netns " NS_SRC " addr add dev tun_src " IP4_TUN_SRC "/24"); 1080 SYS("ip -netns " NS_FWD " addr add dev tun_fwd " IP4_TUN_FWD "/24"); 1081 1082 SYS("ip -netns " NS_SRC " addr add dev tun_src " IP6_TUN_SRC "/64 nodad"); 1083 SYS("ip -netns " NS_FWD " addr add dev tun_fwd " IP6_TUN_FWD "/64 nodad"); 1084 1085 SYS("ip -netns " NS_SRC " route del " IP4_DST "/32 dev veth_src scope global"); 1086 SYS("ip -netns " NS_SRC " route add " IP4_DST "/32 via " IP4_TUN_FWD 1087 " dev tun_src scope global"); 1088 SYS("ip -netns " NS_DST " route add " IP4_TUN_SRC "/32 dev veth_dst scope global"); 1089 SYS("ip -netns " NS_SRC " route del " IP6_DST "/128 dev veth_src scope global"); 1090 SYS("ip -netns " NS_SRC " route add " IP6_DST "/128 via " IP6_TUN_FWD 1091 " dev tun_src scope global"); 1092 SYS("ip -netns " NS_DST " route add " IP6_TUN_SRC "/128 dev veth_dst scope global"); 1093 1094 SYS("ip -netns " NS_DST " neigh add " IP4_TUN_SRC " dev veth_dst lladdr " MAC_DST_FWD); 1095 SYS("ip -netns " NS_DST " neigh add " IP6_TUN_SRC " dev veth_dst lladdr " MAC_DST_FWD); 1096 1097 if (!ASSERT_OK(set_forwarding(false), "disable forwarding")) 1098 goto fail; 1099 1100 test_connectivity(); 1101 1102 fail: 1103 if (tunnel_pid > 0) { 1104 kill(tunnel_pid, SIGTERM); 1105 waitpid(tunnel_pid, NULL, 0); 1106 } 1107 if (src_fd >= 0) 1108 close(src_fd); 1109 if (target_fd >= 0) 1110 close(target_fd); 1111 if (skel) 1112 test_tc_peer__destroy(skel); 1113 if (nstoken) 1114 close_netns(nstoken); 1115 } 1116 1117 #define RUN_TEST(name) \ 1118 ({ \ 1119 struct netns_setup_result setup_result; \ 1120 if (test__start_subtest(#name)) \ 1121 if (ASSERT_OK(netns_setup_namespaces("add"), "setup namespaces")) { \ 1122 if (ASSERT_OK(netns_setup_links_and_routes(&setup_result), \ 1123 "setup links and routes")) \ 1124 test_ ## name(&setup_result); \ 1125 netns_setup_namespaces("delete"); \ 1126 } \ 1127 }) 1128 1129 static void *test_tc_redirect_run_tests(void *arg) 1130 { 1131 netns_setup_namespaces_nofail("delete"); 1132 1133 RUN_TEST(tc_redirect_peer); 1134 RUN_TEST(tc_redirect_peer_l3); 1135 RUN_TEST(tc_redirect_neigh); 1136 RUN_TEST(tc_redirect_neigh_fib); 1137 RUN_TEST(tc_redirect_dtime); 1138 return NULL; 1139 } 1140 1141 void test_tc_redirect(void) 1142 { 1143 pthread_t test_thread; 1144 int err; 1145 1146 /* Run the tests in their own thread to isolate the namespace changes 1147 * so they do not affect the environment of other tests. 1148 * (specifically needed because of unshare(CLONE_NEWNS) in open_netns()) 1149 */ 1150 err = pthread_create(&test_thread, NULL, &test_tc_redirect_run_tests, NULL); 1151 if (ASSERT_OK(err, "pthread_create")) 1152 ASSERT_OK(pthread_join(test_thread, NULL), "pthread_join"); 1153 } 1154