]>
Commit | Line | Data |
---|---|---|
2d7824ff LB |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2018 Facebook | |
3 | // Copyright (c) 2019 Cloudflare | |
4 | // Copyright (c) 2020 Isovalent, Inc. | |
5 | /* | |
6 | * Test that the socket assign program is able to redirect traffic towards a | |
7 | * socket, regardless of whether the port or address destination of the traffic | |
8 | * matches the port. | |
9 | */ | |
10 | ||
11 | #define _GNU_SOURCE | |
12 | #include <fcntl.h> | |
13 | #include <signal.h> | |
14 | #include <stdlib.h> | |
15 | #include <unistd.h> | |
16 | ||
17 | #include "test_progs.h" | |
18 | ||
19 | #define BIND_PORT 1234 | |
20 | #define CONNECT_PORT 4321 | |
21 | #define TEST_DADDR (0xC0A80203) | |
22 | #define NS_SELF "/proc/self/ns/net" | |
0b9ad56b | 23 | #define SERVER_MAP_PATH "/sys/fs/bpf/tc/globals/server_map" |
2d7824ff LB |
24 | |
25 | static const struct timeval timeo_sec = { .tv_sec = 3 }; | |
26 | static const size_t timeo_optlen = sizeof(timeo_sec); | |
27 | static int stop, duration; | |
28 | ||
29 | static bool | |
30 | configure_stack(void) | |
31 | { | |
32 | char tc_cmd[BUFSIZ]; | |
33 | ||
34 | /* Move to a new networking namespace */ | |
35 | if (CHECK_FAIL(unshare(CLONE_NEWNET))) | |
36 | return false; | |
37 | ||
38 | /* Configure necessary links, routes */ | |
39 | if (CHECK_FAIL(system("ip link set dev lo up"))) | |
40 | return false; | |
41 | if (CHECK_FAIL(system("ip route add local default dev lo"))) | |
42 | return false; | |
43 | if (CHECK_FAIL(system("ip -6 route add local default dev lo"))) | |
44 | return false; | |
45 | ||
46 | /* Load qdisc, BPF program */ | |
47 | if (CHECK_FAIL(system("tc qdisc add dev lo clsact"))) | |
48 | return false; | |
49 | sprintf(tc_cmd, "%s %s %s %s", "tc filter add dev lo ingress bpf", | |
afef88e6 | 50 | "direct-action object-file ./test_sk_assign.bpf.o", |
c22bdd28 | 51 | "section tc", |
0fcdfffe | 52 | (env.verbosity < VERBOSE_VERY) ? " 2>/dev/null" : "verbose"); |
2d7824ff LB |
53 | if (CHECK(system(tc_cmd), "BPF load failed;", |
54 | "run with -vv for more info\n")) | |
55 | return false; | |
56 | ||
57 | return true; | |
58 | } | |
59 | ||
60 | static int | |
61 | start_server(const struct sockaddr *addr, socklen_t len, int type) | |
62 | { | |
63 | int fd; | |
64 | ||
65 | fd = socket(addr->sa_family, type, 0); | |
66 | if (CHECK_FAIL(fd == -1)) | |
67 | goto out; | |
68 | if (CHECK_FAIL(setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo_sec, | |
69 | timeo_optlen))) | |
70 | goto close_out; | |
71 | if (CHECK_FAIL(bind(fd, addr, len) == -1)) | |
72 | goto close_out; | |
8a02a170 | 73 | if (type == SOCK_STREAM && CHECK_FAIL(listen(fd, 128) == -1)) |
2d7824ff LB |
74 | goto close_out; |
75 | ||
76 | goto out; | |
77 | close_out: | |
78 | close(fd); | |
79 | fd = -1; | |
80 | out: | |
81 | return fd; | |
82 | } | |
83 | ||
84 | static int | |
85 | connect_to_server(const struct sockaddr *addr, socklen_t len, int type) | |
86 | { | |
87 | int fd = -1; | |
88 | ||
89 | fd = socket(addr->sa_family, type, 0); | |
90 | if (CHECK_FAIL(fd == -1)) | |
91 | goto out; | |
92 | if (CHECK_FAIL(setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo_sec, | |
93 | timeo_optlen))) | |
94 | goto close_out; | |
95 | if (CHECK_FAIL(connect(fd, addr, len))) | |
96 | goto close_out; | |
97 | ||
98 | goto out; | |
99 | close_out: | |
100 | close(fd); | |
101 | fd = -1; | |
102 | out: | |
103 | return fd; | |
104 | } | |
105 | ||
106 | static in_port_t | |
107 | get_port(int fd) | |
108 | { | |
109 | struct sockaddr_storage ss; | |
110 | socklen_t slen = sizeof(ss); | |
111 | in_port_t port = 0; | |
112 | ||
113 | if (CHECK_FAIL(getsockname(fd, (struct sockaddr *)&ss, &slen))) | |
114 | return port; | |
115 | ||
116 | switch (ss.ss_family) { | |
117 | case AF_INET: | |
118 | port = ((struct sockaddr_in *)&ss)->sin_port; | |
119 | break; | |
120 | case AF_INET6: | |
121 | port = ((struct sockaddr_in6 *)&ss)->sin6_port; | |
122 | break; | |
123 | default: | |
124 | CHECK(1, "Invalid address family", "%d\n", ss.ss_family); | |
125 | } | |
126 | return port; | |
127 | } | |
128 | ||
8a02a170 JS |
129 | static ssize_t |
130 | rcv_msg(int srv_client, int type) | |
131 | { | |
132 | struct sockaddr_storage ss; | |
133 | char buf[BUFSIZ]; | |
134 | socklen_t slen; | |
135 | ||
136 | if (type == SOCK_STREAM) | |
137 | return read(srv_client, &buf, sizeof(buf)); | |
138 | else | |
139 | return recvfrom(srv_client, &buf, sizeof(buf), 0, | |
140 | (struct sockaddr *)&ss, &slen); | |
141 | } | |
142 | ||
2d7824ff LB |
143 | static int |
144 | run_test(int server_fd, const struct sockaddr *addr, socklen_t len, int type) | |
145 | { | |
146 | int client = -1, srv_client = -1; | |
147 | char buf[] = "testing"; | |
148 | in_port_t port; | |
149 | int ret = 1; | |
150 | ||
151 | client = connect_to_server(addr, len, type); | |
152 | if (client == -1) { | |
153 | perror("Cannot connect to server"); | |
154 | goto out; | |
155 | } | |
156 | ||
8a02a170 JS |
157 | if (type == SOCK_STREAM) { |
158 | srv_client = accept(server_fd, NULL, NULL); | |
159 | if (CHECK_FAIL(srv_client == -1)) { | |
160 | perror("Can't accept connection"); | |
161 | goto out; | |
162 | } | |
163 | } else { | |
164 | srv_client = server_fd; | |
2d7824ff LB |
165 | } |
166 | if (CHECK_FAIL(write(client, buf, sizeof(buf)) != sizeof(buf))) { | |
167 | perror("Can't write on client"); | |
168 | goto out; | |
169 | } | |
8a02a170 | 170 | if (CHECK_FAIL(rcv_msg(srv_client, type) != sizeof(buf))) { |
2d7824ff LB |
171 | perror("Can't read on server"); |
172 | goto out; | |
173 | } | |
174 | ||
175 | port = get_port(srv_client); | |
176 | if (CHECK_FAIL(!port)) | |
177 | goto out; | |
8a02a170 JS |
178 | /* SOCK_STREAM is connected via accept(), so the server's local address |
179 | * will be the CONNECT_PORT rather than the BIND port that corresponds | |
180 | * to the listen socket. SOCK_DGRAM on the other hand is connectionless | |
181 | * so we can't really do the same check there; the server doesn't ever | |
182 | * create a socket with CONNECT_PORT. | |
183 | */ | |
184 | if (type == SOCK_STREAM && | |
185 | CHECK(port != htons(CONNECT_PORT), "Expected", "port %u but got %u", | |
2d7824ff LB |
186 | CONNECT_PORT, ntohs(port))) |
187 | goto out; | |
8a02a170 JS |
188 | else if (type == SOCK_DGRAM && |
189 | CHECK(port != htons(BIND_PORT), "Expected", | |
190 | "port %u but got %u", BIND_PORT, ntohs(port))) | |
191 | goto out; | |
2d7824ff LB |
192 | |
193 | ret = 0; | |
194 | out: | |
195 | close(client); | |
196 | if (srv_client != server_fd) | |
197 | close(srv_client); | |
198 | if (ret) | |
199 | WRITE_ONCE(stop, 1); | |
200 | return ret; | |
201 | } | |
202 | ||
203 | static void | |
204 | prepare_addr(struct sockaddr *addr, int family, __u16 port, bool rewrite_addr) | |
205 | { | |
206 | struct sockaddr_in *addr4; | |
207 | struct sockaddr_in6 *addr6; | |
208 | ||
209 | switch (family) { | |
210 | case AF_INET: | |
211 | addr4 = (struct sockaddr_in *)addr; | |
212 | memset(addr4, 0, sizeof(*addr4)); | |
213 | addr4->sin_family = family; | |
214 | addr4->sin_port = htons(port); | |
215 | if (rewrite_addr) | |
216 | addr4->sin_addr.s_addr = htonl(TEST_DADDR); | |
217 | else | |
218 | addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK); | |
219 | break; | |
220 | case AF_INET6: | |
221 | addr6 = (struct sockaddr_in6 *)addr; | |
222 | memset(addr6, 0, sizeof(*addr6)); | |
223 | addr6->sin6_family = family; | |
224 | addr6->sin6_port = htons(port); | |
225 | addr6->sin6_addr = in6addr_loopback; | |
226 | if (rewrite_addr) | |
227 | addr6->sin6_addr.s6_addr32[3] = htonl(TEST_DADDR); | |
228 | break; | |
229 | default: | |
230 | fprintf(stderr, "Invalid family %d", family); | |
231 | } | |
232 | } | |
233 | ||
234 | struct test_sk_cfg { | |
235 | const char *name; | |
236 | int family; | |
237 | struct sockaddr *addr; | |
238 | socklen_t len; | |
239 | int type; | |
240 | bool rewrite_addr; | |
241 | }; | |
242 | ||
243 | #define TEST(NAME, FAMILY, TYPE, REWRITE) \ | |
244 | { \ | |
245 | .name = NAME, \ | |
246 | .family = FAMILY, \ | |
247 | .addr = (FAMILY == AF_INET) ? (struct sockaddr *)&addr4 \ | |
248 | : (struct sockaddr *)&addr6, \ | |
249 | .len = (FAMILY == AF_INET) ? sizeof(addr4) : sizeof(addr6), \ | |
250 | .type = TYPE, \ | |
251 | .rewrite_addr = REWRITE, \ | |
252 | } | |
253 | ||
254 | void test_sk_assign(void) | |
255 | { | |
256 | struct sockaddr_in addr4; | |
257 | struct sockaddr_in6 addr6; | |
258 | struct test_sk_cfg tests[] = { | |
259 | TEST("ipv4 tcp port redir", AF_INET, SOCK_STREAM, false), | |
260 | TEST("ipv4 tcp addr redir", AF_INET, SOCK_STREAM, true), | |
261 | TEST("ipv6 tcp port redir", AF_INET6, SOCK_STREAM, false), | |
262 | TEST("ipv6 tcp addr redir", AF_INET6, SOCK_STREAM, true), | |
8a02a170 JS |
263 | TEST("ipv4 udp port redir", AF_INET, SOCK_DGRAM, false), |
264 | TEST("ipv4 udp addr redir", AF_INET, SOCK_DGRAM, true), | |
265 | TEST("ipv6 udp port redir", AF_INET6, SOCK_DGRAM, false), | |
266 | TEST("ipv6 udp addr redir", AF_INET6, SOCK_DGRAM, true), | |
2d7824ff | 267 | }; |
b6ed6cf4 | 268 | __s64 server = -1; |
0b9ad56b | 269 | int server_map; |
2d7824ff | 270 | int self_net; |
37a6a9e7 | 271 | int i; |
2d7824ff LB |
272 | |
273 | self_net = open(NS_SELF, O_RDONLY); | |
274 | if (CHECK_FAIL(self_net < 0)) { | |
275 | perror("Unable to open "NS_SELF); | |
276 | return; | |
277 | } | |
278 | ||
279 | if (!configure_stack()) { | |
280 | perror("configure_stack"); | |
281 | goto cleanup; | |
282 | } | |
283 | ||
0b9ad56b JS |
284 | server_map = bpf_obj_get(SERVER_MAP_PATH); |
285 | if (CHECK_FAIL(server_map < 0)) { | |
286 | perror("Unable to open " SERVER_MAP_PATH); | |
287 | goto cleanup; | |
288 | } | |
289 | ||
37a6a9e7 | 290 | for (i = 0; i < ARRAY_SIZE(tests) && !READ_ONCE(stop); i++) { |
2d7824ff LB |
291 | struct test_sk_cfg *test = &tests[i]; |
292 | const struct sockaddr *addr; | |
0b9ad56b JS |
293 | const int zero = 0; |
294 | int err; | |
2d7824ff LB |
295 | |
296 | if (!test__start_subtest(test->name)) | |
297 | continue; | |
298 | prepare_addr(test->addr, test->family, BIND_PORT, false); | |
299 | addr = (const struct sockaddr *)test->addr; | |
300 | server = start_server(addr, test->len, test->type); | |
301 | if (server == -1) | |
0b9ad56b JS |
302 | goto close; |
303 | ||
304 | err = bpf_map_update_elem(server_map, &zero, &server, BPF_ANY); | |
305 | if (CHECK_FAIL(err)) { | |
306 | perror("Unable to update server_map"); | |
307 | goto close; | |
308 | } | |
2d7824ff LB |
309 | |
310 | /* connect to unbound ports */ | |
311 | prepare_addr(test->addr, test->family, CONNECT_PORT, | |
312 | test->rewrite_addr); | |
313 | if (run_test(server, addr, test->len, test->type)) | |
314 | goto close; | |
315 | ||
316 | close(server); | |
317 | server = -1; | |
318 | } | |
319 | ||
320 | close: | |
321 | close(server); | |
0b9ad56b | 322 | close(server_map); |
2d7824ff | 323 | cleanup: |
0b9ad56b JS |
324 | if (CHECK_FAIL(unlink(SERVER_MAP_PATH))) |
325 | perror("Unable to unlink " SERVER_MAP_PATH); | |
2d7824ff LB |
326 | if (CHECK_FAIL(setns(self_net, CLONE_NEWNET))) |
327 | perror("Failed to setns("NS_SELF")"); | |
328 | close(self_net); | |
329 | } |