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