1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (c) 2023 Facebook */ |
3 | #include <test_progs.h> |
4 | #include <linux/in6.h> |
5 | #include <sys/socket.h> |
6 | #include <sched.h> |
7 | #include <unistd.h> |
8 | #include "cgroup_helpers.h" |
9 | #include "testing_helpers.h" |
10 | #include "cgroup_tcp_skb.skel.h" |
11 | #include "cgroup_tcp_skb.h" |
12 | #include "network_helpers.h" |
13 | |
14 | #define CGROUP_TCP_SKB_PATH "/test_cgroup_tcp_skb" |
15 | |
16 | static int install_filters(int cgroup_fd, |
17 | struct bpf_link **egress_link, |
18 | struct bpf_link **ingress_link, |
19 | struct bpf_program *egress_prog, |
20 | struct bpf_program *ingress_prog, |
21 | struct cgroup_tcp_skb *skel) |
22 | { |
23 | /* Prepare filters */ |
24 | skel->bss->g_sock_state = 0; |
25 | skel->bss->g_unexpected = 0; |
26 | *egress_link = |
27 | bpf_program__attach_cgroup(egress_prog, |
28 | cgroup_fd); |
29 | if (!ASSERT_OK_PTR(egress_link, "egress_link" )) |
30 | return -1; |
31 | *ingress_link = |
32 | bpf_program__attach_cgroup(ingress_prog, |
33 | cgroup_fd); |
34 | if (!ASSERT_OK_PTR(ingress_link, "ingress_link" )) |
35 | return -1; |
36 | |
37 | return 0; |
38 | } |
39 | |
40 | static void uninstall_filters(struct bpf_link **egress_link, |
41 | struct bpf_link **ingress_link) |
42 | { |
43 | bpf_link__destroy(*egress_link); |
44 | *egress_link = NULL; |
45 | bpf_link__destroy(*ingress_link); |
46 | *ingress_link = NULL; |
47 | } |
48 | |
49 | static int create_client_sock_v6(void) |
50 | { |
51 | int fd; |
52 | |
53 | fd = socket(AF_INET6, SOCK_STREAM, 0); |
54 | if (fd < 0) { |
55 | perror("socket" ); |
56 | return -1; |
57 | } |
58 | |
59 | return fd; |
60 | } |
61 | |
62 | /* Connect to the server in a cgroup from the outside of the cgroup. */ |
63 | static int talk_to_cgroup(int *client_fd, int *listen_fd, int *service_fd, |
64 | struct cgroup_tcp_skb *skel) |
65 | { |
66 | int err, cp; |
67 | char buf[5]; |
68 | int port; |
69 | |
70 | /* Create client & server socket */ |
71 | err = join_root_cgroup(); |
72 | if (!ASSERT_OK(err, "join_root_cgroup" )) |
73 | return -1; |
74 | *client_fd = create_client_sock_v6(); |
75 | if (!ASSERT_GE(*client_fd, 0, "client_fd" )) |
76 | return -1; |
77 | err = join_cgroup(CGROUP_TCP_SKB_PATH); |
78 | if (!ASSERT_OK(err, "join_cgroup" )) |
79 | return -1; |
80 | *listen_fd = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); |
81 | if (!ASSERT_GE(*listen_fd, 0, "listen_fd" )) |
82 | return -1; |
83 | port = get_socket_local_port(*listen_fd); |
84 | if (!ASSERT_GE(port, 0, "get_socket_local_port" )) |
85 | return -1; |
86 | skel->bss->g_sock_port = ntohs(port); |
87 | |
88 | /* Connect client to server */ |
89 | err = connect_fd_to_fd(*client_fd, *listen_fd, 0); |
90 | if (!ASSERT_OK(err, "connect_fd_to_fd" )) |
91 | return -1; |
92 | *service_fd = accept(*listen_fd, NULL, NULL); |
93 | if (!ASSERT_GE(*service_fd, 0, "service_fd" )) |
94 | return -1; |
95 | err = join_root_cgroup(); |
96 | if (!ASSERT_OK(err, "join_root_cgroup" )) |
97 | return -1; |
98 | cp = write(*client_fd, "hello" , 5); |
99 | if (!ASSERT_EQ(cp, 5, "write" )) |
100 | return -1; |
101 | cp = read(*service_fd, buf, 5); |
102 | if (!ASSERT_EQ(cp, 5, "read" )) |
103 | return -1; |
104 | |
105 | return 0; |
106 | } |
107 | |
108 | /* Connect to the server out of a cgroup from inside the cgroup. */ |
109 | static int talk_to_outside(int *client_fd, int *listen_fd, int *service_fd, |
110 | struct cgroup_tcp_skb *skel) |
111 | |
112 | { |
113 | int err, cp; |
114 | char buf[5]; |
115 | int port; |
116 | |
117 | /* Create client & server socket */ |
118 | err = join_root_cgroup(); |
119 | if (!ASSERT_OK(err, "join_root_cgroup" )) |
120 | return -1; |
121 | *listen_fd = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0); |
122 | if (!ASSERT_GE(*listen_fd, 0, "listen_fd" )) |
123 | return -1; |
124 | err = join_cgroup(CGROUP_TCP_SKB_PATH); |
125 | if (!ASSERT_OK(err, "join_cgroup" )) |
126 | return -1; |
127 | *client_fd = create_client_sock_v6(); |
128 | if (!ASSERT_GE(*client_fd, 0, "client_fd" )) |
129 | return -1; |
130 | err = join_root_cgroup(); |
131 | if (!ASSERT_OK(err, "join_root_cgroup" )) |
132 | return -1; |
133 | port = get_socket_local_port(*listen_fd); |
134 | if (!ASSERT_GE(port, 0, "get_socket_local_port" )) |
135 | return -1; |
136 | skel->bss->g_sock_port = ntohs(port); |
137 | |
138 | /* Connect client to server */ |
139 | err = connect_fd_to_fd(*client_fd, *listen_fd, 0); |
140 | if (!ASSERT_OK(err, "connect_fd_to_fd" )) |
141 | return -1; |
142 | *service_fd = accept(*listen_fd, NULL, NULL); |
143 | if (!ASSERT_GE(*service_fd, 0, "service_fd" )) |
144 | return -1; |
145 | cp = write(*client_fd, "hello" , 5); |
146 | if (!ASSERT_EQ(cp, 5, "write" )) |
147 | return -1; |
148 | cp = read(*service_fd, buf, 5); |
149 | if (!ASSERT_EQ(cp, 5, "read" )) |
150 | return -1; |
151 | |
152 | return 0; |
153 | } |
154 | |
155 | static int close_connection(int *closing_fd, int *peer_fd, int *listen_fd, |
156 | struct cgroup_tcp_skb *skel) |
157 | { |
158 | __u32 saved_packet_count = 0; |
159 | int err; |
160 | int i; |
161 | |
162 | /* Wait for ACKs to be sent */ |
163 | saved_packet_count = skel->bss->g_packet_count; |
164 | usleep(100000); /* 0.1s */ |
165 | for (i = 0; |
166 | skel->bss->g_packet_count != saved_packet_count && i < 10; |
167 | i++) { |
168 | saved_packet_count = skel->bss->g_packet_count; |
169 | usleep(100000); /* 0.1s */ |
170 | } |
171 | if (!ASSERT_EQ(skel->bss->g_packet_count, saved_packet_count, |
172 | "packet_count" )) |
173 | return -1; |
174 | |
175 | skel->bss->g_packet_count = 0; |
176 | saved_packet_count = 0; |
177 | |
178 | /* Half shutdown to make sure the closing socket having a chance to |
179 | * receive a FIN from the peer. |
180 | */ |
181 | err = shutdown(*closing_fd, SHUT_WR); |
182 | if (!ASSERT_OK(err, "shutdown closing_fd" )) |
183 | return -1; |
184 | |
185 | /* Wait for FIN and the ACK of the FIN to be observed */ |
186 | for (i = 0; |
187 | skel->bss->g_packet_count < saved_packet_count + 2 && i < 10; |
188 | i++) |
189 | usleep(100000); /* 0.1s */ |
190 | if (!ASSERT_GE(skel->bss->g_packet_count, saved_packet_count + 2, |
191 | "packet_count" )) |
192 | return -1; |
193 | |
194 | saved_packet_count = skel->bss->g_packet_count; |
195 | |
196 | /* Fully shutdown the connection */ |
197 | err = close(*peer_fd); |
198 | if (!ASSERT_OK(err, "close peer_fd" )) |
199 | return -1; |
200 | *peer_fd = -1; |
201 | |
202 | /* Wait for FIN and the ACK of the FIN to be observed */ |
203 | for (i = 0; |
204 | skel->bss->g_packet_count < saved_packet_count + 2 && i < 10; |
205 | i++) |
206 | usleep(100000); /* 0.1s */ |
207 | if (!ASSERT_GE(skel->bss->g_packet_count, saved_packet_count + 2, |
208 | "packet_count" )) |
209 | return -1; |
210 | |
211 | err = close(*closing_fd); |
212 | if (!ASSERT_OK(err, "close closing_fd" )) |
213 | return -1; |
214 | *closing_fd = -1; |
215 | |
216 | close(*listen_fd); |
217 | *listen_fd = -1; |
218 | |
219 | return 0; |
220 | } |
221 | |
222 | /* This test case includes four scenarios: |
223 | * 1. Connect to the server from outside the cgroup and close the connection |
224 | * from outside the cgroup. |
225 | * 2. Connect to the server from outside the cgroup and close the connection |
226 | * from inside the cgroup. |
227 | * 3. Connect to the server from inside the cgroup and close the connection |
228 | * from outside the cgroup. |
229 | * 4. Connect to the server from inside the cgroup and close the connection |
230 | * from inside the cgroup. |
231 | * |
232 | * The test case is to verify that cgroup_skb/{egress,ingress} filters |
233 | * receive expected packets including SYN, SYN/ACK, ACK, FIN, and FIN/ACK. |
234 | */ |
235 | void test_cgroup_tcp_skb(void) |
236 | { |
237 | struct bpf_link *ingress_link = NULL; |
238 | struct bpf_link *egress_link = NULL; |
239 | int client_fd = -1, listen_fd = -1; |
240 | struct cgroup_tcp_skb *skel; |
241 | int service_fd = -1; |
242 | int cgroup_fd = -1; |
243 | int err; |
244 | |
245 | skel = cgroup_tcp_skb__open_and_load(); |
246 | if (!ASSERT_OK(!skel, "skel_open_load" )) |
247 | return; |
248 | |
249 | err = setup_cgroup_environment(); |
250 | if (!ASSERT_OK(err, "setup_cgroup_environment" )) |
251 | goto cleanup; |
252 | |
253 | cgroup_fd = create_and_get_cgroup(CGROUP_TCP_SKB_PATH); |
254 | if (!ASSERT_GE(cgroup_fd, 0, "cgroup_fd" )) |
255 | goto cleanup; |
256 | |
257 | /* Scenario 1 */ |
258 | err = install_filters(cgroup_fd, egress_link: &egress_link, ingress_link: &ingress_link, |
259 | egress_prog: skel->progs.server_egress, |
260 | ingress_prog: skel->progs.server_ingress, |
261 | skel); |
262 | if (!ASSERT_OK(err, "install_filters" )) |
263 | goto cleanup; |
264 | |
265 | err = talk_to_cgroup(client_fd: &client_fd, listen_fd: &listen_fd, service_fd: &service_fd, skel); |
266 | if (!ASSERT_OK(err, "talk_to_cgroup" )) |
267 | goto cleanup; |
268 | |
269 | err = close_connection(closing_fd: &client_fd, peer_fd: &service_fd, listen_fd: &listen_fd, skel); |
270 | if (!ASSERT_OK(err, "close_connection" )) |
271 | goto cleanup; |
272 | |
273 | ASSERT_EQ(skel->bss->g_unexpected, 0, "g_unexpected" ); |
274 | ASSERT_EQ(skel->bss->g_sock_state, CLOSED, "g_sock_state" ); |
275 | |
276 | uninstall_filters(egress_link: &egress_link, ingress_link: &ingress_link); |
277 | |
278 | /* Scenario 2 */ |
279 | err = install_filters(cgroup_fd, egress_link: &egress_link, ingress_link: &ingress_link, |
280 | egress_prog: skel->progs.server_egress_srv, |
281 | ingress_prog: skel->progs.server_ingress_srv, |
282 | skel); |
283 | |
284 | err = talk_to_cgroup(client_fd: &client_fd, listen_fd: &listen_fd, service_fd: &service_fd, skel); |
285 | if (!ASSERT_OK(err, "talk_to_cgroup" )) |
286 | goto cleanup; |
287 | |
288 | err = close_connection(closing_fd: &service_fd, peer_fd: &client_fd, listen_fd: &listen_fd, skel); |
289 | if (!ASSERT_OK(err, "close_connection" )) |
290 | goto cleanup; |
291 | |
292 | ASSERT_EQ(skel->bss->g_unexpected, 0, "g_unexpected" ); |
293 | ASSERT_EQ(skel->bss->g_sock_state, TIME_WAIT, "g_sock_state" ); |
294 | |
295 | uninstall_filters(egress_link: &egress_link, ingress_link: &ingress_link); |
296 | |
297 | /* Scenario 3 */ |
298 | err = install_filters(cgroup_fd, egress_link: &egress_link, ingress_link: &ingress_link, |
299 | egress_prog: skel->progs.client_egress_srv, |
300 | ingress_prog: skel->progs.client_ingress_srv, |
301 | skel); |
302 | |
303 | err = talk_to_outside(client_fd: &client_fd, listen_fd: &listen_fd, service_fd: &service_fd, skel); |
304 | if (!ASSERT_OK(err, "talk_to_outside" )) |
305 | goto cleanup; |
306 | |
307 | err = close_connection(closing_fd: &service_fd, peer_fd: &client_fd, listen_fd: &listen_fd, skel); |
308 | if (!ASSERT_OK(err, "close_connection" )) |
309 | goto cleanup; |
310 | |
311 | ASSERT_EQ(skel->bss->g_unexpected, 0, "g_unexpected" ); |
312 | ASSERT_EQ(skel->bss->g_sock_state, CLOSED, "g_sock_state" ); |
313 | |
314 | uninstall_filters(egress_link: &egress_link, ingress_link: &ingress_link); |
315 | |
316 | /* Scenario 4 */ |
317 | err = install_filters(cgroup_fd, egress_link: &egress_link, ingress_link: &ingress_link, |
318 | egress_prog: skel->progs.client_egress, |
319 | ingress_prog: skel->progs.client_ingress, |
320 | skel); |
321 | |
322 | err = talk_to_outside(client_fd: &client_fd, listen_fd: &listen_fd, service_fd: &service_fd, skel); |
323 | if (!ASSERT_OK(err, "talk_to_outside" )) |
324 | goto cleanup; |
325 | |
326 | err = close_connection(closing_fd: &client_fd, peer_fd: &service_fd, listen_fd: &listen_fd, skel); |
327 | if (!ASSERT_OK(err, "close_connection" )) |
328 | goto cleanup; |
329 | |
330 | ASSERT_EQ(skel->bss->g_unexpected, 0, "g_unexpected" ); |
331 | ASSERT_EQ(skel->bss->g_sock_state, TIME_WAIT, "g_sock_state" ); |
332 | |
333 | uninstall_filters(egress_link: &egress_link, ingress_link: &ingress_link); |
334 | |
335 | cleanup: |
336 | close(client_fd); |
337 | close(listen_fd); |
338 | close(service_fd); |
339 | close(cgroup_fd); |
340 | bpf_link__destroy(egress_link); |
341 | bpf_link__destroy(ingress_link); |
342 | cleanup_cgroup_environment(); |
343 | cgroup_tcp_skb__destroy(skel); |
344 | } |
345 | |