| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2023 Isovalent */ |
| #include <uapi/linux/if_link.h> |
| #include <test_progs.h> |
| |
| #include <netinet/tcp.h> |
| #include <netinet/udp.h> |
| |
| #include "network_helpers.h" |
| #include "test_assign_reuse.skel.h" |
| |
| #define NS_TEST "assign_reuse" |
| #define LOOPBACK 1 |
| #define PORT 4443 |
| |
| static int attach_reuseport(int sock_fd, int prog_fd) |
| { |
| return setsockopt(sock_fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, |
| &prog_fd, sizeof(prog_fd)); |
| } |
| |
| static __u64 cookie(int fd) |
| { |
| __u64 cookie = 0; |
| socklen_t cookie_len = sizeof(cookie); |
| int ret; |
| |
| ret = getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &cookie_len); |
| ASSERT_OK(ret, "cookie"); |
| ASSERT_GT(cookie, 0, "cookie_invalid"); |
| |
| return cookie; |
| } |
| |
| static int echo_test_udp(int fd_sv) |
| { |
| struct sockaddr_storage addr = {}; |
| socklen_t len = sizeof(addr); |
| char buff[1] = {}; |
| int fd_cl = -1, ret; |
| |
| fd_cl = connect_to_fd(fd_sv, 100); |
| ASSERT_GT(fd_cl, 0, "create_client"); |
| ASSERT_EQ(getsockname(fd_cl, (void *)&addr, &len), 0, "getsockname"); |
| |
| ASSERT_EQ(send(fd_cl, buff, sizeof(buff), 0), 1, "send_client"); |
| |
| ret = recv(fd_sv, buff, sizeof(buff), 0); |
| if (ret < 0) { |
| close(fd_cl); |
| return errno; |
| } |
| |
| ASSERT_EQ(ret, 1, "recv_server"); |
| ASSERT_EQ(sendto(fd_sv, buff, sizeof(buff), 0, (void *)&addr, len), 1, "send_server"); |
| ASSERT_EQ(recv(fd_cl, buff, sizeof(buff), 0), 1, "recv_client"); |
| close(fd_cl); |
| return 0; |
| } |
| |
| static int echo_test_tcp(int fd_sv) |
| { |
| char buff[1] = {}; |
| int fd_cl = -1, fd_sv_cl = -1; |
| |
| fd_cl = connect_to_fd(fd_sv, 100); |
| if (fd_cl < 0) |
| return errno; |
| |
| fd_sv_cl = accept(fd_sv, NULL, NULL); |
| ASSERT_GE(fd_sv_cl, 0, "accept_fd"); |
| |
| ASSERT_EQ(send(fd_cl, buff, sizeof(buff), 0), 1, "send_client"); |
| ASSERT_EQ(recv(fd_sv_cl, buff, sizeof(buff), 0), 1, "recv_server"); |
| ASSERT_EQ(send(fd_sv_cl, buff, sizeof(buff), 0), 1, "send_server"); |
| ASSERT_EQ(recv(fd_cl, buff, sizeof(buff), 0), 1, "recv_client"); |
| close(fd_sv_cl); |
| close(fd_cl); |
| return 0; |
| } |
| |
| void run_assign_reuse(int family, int sotype, const char *ip, __u16 port) |
| { |
| DECLARE_LIBBPF_OPTS(bpf_tc_hook, tc_hook, |
| .ifindex = LOOPBACK, |
| .attach_point = BPF_TC_INGRESS, |
| ); |
| DECLARE_LIBBPF_OPTS(bpf_tc_opts, tc_opts, |
| .handle = 1, |
| .priority = 1, |
| ); |
| bool hook_created = false, tc_attached = false; |
| int ret, fd_tc, fd_accept, fd_drop, fd_map; |
| int *fd_sv = NULL; |
| __u64 fd_val; |
| struct test_assign_reuse *skel; |
| const int zero = 0; |
| |
| skel = test_assign_reuse__open(); |
| if (!ASSERT_OK_PTR(skel, "skel_open")) |
| goto cleanup; |
| |
| skel->rodata->dest_port = port; |
| |
| ret = test_assign_reuse__load(skel); |
| if (!ASSERT_OK(ret, "skel_load")) |
| goto cleanup; |
| |
| ASSERT_EQ(skel->bss->sk_cookie_seen, 0, "cookie_init"); |
| |
| fd_tc = bpf_program__fd(skel->progs.tc_main); |
| fd_accept = bpf_program__fd(skel->progs.reuse_accept); |
| fd_drop = bpf_program__fd(skel->progs.reuse_drop); |
| fd_map = bpf_map__fd(skel->maps.sk_map); |
| |
| fd_sv = start_reuseport_server(family, sotype, ip, port, 100, 1); |
| if (!ASSERT_NEQ(fd_sv, NULL, "start_reuseport_server")) |
| goto cleanup; |
| |
| ret = attach_reuseport(*fd_sv, fd_drop); |
| if (!ASSERT_OK(ret, "attach_reuseport")) |
| goto cleanup; |
| |
| fd_val = *fd_sv; |
| ret = bpf_map_update_elem(fd_map, &zero, &fd_val, BPF_NOEXIST); |
| if (!ASSERT_OK(ret, "bpf_sk_map")) |
| goto cleanup; |
| |
| ret = bpf_tc_hook_create(&tc_hook); |
| if (ret == 0) |
| hook_created = true; |
| ret = ret == -EEXIST ? 0 : ret; |
| if (!ASSERT_OK(ret, "bpf_tc_hook_create")) |
| goto cleanup; |
| |
| tc_opts.prog_fd = fd_tc; |
| ret = bpf_tc_attach(&tc_hook, &tc_opts); |
| if (!ASSERT_OK(ret, "bpf_tc_attach")) |
| goto cleanup; |
| tc_attached = true; |
| |
| if (sotype == SOCK_STREAM) |
| ASSERT_EQ(echo_test_tcp(*fd_sv), ECONNREFUSED, "drop_tcp"); |
| else |
| ASSERT_EQ(echo_test_udp(*fd_sv), EAGAIN, "drop_udp"); |
| ASSERT_EQ(skel->bss->reuseport_executed, 1, "program executed once"); |
| |
| skel->bss->sk_cookie_seen = 0; |
| skel->bss->reuseport_executed = 0; |
| ASSERT_OK(attach_reuseport(*fd_sv, fd_accept), "attach_reuseport(accept)"); |
| |
| if (sotype == SOCK_STREAM) |
| ASSERT_EQ(echo_test_tcp(*fd_sv), 0, "echo_tcp"); |
| else |
| ASSERT_EQ(echo_test_udp(*fd_sv), 0, "echo_udp"); |
| |
| ASSERT_EQ(skel->bss->sk_cookie_seen, cookie(*fd_sv), |
| "cookie_mismatch"); |
| ASSERT_EQ(skel->bss->reuseport_executed, 1, "program executed once"); |
| cleanup: |
| if (tc_attached) { |
| tc_opts.flags = tc_opts.prog_fd = tc_opts.prog_id = 0; |
| ret = bpf_tc_detach(&tc_hook, &tc_opts); |
| ASSERT_OK(ret, "bpf_tc_detach"); |
| } |
| if (hook_created) { |
| tc_hook.attach_point = BPF_TC_INGRESS | BPF_TC_EGRESS; |
| bpf_tc_hook_destroy(&tc_hook); |
| } |
| test_assign_reuse__destroy(skel); |
| free_fds(fd_sv, 1); |
| } |
| |
| void test_assign_reuse(void) |
| { |
| struct nstoken *tok = NULL; |
| |
| SYS(out, "ip netns add %s", NS_TEST); |
| SYS(cleanup, "ip -net %s link set dev lo up", NS_TEST); |
| |
| tok = open_netns(NS_TEST); |
| if (!ASSERT_OK_PTR(tok, "netns token")) |
| return; |
| |
| if (test__start_subtest("tcpv4")) |
| run_assign_reuse(AF_INET, SOCK_STREAM, "127.0.0.1", PORT); |
| if (test__start_subtest("tcpv6")) |
| run_assign_reuse(AF_INET6, SOCK_STREAM, "::1", PORT); |
| if (test__start_subtest("udpv4")) |
| run_assign_reuse(AF_INET, SOCK_DGRAM, "127.0.0.1", PORT); |
| if (test__start_subtest("udpv6")) |
| run_assign_reuse(AF_INET6, SOCK_DGRAM, "::1", PORT); |
| |
| cleanup: |
| close_netns(tok); |
| SYS_NOFAIL("ip netns delete %s", NS_TEST); |
| out: |
| return; |
| } |