Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-only
2 : : /*
3 : : * Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
4 : : */
5 : :
6 : : #include "cgen/prog/link.h"
7 : :
8 : : #include <linux/bpf.h>
9 : : #include <linux/if_link.h>
10 : :
11 : : #include <errno.h>
12 : : #include <fcntl.h>
13 : : #include <stdio.h>
14 : : #include <stdlib.h>
15 : : #include <string.h>
16 : : #include <sys/socket.h>
17 : : #include <unistd.h>
18 : :
19 : : #include <bpfilter/bpf.h>
20 : : #include <bpfilter/dump.h>
21 : : #include <bpfilter/flavor.h>
22 : : #include <bpfilter/helper.h>
23 : : #include <bpfilter/hook.h>
24 : : #include <bpfilter/logger.h>
25 : : #include <bpfilter/pack.h>
26 : :
27 : 70 : int bf_link_new(struct bf_link **link, const char *name)
28 : : {
29 : 70 : _free_bf_link_ struct bf_link *_link = NULL;
30 : :
31 : : assert(link);
32 : : assert(name);
33 : : assert(name[0] != '\0');
34 : :
35 : 70 : _link = malloc(sizeof(*_link));
36 [ + - ]: 70 : if (!_link)
37 : : return -ENOMEM;
38 : :
39 : 70 : bf_strncpy(_link->name, BPF_OBJ_NAME_LEN, name);
40 : :
41 : 70 : _link->hookopts = NULL;
42 : 70 : _link->fd = -1;
43 : 70 : _link->fd_extra = -1;
44 : :
45 : 70 : *link = TAKE_PTR(_link);
46 : :
47 : 70 : return 0;
48 : : }
49 : :
50 : 3 : int bf_link_new_from_pack(struct bf_link **link, int dir_fd,
51 : : bf_rpack_node_t node)
52 : : {
53 : 3 : _free_bf_link_ struct bf_link *_link = NULL;
54 : 3 : _cleanup_free_ char *name = NULL;
55 : : bf_rpack_node_t child;
56 : : int r;
57 : :
58 : : assert(link);
59 : :
60 : 3 : r = bf_rpack_kv_str(node, "name", &name);
61 [ - + ]: 3 : if (r)
62 [ # # ]: 0 : return bf_rpack_key_err(r, "bf_link.name");
63 : :
64 : 3 : r = bf_link_new(&_link, name);
65 [ - + ]: 3 : if (r)
66 [ # # ]: 0 : return bf_err_r(r, "failed to create bf_link from pack");
67 : :
68 : 3 : r = bf_rpack_kv_node(node, "hookopts", &child);
69 [ - + ]: 3 : if (r)
70 [ # # ]: 0 : return bf_rpack_key_err(r, "bf_link.hookopts");
71 [ + + ]: 3 : if (!bf_rpack_is_nil(child)) {
72 : 2 : r = bf_hookopts_new_from_pack(&_link->hookopts, child);
73 [ + - ]: 2 : if (r)
74 : : return r;
75 : : }
76 : :
77 [ + - ]: 3 : if (dir_fd != -1) {
78 : 3 : r = bf_bpf_obj_get("bf_link", dir_fd, &_link->fd);
79 [ + + ]: 3 : if (r)
80 [ + - ]: 1 : return bf_err_r(r, "failed to open pinned BPF link 'bf_link'");
81 : :
82 : 2 : r = bf_bpf_obj_get("bf_link_extra", dir_fd, &_link->fd_extra);
83 [ - + ]: 2 : if (r && r != -ENOENT) {
84 [ # # ]: 0 : return bf_err_r(
85 : : r, "failed to open pinned extra BPF link 'bf_link_extra'");
86 : : }
87 : : }
88 : :
89 : 2 : *link = TAKE_PTR(_link);
90 : :
91 : 2 : return 0;
92 : : }
93 : :
94 : 143 : void bf_link_free(struct bf_link **link)
95 : : {
96 : : assert(link);
97 : :
98 [ + + ]: 143 : if (!*link)
99 : : return;
100 : :
101 : 70 : bf_hookopts_free(&(*link)->hookopts);
102 : 70 : closep(&(*link)->fd);
103 : 70 : closep(&(*link)->fd_extra);
104 : : freep((void *)link);
105 : : }
106 : :
107 : 169 : int bf_link_pack(const struct bf_link *link, bf_wpack_t *pack)
108 : : {
109 : : bf_assert(link);
110 : : bf_assert(pack);
111 : :
112 : 169 : bf_wpack_kv_str(pack, "name", link->name);
113 : :
114 [ + + ]: 169 : if (link->hookopts) {
115 : 51 : bf_wpack_open_object(pack, "hookopts");
116 : 51 : bf_hookopts_pack(link->hookopts, pack);
117 : 51 : bf_wpack_close_object(pack);
118 : : } else {
119 : : bf_wpack_kv_nil(pack, "hookopts");
120 : : }
121 : :
122 [ - + ]: 169 : return bf_wpack_is_valid(pack) ? 0 : -EINVAL;
123 : : }
124 : :
125 : 3 : void bf_link_dump(const struct bf_link *link, prefix_t *prefix)
126 : : {
127 : : assert(link);
128 : : assert(prefix);
129 : :
130 [ + - ]: 3 : DUMP(prefix, "struct bf_link at %p", link);
131 : :
132 : 3 : bf_dump_prefix_push(prefix);
133 [ + - ]: 3 : DUMP(prefix, "name: %s", link->name);
134 : :
135 [ + + ]: 3 : if (link->hookopts) {
136 [ + - ]: 2 : DUMP(prefix, "hookopts: struct bf_hookopts *");
137 : 2 : bf_dump_prefix_push(prefix);
138 : 2 : bf_hookopts_dump(link->hookopts, prefix);
139 : 2 : bf_dump_prefix_pop(prefix);
140 : : } else {
141 [ + - ]: 1 : DUMP(prefix, "hookopts: struct bf_hookopts * (NULL)");
142 : : }
143 : :
144 [ + - ]: 3 : DUMP(bf_dump_prefix_last(prefix), "fd: %d", link->fd);
145 [ + - ]: 3 : DUMP(bf_dump_prefix_last(prefix), "fd_extra: %d", link->fd_extra);
146 : 3 : bf_dump_prefix_pop(prefix);
147 : 3 : }
148 : :
149 : 28 : int bf_link_attach(struct bf_link *link, enum bf_hook hook,
150 : : struct bf_hookopts **hookopts, int prog_fd)
151 : : {
152 : 28 : _cleanup_close_ int fd = -1;
153 : 28 : _cleanup_close_ int fd_extra = -1;
154 : 28 : _cleanup_close_ int cgroup_fd = -1;
155 : 28 : struct bf_hookopts *_hookopts = *hookopts;
156 : : int r;
157 : :
158 : : assert(link);
159 : : assert(hookopts);
160 : :
161 [ + + + + : 28 : switch (bf_hook_to_flavor(hook)) {
- ]
162 : 14 : case BF_FLAVOR_XDP:
163 : 14 : r = bf_bpf_link_create(prog_fd, _hookopts->ifindex, hook,
164 : : XDP_FLAGS_SKB_MODE, 0, 0);
165 [ + + ]: 14 : if (r < 0)
166 [ + - ]: 3 : return bf_err_r(r, "failed to create XDP BPF link");
167 : :
168 : 11 : fd = r;
169 : 11 : break;
170 : 5 : case BF_FLAVOR_TC:
171 : 5 : r = bf_bpf_link_create(prog_fd, _hookopts->ifindex, hook, 0, 0, 0);
172 [ - + ]: 5 : if (r < 0)
173 [ # # ]: 0 : return bf_err_r(r, "failed to create TC BPF link");
174 : :
175 : 5 : fd = r;
176 : 5 : break;
177 : 2 : case BF_FLAVOR_CGROUP:
178 : 2 : cgroup_fd = open(_hookopts->cgpath, O_DIRECTORY | O_RDONLY);
179 [ - + ]: 2 : if (cgroup_fd < 0) {
180 [ # # ]: 0 : return bf_err_r(errno, "failed to open cgroup '%s'",
181 : : _hookopts->cgpath);
182 : : }
183 : :
184 : 2 : r = bf_bpf_link_create(prog_fd, cgroup_fd, hook, 0, 0, 0);
185 [ - + ]: 2 : if (r < 0)
186 [ # # ]: 0 : return bf_err_r(r, "failed to create cgroup BPF link");
187 : :
188 : 2 : fd = r;
189 : 2 : break;
190 : 7 : case BF_FLAVOR_NF:
191 : 7 : r = bf_bpf_link_create(prog_fd, 0, hook, 0, PF_INET,
192 : 7 : _hookopts->priorities[0]);
193 [ + + ]: 7 : if (r < 0)
194 [ + - ]: 1 : return bf_err_r(r, "failed to create nf_inet BPF link");
195 : :
196 : 6 : fd = r;
197 : :
198 : 6 : r = bf_bpf_link_create(prog_fd, 0, hook, 0, PF_INET6,
199 : 6 : _hookopts->priorities[0]);
200 [ - + ]: 6 : if (r < 0)
201 [ # # ]: 0 : return bf_err_r(r, "failed to create nf_inet6 BPF link");
202 : :
203 : 6 : fd_extra = r;
204 : 6 : break;
205 : : default:
206 : : return -ENOTSUP;
207 : : }
208 : :
209 : 24 : link->fd = TAKE_FD(fd);
210 : 24 : link->fd_extra = TAKE_FD(fd_extra);
211 : 24 : link->hookopts = TAKE_PTR(*hookopts);
212 : :
213 : 24 : return 0;
214 : : }
215 : :
216 : 1 : static int _bf_link_update_nf(struct bf_link *link, enum bf_hook hook,
217 : : int prog_fd)
218 : : {
219 : 1 : _cleanup_close_ int new_inet_fd = -1;
220 : 1 : _cleanup_close_ int new_inet6_fd = -1;
221 : 1 : struct bf_hookopts opts = *link->hookopts;
222 : : int r;
223 : :
224 : : assert(link);
225 : :
226 : : // Attach new program to both inet4 and inet6 using the unused priority
227 : : // This ensures the network is never left unfiltered
228 : 1 : r = bf_bpf_link_create(prog_fd, 0, hook, 0, PF_INET, opts.priorities[1]);
229 [ - + ]: 1 : if (r < 0)
230 [ # # ]: 0 : return bf_err_r(r, "failed to create nf_inet BPF link");
231 : 1 : new_inet_fd = r;
232 : :
233 : 1 : r = bf_bpf_link_create(prog_fd, 0, hook, 0, PF_INET6, opts.priorities[1]);
234 [ - + ]: 1 : if (r < 0)
235 [ # # ]: 0 : return bf_err_r(r, "failed to create nf_inet6 BPF link");
236 : 1 : new_inet6_fd = r;
237 : :
238 : : // Detach old links - safe now that new ones are active
239 : 1 : closep(&link->fd);
240 : 1 : closep(&link->fd_extra);
241 : :
242 : : // Update link with new file descriptors
243 : 1 : link->fd = TAKE_FD(new_inet_fd);
244 : 1 : link->fd_extra = TAKE_FD(new_inet6_fd);
245 : :
246 : : // Swap priorities so priorities[0] reflects the currently active priority
247 : 1 : link->hookopts->priorities[0] = opts.priorities[1];
248 : 1 : link->hookopts->priorities[1] = opts.priorities[0];
249 : :
250 : 1 : return 0;
251 : : }
252 : :
253 : 4 : int bf_link_update(struct bf_link *link, enum bf_hook hook, int prog_fd)
254 : : {
255 : : bf_assert(link);
256 : :
257 : : int r;
258 : :
259 [ + + - ]: 4 : switch (bf_hook_to_flavor(hook)) {
260 : 3 : case BF_FLAVOR_XDP:
261 : : case BF_FLAVOR_TC:
262 : : case BF_FLAVOR_CGROUP:
263 : 3 : r = bf_bpf_link_update(link->fd, prog_fd);
264 : 3 : break;
265 : 1 : case BF_FLAVOR_NF:
266 : 1 : r = _bf_link_update_nf(link, hook, prog_fd);
267 : 1 : break;
268 : : default:
269 : : return -ENOTSUP;
270 : : }
271 : :
272 : : return r;
273 : : }
274 : :
275 : 38 : void bf_link_detach(struct bf_link *link)
276 : : {
277 : : bf_assert(link);
278 : :
279 : : int r;
280 : :
281 : 38 : r = bf_bpf_link_detach(link->fd);
282 [ + + ]: 38 : if (r) {
283 [ + - ]: 20 : bf_warn_r(
284 : : r,
285 : : "call to BPF_LINK_DETACH failed, closing the file descriptor and assuming the link is destroyed");
286 : : }
287 : :
288 [ + + ]: 38 : if (link->fd_extra > 0) {
289 : 6 : r = bf_bpf_link_detach(link->fd_extra);
290 [ - + ]: 6 : if (r) {
291 [ # # ]: 0 : bf_warn_r(
292 : : r,
293 : : "call to BPF_LINK_DETACH for extra link failed, closing the file descriptor and assuming the link is destroyed");
294 : : }
295 : : }
296 : :
297 : 38 : bf_hookopts_free(&link->hookopts);
298 : 38 : closep(&link->fd);
299 : 38 : closep(&link->fd_extra);
300 : 38 : }
301 : :
302 : 28 : int bf_link_pin(struct bf_link *link, int dir_fd)
303 : : {
304 : : int r;
305 : :
306 : : bf_assert(link);
307 : : bf_assert(dir_fd > 0);
308 : :
309 : 28 : r = bf_bpf_obj_pin("bf_link", link->fd, dir_fd);
310 [ - + ]: 28 : if (r)
311 [ # # ]: 0 : return bf_err_r(r, "failed to pin BPF link");
312 : :
313 [ + + ]: 28 : if (link->fd_extra > 0) {
314 : 7 : r = bf_bpf_obj_pin("bf_link_extra", link->fd_extra, dir_fd);
315 [ + - ]: 7 : if (r) {
316 : 0 : bf_link_unpin(link, dir_fd);
317 [ # # ]: 0 : return bf_err_r(r, "failed to pin extra BPF link");
318 : : }
319 : : }
320 : :
321 : : return 0;
322 : : }
323 : :
324 : 42 : void bf_link_unpin(struct bf_link *link, int dir_fd)
325 : : {
326 : : int r;
327 : :
328 : : assert(link);
329 : : assert(dir_fd > 0);
330 : :
331 : : (void)link;
332 : :
333 : 42 : r = unlinkat(dir_fd, "bf_link", 0);
334 [ + + - + ]: 42 : if (r < 0 && errno != ENOENT) {
335 : : // Do not warn on ENOENT, we want the file to be gone!
336 [ # # ]: 0 : bf_warn_r(errno,
337 : : "failed to unlink BPF link, assuming the link is not pinned");
338 : : }
339 : :
340 : 42 : r = unlinkat(dir_fd, "bf_link_extra", 0);
341 [ + + - + ]: 42 : if (r < 0 && errno != ENOENT) {
342 : : // Do not warn on ENOENT, we want the file to be gone!
343 [ # # ]: 0 : bf_warn_r(
344 : : errno,
345 : : "failed to unlink extra BPF link, assuming the link is not pinned");
346 : : }
347 : 42 : }
|