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