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 : 123 : 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 : 123 : _free_bf_link_ struct bf_link *_link = NULL;
34 : 123 : _cleanup_close_ int fd = -1;
35 : 123 : _cleanup_close_ int fd_extra = -1;
36 : 123 : _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 [ - + ]: 123 : if (name[0] == '\0')
45 [ # # ]: 0 : return bf_err_r(-EINVAL, "link name can't be empty");
46 : :
47 [ - + ]: 123 : 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 : 123 : _link = calloc(1, sizeof(*_link));
54 [ + - ]: 123 : if (!_link)
55 : : return -ENOMEM;
56 : :
57 : 123 : _link->hook = hook;
58 : 123 : _link->fd = -1;
59 : 123 : _link->fd_extra = -1;
60 : 123 : bf_strncpy(_link->name, BPF_OBJ_NAME_LEN, name);
61 : :
62 [ + + + + : 123 : switch (bf_hook_to_flavor(hook)) {
- ]
63 : 32 : case BF_FLAVOR_XDP:
64 : 32 : r = _bf_link_try_attach_xdp(prog_fd, _hookopts->ifindex, hook);
65 [ + + ]: 32 : if (r < 0)
66 [ + - ]: 3 : return bf_err_r(r, "failed to create XDP BPF link");
67 : :
68 : 29 : fd = r;
69 : 29 : break;
70 : 21 : case BF_FLAVOR_TC:
71 : 21 : r = bf_bpf_link_create(prog_fd, _hookopts->ifindex, hook, 0, 0, 0);
72 [ - + ]: 21 : if (r < 0)
73 [ # # ]: 0 : return bf_err_r(r, "failed to create TC BPF link");
74 : :
75 : 21 : fd = r;
76 : 21 : break;
77 : 63 : case BF_FLAVOR_CGROUP_SKB:
78 : : case BF_FLAVOR_CGROUP_SOCK_ADDR:
79 : 63 : cgroup_fd = open(_hookopts->cgpath, O_DIRECTORY | O_RDONLY);
80 [ - + ]: 63 : if (cgroup_fd < 0) {
81 [ # # ]: 0 : return bf_err_r(errno, "failed to open cgroup '%s'",
82 : : _hookopts->cgpath);
83 : : }
84 : :
85 : 63 : r = bf_bpf_link_create(prog_fd, cgroup_fd, hook, 0, 0, 0);
86 [ - + ]: 63 : if (r < 0)
87 [ # # ]: 0 : return bf_err_r(r, "failed to create cgroup BPF link");
88 : :
89 : 63 : fd = r;
90 : 63 : break;
91 : 7 : case BF_FLAVOR_NF:
92 : 7 : r = bf_bpf_link_create(prog_fd, 0, hook, 0, PF_INET,
93 : 7 : _hookopts->priorities[0]);
94 [ + + ]: 7 : if (r < 0)
95 [ + - ]: 1 : return bf_err_r(r, "failed to create nf_inet BPF link");
96 : :
97 : 6 : fd = r;
98 : :
99 : 6 : r = bf_bpf_link_create(prog_fd, 0, hook, 0, PF_INET6,
100 : 6 : _hookopts->priorities[0]);
101 [ - + ]: 6 : if (r < 0)
102 [ # # ]: 0 : return bf_err_r(r, "failed to create nf_inet6 BPF link");
103 : :
104 : 6 : fd_extra = r;
105 : 6 : break;
106 : : default:
107 : : return -ENOTSUP;
108 : : }
109 : :
110 : 119 : _link->fd = TAKE_FD(fd);
111 : 119 : _link->fd_extra = TAKE_FD(fd_extra);
112 : 119 : _link->hookopts = TAKE_PTR(*hookopts);
113 : :
114 : 119 : *link = TAKE_PTR(_link);
115 : :
116 : 119 : return 0;
117 : : }
118 : :
119 : 158 : int bf_link_new_from_pack(struct bf_link **link, int dir_fd,
120 : : bf_rpack_node_t node)
121 : : {
122 : 158 : _free_bf_link_ struct bf_link *_link = NULL;
123 : 158 : _cleanup_free_ char *name = NULL;
124 : : bf_rpack_node_t child;
125 : : int r;
126 : :
127 : : assert(link);
128 : :
129 : 158 : r = bf_rpack_kv_str(node, "name", &name);
130 [ - + ]: 158 : if (r)
131 [ # # ]: 0 : return bf_rpack_key_err(r, "bf_link.name");
132 : :
133 : 158 : _link = calloc(1, sizeof(*_link));
134 [ + - ]: 158 : if (!_link)
135 : : return -ENOMEM;
136 : :
137 : 158 : _link->fd = -1;
138 : 158 : _link->fd_extra = -1;
139 : 158 : bf_strncpy(_link->name, BPF_OBJ_NAME_LEN, name);
140 : :
141 [ - + - + : 158 : 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 : 158 : r = bf_rpack_kv_node(node, "hookopts", &child);
146 [ - + ]: 158 : if (r)
147 [ # # ]: 0 : return bf_rpack_key_err(r, "bf_link.hookopts");
148 : 158 : r = bf_hookopts_new_from_pack(&_link->hookopts, child);
149 [ + - ]: 158 : if (r)
150 : : return r;
151 : :
152 : 158 : r = bf_bpf_obj_get("bf_link", dir_fd, &_link->fd);
153 [ - + ]: 158 : if (r)
154 [ # # ]: 0 : return bf_err_r(r, "failed to open pinned BPF link 'bf_link'");
155 : :
156 : 158 : r = bf_bpf_obj_get("bf_link_extra", dir_fd, &_link->fd_extra);
157 [ - + ]: 158 : 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 : 158 : *link = TAKE_PTR(_link);
163 : :
164 : 158 : return 0;
165 : : }
166 : :
167 : 9444 : void bf_link_free(struct bf_link **link)
168 : : {
169 : : assert(link);
170 : :
171 [ + + ]: 9444 : if (!*link)
172 : : return;
173 : :
174 : 281 : bf_hookopts_free(&(*link)->hookopts);
175 : 281 : closep(&(*link)->fd);
176 : 281 : closep(&(*link)->fd_extra);
177 : : freep((void *)link);
178 : : }
179 : :
180 : 143 : int bf_link_pack(const struct bf_link *link, bf_wpack_t *pack)
181 : : {
182 : : assert(link);
183 : : assert(pack);
184 : :
185 : 143 : bf_wpack_kv_str(pack, "name", link->name);
186 : 143 : bf_wpack_kv_enum(pack, "hook", link->hook);
187 : 143 : bf_wpack_open_object(pack, "hookopts");
188 : 143 : bf_hookopts_pack(link->hookopts, pack);
189 : 143 : bf_wpack_close_object(pack);
190 : :
191 [ - + ]: 143 : 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 : 32 : static int _bf_link_try_attach_xdp(int prog_fd, int ifindex, enum bf_hook hook)
226 : : {
227 : : int r;
228 : :
229 : 32 : r = bf_bpf_link_create(prog_fd, ifindex, hook, XDP_FLAGS_DRV_MODE, 0, 0);
230 [ + + ]: 32 : if (r >= 0) {
231 [ + - ]: 29 : bf_info("attached XDP program in driver mode");
232 : 29 : return r;
233 : : }
234 : :
235 [ + - ]: 3 : if (r != -ENOTSUP)
236 : : return r;
237 : :
238 [ # # ]: 0 : bf_dbg_r(r, "driver mode not available");
239 : :
240 : 0 : r = bf_bpf_link_create(prog_fd, ifindex, hook, XDP_FLAGS_SKB_MODE, 0, 0);
241 [ # # ]: 0 : if (r >= 0)
242 [ # # ]: 0 : bf_info("attached XDP program in SKB mode");
243 : :
244 : : return r;
245 : : }
246 : :
247 : 1 : static int _bf_link_update_nf(struct bf_link *link, int prog_fd)
248 : : {
249 : 1 : _cleanup_close_ int new_inet_fd = -1;
250 : 1 : _cleanup_close_ int new_inet6_fd = -1;
251 : : struct bf_hookopts opts;
252 : : int r;
253 : :
254 : : assert(link);
255 : :
256 : 1 : opts = *link->hookopts;
257 : :
258 : : // Attach new program to both inet4 and inet6 using the unused priority
259 : : // This ensures the network is never left unfiltered
260 : 1 : r = bf_bpf_link_create(prog_fd, 0, link->hook, 0, PF_INET,
261 : : opts.priorities[1]);
262 [ - + ]: 1 : if (r < 0)
263 [ # # ]: 0 : return bf_err_r(r, "failed to create nf_inet BPF link");
264 : 1 : new_inet_fd = r;
265 : :
266 : 1 : r = bf_bpf_link_create(prog_fd, 0, link->hook, 0, PF_INET6,
267 : : opts.priorities[1]);
268 [ - + ]: 1 : if (r < 0)
269 [ # # ]: 0 : return bf_err_r(r, "failed to create nf_inet6 BPF link");
270 : 1 : new_inet6_fd = r;
271 : :
272 : : // Detach old links - safe now that new ones are active
273 : 1 : closep(&link->fd);
274 : 1 : closep(&link->fd_extra);
275 : :
276 : : // Update link with new file descriptors
277 : 1 : link->fd = TAKE_FD(new_inet_fd);
278 : 1 : link->fd_extra = TAKE_FD(new_inet6_fd);
279 : :
280 : : // Swap priorities so priorities[0] reflects the currently active priority
281 : 1 : link->hookopts->priorities[0] = opts.priorities[1];
282 : 1 : link->hookopts->priorities[1] = opts.priorities[0];
283 : :
284 : 1 : return 0;
285 : : }
286 : :
287 : 12 : int bf_link_update(struct bf_link *link, int prog_fd)
288 : : {
289 : : int r;
290 : :
291 : : assert(link);
292 : :
293 [ + + - ]: 12 : switch (bf_hook_to_flavor(link->hook)) {
294 : 11 : case BF_FLAVOR_XDP:
295 : : case BF_FLAVOR_TC:
296 : : case BF_FLAVOR_CGROUP_SKB:
297 : : case BF_FLAVOR_CGROUP_SOCK_ADDR:
298 : 11 : r = bf_bpf_link_update(link->fd, prog_fd);
299 : 11 : break;
300 : 1 : case BF_FLAVOR_NF:
301 : 1 : r = _bf_link_update_nf(link, prog_fd);
302 : 1 : break;
303 : : default:
304 : : return -ENOTSUP;
305 : : }
306 : :
307 : : return r;
308 : : }
309 : :
310 : 131 : int bf_link_pin(struct bf_link *link, struct bf_lock *lock)
311 : : {
312 : : int r;
313 : :
314 : : assert(link);
315 : : assert(lock);
316 : :
317 : 131 : r = bf_bpf_obj_pin("bf_link", link->fd, lock->chain_fd);
318 [ - + ]: 131 : if (r)
319 [ # # ]: 0 : return bf_err_r(r, "failed to pin BPF link");
320 : :
321 [ + + ]: 131 : if (link->fd_extra >= 0) {
322 : 7 : r = bf_bpf_obj_pin("bf_link_extra", link->fd_extra, lock->chain_fd);
323 [ + - ]: 7 : if (r) {
324 : 0 : bf_link_unpin(link, lock);
325 [ # # ]: 0 : return bf_err_r(r, "failed to pin extra BPF link");
326 : : }
327 : : }
328 : :
329 : : return 0;
330 : : }
331 : :
332 : 122 : void bf_link_unpin(struct bf_link *link, struct bf_lock *lock)
333 : : {
334 : : int r;
335 : :
336 : : assert(lock);
337 : :
338 : : (void)link;
339 : :
340 : 122 : r = unlinkat(lock->chain_fd, "bf_link", 0);
341 [ - + - - ]: 122 : if (r < 0 && errno != ENOENT) {
342 : : // Do not warn on ENOENT, we want the file to be gone!
343 [ # # ]: 0 : bf_warn_r(errno,
344 : : "failed to unlink BPF link, assuming the link is not pinned");
345 : : }
346 : :
347 : 122 : r = unlinkat(lock->chain_fd, "bf_link_extra", 0);
348 [ + + - + ]: 122 : if (r < 0 && errno != ENOENT) {
349 : : // Do not warn on ENOENT, we want the file to be gone!
350 [ # # ]: 0 : bf_warn_r(
351 : : errno,
352 : : "failed to unlink extra BPF link, assuming the link is not pinned");
353 : : }
354 : 122 : }
|