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