Commit | Line | Data |
---|---|---|
bafc0ba8 LB |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2018 Facebook | |
3 | // Copyright (c) 2019 Cloudflare | |
4 | ||
91bc3578 | 5 | #include <limits.h> |
bafc0ba8 LB |
6 | #include <string.h> |
7 | #include <stdlib.h> | |
8 | #include <unistd.h> | |
9 | ||
10 | #include <arpa/inet.h> | |
11 | #include <netinet/in.h> | |
12 | #include <sys/types.h> | |
13 | #include <sys/socket.h> | |
14 | ||
15 | #include <bpf/bpf.h> | |
16 | #include <bpf/libbpf.h> | |
17 | ||
18 | #include "bpf_rlimit.h" | |
19 | #include "cgroup_helpers.h" | |
20 | ||
53968daf | 21 | static int start_server(const struct sockaddr *addr, socklen_t len, bool dual) |
bafc0ba8 | 22 | { |
53968daf | 23 | int mode = !dual; |
bafc0ba8 LB |
24 | int fd; |
25 | ||
26 | fd = socket(addr->sa_family, SOCK_STREAM, 0); | |
27 | if (fd == -1) { | |
28 | log_err("Failed to create server socket"); | |
29 | goto out; | |
30 | } | |
31 | ||
53968daf MM |
32 | if (addr->sa_family == AF_INET6) { |
33 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&mode, | |
34 | sizeof(mode)) == -1) { | |
35 | log_err("Failed to set the dual-stack mode"); | |
36 | goto close_out; | |
37 | } | |
38 | } | |
39 | ||
bafc0ba8 LB |
40 | if (bind(fd, addr, len) == -1) { |
41 | log_err("Failed to bind server socket"); | |
42 | goto close_out; | |
43 | } | |
44 | ||
45 | if (listen(fd, 128) == -1) { | |
46 | log_err("Failed to listen on server socket"); | |
47 | goto close_out; | |
48 | } | |
49 | ||
50 | goto out; | |
51 | ||
52 | close_out: | |
53 | close(fd); | |
54 | fd = -1; | |
55 | out: | |
56 | return fd; | |
57 | } | |
58 | ||
53968daf | 59 | static int connect_to_server(const struct sockaddr *addr, socklen_t len) |
bafc0ba8 | 60 | { |
bafc0ba8 LB |
61 | int fd = -1; |
62 | ||
53968daf | 63 | fd = socket(addr->sa_family, SOCK_STREAM, 0); |
bafc0ba8 LB |
64 | if (fd == -1) { |
65 | log_err("Failed to create client socket"); | |
66 | goto out; | |
67 | } | |
68 | ||
53968daf | 69 | if (connect(fd, (const struct sockaddr *)addr, len) == -1) { |
bafc0ba8 LB |
70 | log_err("Fail to connect to server"); |
71 | goto close_out; | |
72 | } | |
73 | ||
74 | goto out; | |
75 | ||
76 | close_out: | |
77 | close(fd); | |
78 | fd = -1; | |
79 | out: | |
80 | return fd; | |
81 | } | |
82 | ||
91bc3578 | 83 | static int get_map_fd_by_prog_id(int prog_id, bool *xdp) |
bafc0ba8 LB |
84 | { |
85 | struct bpf_prog_info info = {}; | |
86 | __u32 info_len = sizeof(info); | |
87 | __u32 map_ids[1]; | |
88 | int prog_fd = -1; | |
89 | int map_fd = -1; | |
90 | ||
91 | prog_fd = bpf_prog_get_fd_by_id(prog_id); | |
92 | if (prog_fd < 0) { | |
93 | log_err("Failed to get fd by prog id %d", prog_id); | |
94 | goto err; | |
95 | } | |
96 | ||
97 | info.nr_map_ids = 1; | |
98 | info.map_ids = (__u64)(unsigned long)map_ids; | |
99 | ||
100 | if (bpf_obj_get_info_by_fd(prog_fd, &info, &info_len)) { | |
101 | log_err("Failed to get info by prog fd %d", prog_fd); | |
102 | goto err; | |
103 | } | |
104 | ||
105 | if (!info.nr_map_ids) { | |
106 | log_err("No maps found for prog fd %d", prog_fd); | |
107 | goto err; | |
108 | } | |
109 | ||
91bc3578 PP |
110 | *xdp = info.type == BPF_PROG_TYPE_XDP; |
111 | ||
bafc0ba8 LB |
112 | map_fd = bpf_map_get_fd_by_id(map_ids[0]); |
113 | if (map_fd < 0) | |
114 | log_err("Failed to get fd by map id %d", map_ids[0]); | |
115 | err: | |
116 | if (prog_fd >= 0) | |
117 | close(prog_fd); | |
118 | return map_fd; | |
119 | } | |
120 | ||
53968daf MM |
121 | static int run_test(int server_fd, int results_fd, bool xdp, |
122 | const struct sockaddr *addr, socklen_t len) | |
bafc0ba8 LB |
123 | { |
124 | int client = -1, srv_client = -1; | |
125 | int ret = 0; | |
126 | __u32 key = 0; | |
91bc3578 PP |
127 | __u32 key_gen = 1; |
128 | __u32 key_mss = 2; | |
129 | __u32 value = 0; | |
130 | __u32 value_gen = 0; | |
131 | __u32 value_mss = 0; | |
bafc0ba8 LB |
132 | |
133 | if (bpf_map_update_elem(results_fd, &key, &value, 0) < 0) { | |
134 | log_err("Can't clear results"); | |
135 | goto err; | |
136 | } | |
137 | ||
91bc3578 PP |
138 | if (bpf_map_update_elem(results_fd, &key_gen, &value_gen, 0) < 0) { |
139 | log_err("Can't clear results"); | |
140 | goto err; | |
141 | } | |
142 | ||
143 | if (bpf_map_update_elem(results_fd, &key_mss, &value_mss, 0) < 0) { | |
144 | log_err("Can't clear results"); | |
145 | goto err; | |
146 | } | |
147 | ||
53968daf | 148 | client = connect_to_server(addr, len); |
bafc0ba8 LB |
149 | if (client == -1) |
150 | goto err; | |
151 | ||
152 | srv_client = accept(server_fd, NULL, 0); | |
153 | if (srv_client == -1) { | |
154 | log_err("Can't accept connection"); | |
155 | goto err; | |
156 | } | |
157 | ||
158 | if (bpf_map_lookup_elem(results_fd, &key, &value) < 0) { | |
159 | log_err("Can't lookup result"); | |
160 | goto err; | |
161 | } | |
162 | ||
91bc3578 PP |
163 | if (value == 0) { |
164 | log_err("Didn't match syncookie: %u", value); | |
165 | goto err; | |
166 | } | |
167 | ||
168 | if (bpf_map_lookup_elem(results_fd, &key_gen, &value_gen) < 0) { | |
169 | log_err("Can't lookup result"); | |
170 | goto err; | |
171 | } | |
172 | ||
173 | if (xdp && value_gen == 0) { | |
174 | // SYN packets do not get passed through generic XDP, skip the | |
175 | // rest of the test. | |
176 | printf("Skipping XDP cookie check\n"); | |
177 | goto out; | |
178 | } | |
179 | ||
180 | if (bpf_map_lookup_elem(results_fd, &key_mss, &value_mss) < 0) { | |
181 | log_err("Can't lookup result"); | |
182 | goto err; | |
183 | } | |
184 | ||
185 | if (value != value_gen) { | |
186 | log_err("BPF generated cookie does not match kernel one"); | |
187 | goto err; | |
188 | } | |
189 | ||
190 | if (value_mss < 536 || value_mss > USHRT_MAX) { | |
191 | log_err("Unexpected MSS retrieved"); | |
bafc0ba8 LB |
192 | goto err; |
193 | } | |
194 | ||
195 | goto out; | |
196 | ||
197 | err: | |
198 | ret = 1; | |
199 | out: | |
200 | close(client); | |
201 | close(srv_client); | |
202 | return ret; | |
203 | } | |
204 | ||
53968daf MM |
205 | static bool get_port(int server_fd, in_port_t *port) |
206 | { | |
207 | struct sockaddr_in addr; | |
208 | socklen_t len = sizeof(addr); | |
209 | ||
210 | if (getsockname(server_fd, (struct sockaddr *)&addr, &len)) { | |
211 | log_err("Failed to get server addr"); | |
212 | return false; | |
213 | } | |
214 | ||
215 | /* sin_port and sin6_port are located at the same offset. */ | |
216 | *port = addr.sin_port; | |
217 | return true; | |
218 | } | |
219 | ||
bafc0ba8 LB |
220 | int main(int argc, char **argv) |
221 | { | |
222 | struct sockaddr_in addr4; | |
223 | struct sockaddr_in6 addr6; | |
53968daf MM |
224 | struct sockaddr_in addr4dual; |
225 | struct sockaddr_in6 addr6dual; | |
bafc0ba8 LB |
226 | int server = -1; |
227 | int server_v6 = -1; | |
53968daf | 228 | int server_dual = -1; |
bafc0ba8 LB |
229 | int results = -1; |
230 | int err = 0; | |
91bc3578 | 231 | bool xdp; |
bafc0ba8 LB |
232 | |
233 | if (argc < 2) { | |
234 | fprintf(stderr, "Usage: %s prog_id\n", argv[0]); | |
235 | exit(1); | |
236 | } | |
237 | ||
91bc3578 | 238 | results = get_map_fd_by_prog_id(atoi(argv[1]), &xdp); |
bafc0ba8 LB |
239 | if (results < 0) { |
240 | log_err("Can't get map"); | |
241 | goto err; | |
242 | } | |
243 | ||
244 | memset(&addr4, 0, sizeof(addr4)); | |
245 | addr4.sin_family = AF_INET; | |
246 | addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); | |
247 | addr4.sin_port = 0; | |
53968daf | 248 | memcpy(&addr4dual, &addr4, sizeof(addr4dual)); |
bafc0ba8 LB |
249 | |
250 | memset(&addr6, 0, sizeof(addr6)); | |
251 | addr6.sin6_family = AF_INET6; | |
252 | addr6.sin6_addr = in6addr_loopback; | |
253 | addr6.sin6_port = 0; | |
254 | ||
53968daf MM |
255 | memset(&addr6dual, 0, sizeof(addr6dual)); |
256 | addr6dual.sin6_family = AF_INET6; | |
257 | addr6dual.sin6_addr = in6addr_any; | |
258 | addr6dual.sin6_port = 0; | |
259 | ||
260 | server = start_server((const struct sockaddr *)&addr4, sizeof(addr4), | |
261 | false); | |
262 | if (server == -1 || !get_port(server, &addr4.sin_port)) | |
bafc0ba8 LB |
263 | goto err; |
264 | ||
265 | server_v6 = start_server((const struct sockaddr *)&addr6, | |
53968daf MM |
266 | sizeof(addr6), false); |
267 | if (server_v6 == -1 || !get_port(server_v6, &addr6.sin6_port)) | |
268 | goto err; | |
269 | ||
270 | server_dual = start_server((const struct sockaddr *)&addr6dual, | |
271 | sizeof(addr6dual), true); | |
272 | if (server_dual == -1 || !get_port(server_dual, &addr4dual.sin_port)) | |
273 | goto err; | |
274 | ||
275 | if (run_test(server, results, xdp, | |
276 | (const struct sockaddr *)&addr4, sizeof(addr4))) | |
bafc0ba8 LB |
277 | goto err; |
278 | ||
53968daf MM |
279 | if (run_test(server_v6, results, xdp, |
280 | (const struct sockaddr *)&addr6, sizeof(addr6))) | |
bafc0ba8 LB |
281 | goto err; |
282 | ||
53968daf MM |
283 | if (run_test(server_dual, results, xdp, |
284 | (const struct sockaddr *)&addr4dual, sizeof(addr4dual))) | |
bafc0ba8 LB |
285 | goto err; |
286 | ||
287 | printf("ok\n"); | |
288 | goto out; | |
289 | err: | |
290 | err = 1; | |
291 | out: | |
292 | close(server); | |
293 | close(server_v6); | |
53968daf | 294 | close(server_dual); |
bafc0ba8 LB |
295 | close(results); |
296 | return err; | |
297 | } |