| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* Copyright (c) 2021 Facebook */ |
| 3 | #define _GNU_SOURCE |
| 4 | #include <sched.h> |
| 5 | #include <test_progs.h> |
| 6 | #include "network_helpers.h" |
| 7 | #include "bpf_dctcp.skel.h" |
| 8 | #include "bpf_cubic.skel.h" |
| 9 | #include "bpf_iter_setsockopt.skel.h" |
| 10 | |
| 11 | static int create_netns(void) |
| 12 | { |
| 13 | if (!ASSERT_OK(unshare(CLONE_NEWNET), "create netns" )) |
| 14 | return -1; |
| 15 | |
| 16 | if (!ASSERT_OK(system("ip link set dev lo up" ), "bring up lo" )) |
| 17 | return -1; |
| 18 | |
| 19 | return 0; |
| 20 | } |
| 21 | |
| 22 | static unsigned int set_bpf_cubic(int *fds, unsigned int nr_fds) |
| 23 | { |
| 24 | unsigned int i; |
| 25 | |
| 26 | for (i = 0; i < nr_fds; i++) { |
| 27 | if (setsockopt(fds[i], SOL_TCP, TCP_CONGESTION, "bpf_cubic" , |
| 28 | sizeof("bpf_cubic" ))) |
| 29 | return i; |
| 30 | } |
| 31 | |
| 32 | return nr_fds; |
| 33 | } |
| 34 | |
| 35 | static unsigned int check_bpf_dctcp(int *fds, unsigned int nr_fds) |
| 36 | { |
| 37 | char tcp_cc[16]; |
| 38 | socklen_t optlen = sizeof(tcp_cc); |
| 39 | unsigned int i; |
| 40 | |
| 41 | for (i = 0; i < nr_fds; i++) { |
| 42 | if (getsockopt(fds[i], SOL_TCP, TCP_CONGESTION, |
| 43 | tcp_cc, &optlen) || |
| 44 | strcmp(tcp_cc, "bpf_dctcp" )) |
| 45 | return i; |
| 46 | } |
| 47 | |
| 48 | return nr_fds; |
| 49 | } |
| 50 | |
| 51 | static int *make_established(int listen_fd, unsigned int nr_est, |
| 52 | int **paccepted_fds) |
| 53 | { |
| 54 | int *est_fds, *accepted_fds; |
| 55 | unsigned int i; |
| 56 | |
| 57 | est_fds = malloc(sizeof(*est_fds) * nr_est); |
| 58 | if (!est_fds) |
| 59 | return NULL; |
| 60 | |
| 61 | accepted_fds = malloc(sizeof(*accepted_fds) * nr_est); |
| 62 | if (!accepted_fds) { |
| 63 | free(est_fds); |
| 64 | return NULL; |
| 65 | } |
| 66 | |
| 67 | for (i = 0; i < nr_est; i++) { |
| 68 | est_fds[i] = connect_to_fd(listen_fd, 0); |
| 69 | if (est_fds[i] == -1) |
| 70 | break; |
| 71 | if (set_bpf_cubic(fds: &est_fds[i], nr_fds: 1) != 1) { |
| 72 | close(est_fds[i]); |
| 73 | break; |
| 74 | } |
| 75 | |
| 76 | accepted_fds[i] = accept(listen_fd, NULL, 0); |
| 77 | if (accepted_fds[i] == -1) { |
| 78 | close(est_fds[i]); |
| 79 | break; |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | if (!ASSERT_EQ(i, nr_est, "create established fds" )) { |
| 84 | free_fds(accepted_fds, i); |
| 85 | free_fds(est_fds, i); |
| 86 | return NULL; |
| 87 | } |
| 88 | |
| 89 | *paccepted_fds = accepted_fds; |
| 90 | return est_fds; |
| 91 | } |
| 92 | |
| 93 | static unsigned short get_local_port(int fd) |
| 94 | { |
| 95 | struct sockaddr_in6 addr; |
| 96 | socklen_t addrlen = sizeof(addr); |
| 97 | |
| 98 | if (!getsockname(fd, (struct sockaddr *)&addr, &addrlen)) |
| 99 | return ntohs(addr.sin6_port); |
| 100 | |
| 101 | return 0; |
| 102 | } |
| 103 | |
| 104 | static void do_bpf_iter_setsockopt(struct bpf_iter_setsockopt *iter_skel, |
| 105 | bool random_retry) |
| 106 | { |
| 107 | int *reuse_listen_fds = NULL, *accepted_fds = NULL, *est_fds = NULL; |
| 108 | unsigned int nr_reuse_listens = 256, nr_est = 256; |
| 109 | int err, iter_fd = -1, listen_fd = -1; |
| 110 | char buf; |
| 111 | |
| 112 | /* Prepare non-reuseport listen_fd */ |
| 113 | listen_fd = start_server(AF_INET6, SOCK_STREAM, "::1" , 0, 0); |
| 114 | if (!ASSERT_GE(listen_fd, 0, "start_server" )) |
| 115 | return; |
| 116 | if (!ASSERT_EQ(set_bpf_cubic(fds: &listen_fd, nr_fds: 1), 1, |
| 117 | "set listen_fd to cubic" )) |
| 118 | goto done; |
| 119 | iter_skel->bss->listen_hport = get_local_port(fd: listen_fd); |
| 120 | if (!ASSERT_NEQ(iter_skel->bss->listen_hport, 0, |
| 121 | "get_local_port(listen_fd)" )) |
| 122 | goto done; |
| 123 | |
| 124 | /* Connect to non-reuseport listen_fd */ |
| 125 | est_fds = make_established(listen_fd, nr_est, paccepted_fds: &accepted_fds); |
| 126 | if (!ASSERT_OK_PTR(est_fds, "create established" )) |
| 127 | goto done; |
| 128 | |
| 129 | /* Prepare reuseport listen fds */ |
| 130 | reuse_listen_fds = start_reuseport_server(AF_INET6, SOCK_STREAM, |
| 131 | "::1" , 0, 0, |
| 132 | nr_reuse_listens); |
| 133 | if (!ASSERT_OK_PTR(reuse_listen_fds, "start_reuseport_server" )) |
| 134 | goto done; |
| 135 | if (!ASSERT_EQ(set_bpf_cubic(fds: reuse_listen_fds, nr_fds: nr_reuse_listens), |
| 136 | nr_reuse_listens, "set reuse_listen_fds to cubic" )) |
| 137 | goto done; |
| 138 | iter_skel->bss->reuse_listen_hport = get_local_port(fd: reuse_listen_fds[0]); |
| 139 | if (!ASSERT_NEQ(iter_skel->bss->reuse_listen_hport, 0, |
| 140 | "get_local_port(reuse_listen_fds[0])" )) |
| 141 | goto done; |
| 142 | |
| 143 | /* Run bpf tcp iter to switch from bpf_cubic to bpf_dctcp */ |
| 144 | iter_skel->bss->random_retry = random_retry; |
| 145 | iter_fd = bpf_iter_create(bpf_link__fd(iter_skel->links.change_tcp_cc)); |
| 146 | if (!ASSERT_GE(iter_fd, 0, "create iter_fd" )) |
| 147 | goto done; |
| 148 | |
| 149 | while ((err = read(iter_fd, &buf, sizeof(buf))) == -1 && |
| 150 | errno == EAGAIN) |
| 151 | ; |
| 152 | if (!ASSERT_OK(err, "read iter error" )) |
| 153 | goto done; |
| 154 | |
| 155 | /* Check reuseport listen fds for dctcp */ |
| 156 | ASSERT_EQ(check_bpf_dctcp(fds: reuse_listen_fds, nr_fds: nr_reuse_listens), |
| 157 | nr_reuse_listens, |
| 158 | "check reuse_listen_fds dctcp" ); |
| 159 | |
| 160 | /* Check non reuseport listen fd for dctcp */ |
| 161 | ASSERT_EQ(check_bpf_dctcp(fds: &listen_fd, nr_fds: 1), 1, |
| 162 | "check listen_fd dctcp" ); |
| 163 | |
| 164 | /* Check established fds for dctcp */ |
| 165 | ASSERT_EQ(check_bpf_dctcp(fds: est_fds, nr_fds: nr_est), nr_est, |
| 166 | "check est_fds dctcp" ); |
| 167 | |
| 168 | /* Check accepted fds for dctcp */ |
| 169 | ASSERT_EQ(check_bpf_dctcp(fds: accepted_fds, nr_fds: nr_est), nr_est, |
| 170 | "check accepted_fds dctcp" ); |
| 171 | |
| 172 | done: |
| 173 | if (iter_fd != -1) |
| 174 | close(iter_fd); |
| 175 | if (listen_fd != -1) |
| 176 | close(listen_fd); |
| 177 | free_fds(reuse_listen_fds, nr_reuse_listens); |
| 178 | free_fds(accepted_fds, nr_est); |
| 179 | free_fds(est_fds, nr_est); |
| 180 | } |
| 181 | |
| 182 | void serial_test_bpf_iter_setsockopt(void) |
| 183 | { |
| 184 | struct bpf_iter_setsockopt *iter_skel = NULL; |
| 185 | struct bpf_cubic *cubic_skel = NULL; |
| 186 | struct bpf_dctcp *dctcp_skel = NULL; |
| 187 | struct bpf_link *cubic_link = NULL; |
| 188 | struct bpf_link *dctcp_link = NULL; |
| 189 | |
| 190 | if (create_netns()) |
| 191 | return; |
| 192 | |
| 193 | /* Load iter_skel */ |
| 194 | iter_skel = bpf_iter_setsockopt__open_and_load(); |
| 195 | if (!ASSERT_OK_PTR(iter_skel, "iter_skel" )) |
| 196 | return; |
| 197 | iter_skel->links.change_tcp_cc = bpf_program__attach_iter(iter_skel->progs.change_tcp_cc, NULL); |
| 198 | if (!ASSERT_OK_PTR(iter_skel->links.change_tcp_cc, "attach iter" )) |
| 199 | goto done; |
| 200 | |
| 201 | /* Load bpf_cubic */ |
| 202 | cubic_skel = bpf_cubic__open_and_load(); |
| 203 | if (!ASSERT_OK_PTR(cubic_skel, "cubic_skel" )) |
| 204 | goto done; |
| 205 | cubic_link = bpf_map__attach_struct_ops(cubic_skel->maps.cubic); |
| 206 | if (!ASSERT_OK_PTR(cubic_link, "cubic_link" )) |
| 207 | goto done; |
| 208 | |
| 209 | /* Load bpf_dctcp */ |
| 210 | dctcp_skel = bpf_dctcp__open_and_load(); |
| 211 | if (!ASSERT_OK_PTR(dctcp_skel, "dctcp_skel" )) |
| 212 | goto done; |
| 213 | dctcp_link = bpf_map__attach_struct_ops(dctcp_skel->maps.dctcp); |
| 214 | if (!ASSERT_OK_PTR(dctcp_link, "dctcp_link" )) |
| 215 | goto done; |
| 216 | |
| 217 | do_bpf_iter_setsockopt(iter_skel, true); |
| 218 | do_bpf_iter_setsockopt(iter_skel, false); |
| 219 | |
| 220 | done: |
| 221 | bpf_link__destroy(cubic_link); |
| 222 | bpf_link__destroy(dctcp_link); |
| 223 | bpf_cubic__destroy(cubic_skel); |
| 224 | bpf_dctcp__destroy(dctcp_skel); |
| 225 | bpf_iter_setsockopt__destroy(iter_skel); |
| 226 | } |
| 227 | |