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