Line data Source code
1 : /* SPDX-License-Identifier: GPL-2.0-only */
2 : /*
3 : * Copyright (c) 2023 Meta Platforms, Inc. and affiliates.
4 : */
5 :
6 : #include "ctx.h"
7 :
8 : #include <errno.h>
9 : #include <stdbool.h>
10 : #include <stdlib.h>
11 : #include <unistd.h>
12 :
13 : #include "bpfilter/cgen/cgen.h"
14 : #include "core/chain.h"
15 : #include "core/dump.h"
16 : #include "core/front.h"
17 : #include "core/helper.h"
18 : #include "core/hook.h"
19 : #include "core/list.h"
20 : #include "core/logger.h"
21 : #include "core/marsh.h"
22 : #include "core/ns.h"
23 :
24 : #define _cleanup_bf_ctx_ __attribute__((cleanup(_bf_ctx_free)))
25 :
26 : /**
27 : * @struct bf_ctx
28 : *
29 : * bpfilter working context. Only one context is used during the daemon's
30 : * lifetime.
31 : */
32 : struct bf_ctx
33 : {
34 : /// Namespaces the daemon was started in.
35 : struct bf_ns ns;
36 :
37 : /// Codegens defined in bpfilter. Defined as an array of lists as some
38 : /// hooks can have multiple codegens (e.g. XDP).
39 : bf_list cgens[_BF_HOOK_MAX];
40 : };
41 :
42 : static void _bf_ctx_free(struct bf_ctx **ctx);
43 :
44 : /// Global daemon context. Hidden in this translation unit.
45 : static struct bf_ctx *_bf_global_ctx = NULL;
46 :
47 : /**
48 : * Get the requested BF_HOOK_XDP codegen from the list.
49 : *
50 : * Use @c opts->ifindex to find the expected codegen and return it.
51 : *
52 : * @param list List containing all the BF_HOOK_XDP codegens. Can't be NULL.
53 : * @param opts Hook options, @c opts->ifindex is used to find the correct
54 : * codegen. Can't be NULL.
55 : * @return The request codegen, or NULL if not found.
56 : */
57 0 : static struct bf_cgen *_bf_ctx_get_xdp_cgen(const bf_list *list,
58 : const struct bf_hook_opts *opts)
59 : {
60 0 : bf_list_foreach (list, cgen_node) {
61 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
62 :
63 0 : if (cgen->chain->hook_opts.ifindex == opts->ifindex)
64 : return cgen;
65 : }
66 :
67 : return NULL;
68 : }
69 :
70 : /**
71 : * Get the requested BF_HOOK_NF_* codegen from the list.
72 : *
73 : * There can be only one codegen defined for each BF_HOOK_NF_* hook, so we
74 : * return the first of the list, if defined.
75 : *
76 : * @param list List containing all the BF_HOOK_NF_* codegens. Can't be NULL.
77 : * @param opts Unused.
78 : * @return The requested codegen, or NULL if not found.
79 : */
80 0 : static struct bf_cgen *_bf_ctx_get_nf_cgen(const bf_list *list,
81 : const struct bf_hook_opts *opts)
82 : {
83 : UNUSED(opts);
84 : struct bf_list_node *node;
85 :
86 0 : bf_assert(list);
87 :
88 0 : node = bf_list_get_head(list);
89 0 : return node ? bf_list_node_get_data(node) : NULL;
90 : }
91 :
92 0 : static struct bf_cgen *_bf_ctx_get_cgroup_cgen(const bf_list *list,
93 : const struct bf_hook_opts *opts)
94 : {
95 0 : bf_list_foreach (list, cgen_node) {
96 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
97 :
98 0 : if (bf_streq(cgen->chain->hook_opts.cgroup, opts->cgroup))
99 : return cgen;
100 : }
101 :
102 : return NULL;
103 : }
104 :
105 : static struct bf_cgen *(*_bf_cgen_getters[])(
106 : const bf_list *list, const struct bf_hook_opts *opts) = {
107 : [BF_HOOK_XDP] = _bf_ctx_get_xdp_cgen,
108 : [BF_HOOK_TC_INGRESS] = _bf_ctx_get_xdp_cgen,
109 : [BF_HOOK_NF_PRE_ROUTING] = _bf_ctx_get_nf_cgen,
110 : [BF_HOOK_NF_LOCAL_IN] = _bf_ctx_get_nf_cgen,
111 : [BF_HOOK_CGROUP_INGRESS] = _bf_ctx_get_cgroup_cgen,
112 : [BF_HOOK_CGROUP_EGRESS] = _bf_ctx_get_cgroup_cgen,
113 : [BF_HOOK_NF_FORWARD] = _bf_ctx_get_nf_cgen,
114 : [BF_HOOK_NF_LOCAL_OUT] = _bf_ctx_get_nf_cgen,
115 : [BF_HOOK_NF_POST_ROUTING] = _bf_ctx_get_nf_cgen,
116 : [BF_HOOK_TC_EGRESS] = _bf_ctx_get_xdp_cgen,
117 : };
118 :
119 : static_assert(ARRAY_SIZE(_bf_cgen_getters) == _BF_HOOK_MAX,
120 : "missing entries in _bf_cgen_getters array");
121 :
122 : /**
123 : * Create and initialize a new context.
124 : *
125 : * On failure, @p ctx is left unchanged.
126 : *
127 : * @param ctx New context to create. Can't be NULL.
128 : * @return 0 on success, negative errno value on failure.
129 : */
130 4 : static int _bf_ctx_new(struct bf_ctx **ctx)
131 : {
132 3 : _cleanup_bf_ctx_ struct bf_ctx *_ctx = NULL;
133 : int r;
134 :
135 4 : bf_assert(ctx);
136 :
137 3 : _ctx = malloc(sizeof(*_ctx));
138 3 : if (!_ctx)
139 : return -ENOMEM;
140 :
141 3 : r = bf_ns_init(&_ctx->ns, getpid());
142 3 : if (r)
143 0 : return bf_err_r(r, "failed to initialise current bf_ns");
144 :
145 33 : for (int i = 0; i < _BF_HOOK_MAX; ++i)
146 30 : _ctx->cgens[i] = bf_cgen_list();
147 :
148 3 : *ctx = TAKE_PTR(_ctx);
149 :
150 3 : return 0;
151 : }
152 :
153 : /**
154 : * Allocate a new context and initialise it from serialised data.
155 : *
156 : * @param ctx On success, points to the newly allocated and initialised
157 : * context. Can't be NULL.
158 : * @param marsh Serialised data to use to initialise the context.
159 : * @return 0 on success, or negative errno value on failure.
160 : */
161 0 : static int _bf_ctx_new_from_marsh(struct bf_ctx **ctx,
162 : const struct bf_marsh *marsh)
163 : {
164 0 : _cleanup_bf_ctx_ struct bf_ctx *_ctx = NULL;
165 : struct bf_marsh *list_elem = NULL;
166 : int i = 0;
167 : int r;
168 :
169 0 : bf_assert(ctx && marsh);
170 :
171 0 : r = _bf_ctx_new(&_ctx);
172 0 : if (r < 0)
173 : return r;
174 :
175 : // Unmarsh bf_ctx.cgens
176 0 : while ((list_elem = bf_marsh_next_child(marsh, list_elem))) {
177 : struct bf_marsh *cgen_elem = NULL;
178 :
179 0 : while ((cgen_elem = bf_marsh_next_child(list_elem, cgen_elem))) {
180 0 : _cleanup_bf_cgen_ struct bf_cgen *cgen = NULL;
181 :
182 0 : r = bf_cgen_new_from_marsh(&cgen, cgen_elem);
183 0 : if (r < 0)
184 : return r;
185 :
186 0 : r = bf_list_add_tail(&_ctx->cgens[i], cgen);
187 0 : if (r < 0)
188 : return r;
189 :
190 0 : TAKE_PTR(cgen);
191 : }
192 :
193 0 : ++i;
194 : }
195 :
196 0 : *ctx = TAKE_PTR(_ctx);
197 :
198 0 : return 0;
199 : }
200 :
201 : /**
202 : * Free a context.
203 : *
204 : * If @p ctx points to a NULL pointer, this function does nothing. Once
205 : * the function returns, @p ctx points to a NULL pointer.
206 : *
207 : * @param ctx Context to free. Can't be NULL.
208 : */
209 9 : static void _bf_ctx_free(struct bf_ctx **ctx)
210 : {
211 9 : bf_assert(ctx);
212 :
213 8 : if (!*ctx)
214 : return;
215 :
216 3 : bf_ns_clean(&(*ctx)->ns);
217 :
218 33 : for (int i = 0; i < _BF_HOOK_MAX; ++i)
219 30 : bf_list_clean(&(*ctx)->cgens[i]);
220 :
221 : freep((void *)ctx);
222 : }
223 :
224 : /**
225 : * See @ref bf_ctx_dump for details.
226 : */
227 0 : static void _bf_ctx_dump(const struct bf_ctx *ctx, prefix_t *prefix)
228 : {
229 0 : DUMP(prefix, "struct bf_ctx at %p", ctx);
230 :
231 0 : bf_dump_prefix_push(prefix);
232 :
233 : // Namespaces
234 0 : DUMP(prefix, "ns: struct bf_ns")
235 0 : bf_dump_prefix_push(prefix);
236 :
237 0 : DUMP(prefix, "net: struct bf_ns_info");
238 0 : bf_dump_prefix_push(prefix);
239 0 : DUMP(prefix, "fd: %d", ctx->ns.net.fd);
240 0 : DUMP(bf_dump_prefix_last(prefix), "inode: %u", ctx->ns.net.inode);
241 0 : bf_dump_prefix_pop(prefix);
242 :
243 0 : DUMP(bf_dump_prefix_last(prefix), "mnt: struct bf_ns_info");
244 0 : bf_dump_prefix_push(prefix);
245 0 : DUMP(prefix, "fd: %d", ctx->ns.mnt.fd);
246 0 : DUMP(bf_dump_prefix_last(prefix), "inode: %u", ctx->ns.mnt.inode);
247 0 : bf_dump_prefix_pop(prefix);
248 :
249 0 : bf_dump_prefix_pop(prefix);
250 :
251 : // Codegens
252 0 : DUMP(bf_dump_prefix_last(prefix), "cgens: bf_list[%d]", _BF_HOOK_MAX);
253 0 : bf_dump_prefix_push(prefix);
254 :
255 0 : for (int i = 0; i < _BF_HOOK_MAX; ++i) {
256 0 : if (i == _BF_HOOK_MAX - 1)
257 0 : bf_dump_prefix_last(prefix);
258 :
259 0 : DUMP(prefix, "bf_list<bf_cgen>[%lu]", bf_list_size(&ctx->cgens[i]));
260 0 : bf_dump_prefix_push(prefix);
261 :
262 0 : bf_list_foreach (&ctx->cgens[i], cgen_node) {
263 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
264 :
265 0 : if (bf_list_is_tail(&ctx->cgens[i], cgen_node))
266 0 : bf_dump_prefix_last(prefix);
267 :
268 0 : bf_cgen_dump(cgen, prefix);
269 : }
270 :
271 0 : bf_dump_prefix_pop(prefix);
272 : }
273 :
274 0 : bf_dump_prefix_pop(prefix);
275 0 : }
276 :
277 : /**
278 : * Marsh a context.
279 : *
280 : * If the function succeeds, @p marsh will contain the marshalled context.
281 : *
282 : * @ref bf_ctx only contain the codegens, so the serialized data can be
283 : * flattened to:
284 : * - ctx marsh
285 : * - list marsh
286 : * - cgen marsh
287 : * - ...
288 : * - list marsh
289 : * - ...
290 : *
291 : * @param ctx Context to marsh.
292 : * @param marsh Marsh'd context.
293 : * @return 0 on success, negative errno value on failure.
294 : */
295 0 : static int _bf_ctx_marsh(const struct bf_ctx *ctx, struct bf_marsh **marsh)
296 : {
297 0 : _cleanup_bf_marsh_ struct bf_marsh *_marsh = NULL;
298 : int r;
299 :
300 0 : bf_assert(ctx && marsh);
301 :
302 0 : r = bf_marsh_new(&_marsh, NULL, 0);
303 0 : if (r)
304 0 : return bf_err_r(r, "failed to create marsh for context");
305 :
306 0 : for (int i = 0; i < _BF_HOOK_MAX; ++i) {
307 0 : _cleanup_bf_marsh_ struct bf_marsh *child = NULL;
308 :
309 0 : r = bf_list_marsh(&ctx->cgens[i], &child);
310 0 : if (r < 0)
311 : return r;
312 :
313 0 : r = bf_marsh_add_child_obj(&_marsh, child);
314 0 : if (r)
315 0 : return bf_err_r(r, "failed to append codegen marsh");
316 : }
317 :
318 0 : *marsh = TAKE_PTR(_marsh);
319 :
320 0 : return 0;
321 : }
322 :
323 : /**
324 : * See @ref bf_ctx_get_cgen for details.
325 : */
326 0 : static struct bf_cgen *_bf_ctx_get_cgen(const struct bf_ctx *ctx,
327 : enum bf_hook hook,
328 : const struct bf_hook_opts *opts)
329 : {
330 0 : bf_assert(ctx);
331 :
332 0 : return _bf_cgen_getters[hook](&ctx->cgens[hook], opts);
333 : }
334 :
335 : /**
336 : * See @ref bf_ctx_get_cgens_for_front for details.
337 : */
338 0 : static int _bf_ctx_get_cgens_for_front(const struct bf_ctx *ctx, bf_list *cgens,
339 : enum bf_front front)
340 : {
341 0 : _clean_bf_list_ bf_list _cgens = bf_list_default(NULL, bf_cgen_marsh);
342 : int r;
343 :
344 0 : bf_assert(ctx && cgens);
345 :
346 0 : for (int i = 0; i < _BF_HOOK_MAX; ++i) {
347 0 : bf_list_foreach (&ctx->cgens[i], cgen_node) {
348 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
349 :
350 0 : if (cgen->front != front)
351 0 : continue;
352 :
353 0 : r = bf_list_add_tail(&_cgens, cgen);
354 0 : if (r)
355 0 : return bf_err_r(r, "failed to insert codegen into list");
356 : }
357 : }
358 :
359 0 : *cgens = bf_list_move(_cgens);
360 :
361 0 : return 0;
362 : }
363 :
364 : /**
365 : * See @ref bf_ctx_set_cgen for details.
366 : */
367 0 : static int _bf_ctx_set_cgen(struct bf_ctx *ctx, struct bf_cgen *cgen)
368 : {
369 0 : bf_assert(ctx && cgen);
370 :
371 0 : if (_bf_ctx_get_cgen(ctx, cgen->chain->hook, &cgen->chain->hook_opts))
372 0 : return bf_err_r(-EEXIST, "codegen already exists in context");
373 :
374 0 : return bf_list_add_tail(&ctx->cgens[cgen->chain->hook], cgen);
375 : }
376 :
377 0 : int bf_ctx_setup(void)
378 : {
379 0 : _cleanup_bf_ctx_ struct bf_ctx *_ctx = NULL;
380 : int r;
381 :
382 0 : bf_assert(!_ctx);
383 :
384 0 : r = _bf_ctx_new(&_ctx);
385 0 : if (r)
386 0 : return bf_err_r(r, "failed to create new context");
387 :
388 0 : _bf_global_ctx = TAKE_PTR(_ctx);
389 :
390 0 : return 0;
391 : }
392 :
393 0 : void bf_ctx_teardown(bool clear)
394 : {
395 0 : if (clear) {
396 0 : for (int i = 0; i < _BF_HOOK_MAX; ++i) {
397 0 : bf_list_foreach (&_bf_global_ctx->cgens[i], cgen_node)
398 0 : bf_cgen_unload(bf_list_node_get_data(cgen_node));
399 : }
400 : }
401 :
402 0 : _bf_ctx_free(&_bf_global_ctx);
403 0 : }
404 :
405 0 : int bf_ctx_save(struct bf_marsh **marsh)
406 : {
407 0 : _cleanup_bf_marsh_ struct bf_marsh *_marsh = NULL;
408 : int r;
409 :
410 0 : bf_assert(marsh);
411 :
412 0 : r = _bf_ctx_marsh(_bf_global_ctx, &_marsh);
413 0 : if (r)
414 0 : return bf_err_r(r, "failed to serialize context");
415 :
416 0 : *marsh = TAKE_PTR(_marsh);
417 :
418 0 : return 0;
419 : }
420 :
421 0 : int bf_ctx_load(const struct bf_marsh *marsh)
422 : {
423 0 : _cleanup_bf_ctx_ struct bf_ctx *ctx = NULL;
424 : int r;
425 :
426 0 : bf_assert(marsh);
427 :
428 0 : r = _bf_ctx_new_from_marsh(&ctx, marsh);
429 0 : if (r)
430 0 : return bf_err_r(r, "failed to deserialize context");
431 :
432 0 : _bf_global_ctx = TAKE_PTR(ctx);
433 :
434 0 : return 0;
435 : }
436 :
437 0 : int bf_ctx_flush(void)
438 : {
439 0 : _cleanup_bf_ctx_ struct bf_ctx *_ctx = NULL;
440 : int r;
441 : int err = 0;
442 :
443 0 : r = _bf_ctx_new(&_ctx);
444 0 : if (r)
445 0 : return bf_err_r(r, "failed to create new context");
446 :
447 0 : for (int i = 0; i < _BF_HOOK_MAX; ++i) {
448 0 : bf_list_foreach (&_bf_global_ctx->cgens[i], cgen_node) {
449 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
450 0 : r = bf_cgen_unload(cgen);
451 0 : if (r) {
452 0 : bf_err("failed to unload a %s program attached to %s",
453 : bf_front_to_str(cgen->front),
454 : bf_hook_to_str(cgen->chain->hook));
455 0 : err = err ?: r;
456 : }
457 : }
458 : }
459 :
460 0 : _bf_ctx_free(&_bf_global_ctx);
461 :
462 0 : _bf_global_ctx = TAKE_PTR(_ctx);
463 :
464 0 : if (err)
465 0 : bf_warn("the global context has been partially flushed");
466 : else
467 0 : bf_info("the global context has been flushed");
468 :
469 : return err;
470 : }
471 :
472 0 : bool bf_ctx_is_empty(void)
473 : {
474 0 : for (int i = 0; i < _BF_HOOK_MAX; ++i) {
475 0 : if (!bf_list_is_empty(&_bf_global_ctx->cgens[i]))
476 : return false;
477 : }
478 :
479 : return true;
480 : }
481 :
482 0 : void bf_ctx_dump(prefix_t *prefix)
483 : {
484 0 : _bf_ctx_dump(_bf_global_ctx, prefix);
485 0 : }
486 :
487 0 : struct bf_cgen *bf_ctx_get_cgen(enum bf_hook hook,
488 : const struct bf_hook_opts *opts)
489 : {
490 0 : return _bf_ctx_get_cgen(_bf_global_ctx, hook, opts);
491 : }
492 :
493 0 : int bf_ctx_get_cgens_for_front(bf_list *cgens, enum bf_front front)
494 : {
495 0 : return _bf_ctx_get_cgens_for_front(_bf_global_ctx, cgens, front);
496 : }
497 :
498 0 : int bf_ctx_set_cgen(struct bf_cgen *cgen)
499 : {
500 0 : return _bf_ctx_set_cgen(_bf_global_ctx, cgen);
501 : }
502 :
503 0 : struct bf_ns *bf_ctx_get_ns(void)
504 : {
505 0 : return &_bf_global_ctx->ns;
506 : }
|