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 <errno.h>
7 : #include <stdlib.h>
8 :
9 : #include "bpfilter/cgen/cgen.h"
10 : #include "bpfilter/cgen/prog/link.h"
11 : #include "bpfilter/cgen/program.h"
12 : #include "bpfilter/ctx.h"
13 : #include "bpfilter/xlate/front.h"
14 : #include "core/chain.h"
15 : #include "core/counter.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/request.h"
23 : #include "core/response.h"
24 : #include "core/rule.h"
25 :
26 : static int _bf_cli_setup(void);
27 : static int _bf_cli_teardown(void);
28 : static int _bf_cli_request_handler(struct bf_request *request,
29 : struct bf_response **response);
30 : static int _bf_cli_marsh(struct bf_marsh **marsh);
31 : static int _bf_cli_unmarsh(struct bf_marsh *marsh);
32 :
33 : const struct bf_front_ops cli_front = {
34 : .setup = _bf_cli_setup,
35 : .teardown = _bf_cli_teardown,
36 : .request_handler = _bf_cli_request_handler,
37 : .marsh = _bf_cli_marsh,
38 : .unmarsh = _bf_cli_unmarsh,
39 : };
40 :
41 0 : static int _bf_cli_setup(void)
42 : {
43 0 : return 0;
44 : }
45 :
46 0 : static int _bf_cli_teardown(void)
47 : {
48 0 : return 0;
49 : }
50 :
51 0 : int _bf_cli_ruleset_flush(const struct bf_request *request,
52 : struct bf_response **response)
53 : {
54 : UNUSED(request);
55 : UNUSED(response);
56 :
57 0 : bf_ctx_flush(BF_FRONT_CLI);
58 :
59 0 : return 0;
60 : }
61 :
62 0 : static int _bf_cli_ruleset_get(const struct bf_request *request,
63 : struct bf_response **response)
64 : {
65 0 : _cleanup_bf_marsh_ struct bf_marsh *marsh = NULL;
66 0 : _cleanup_bf_marsh_ struct bf_marsh *chain_marsh = NULL;
67 0 : _cleanup_bf_marsh_ struct bf_marsh *hookopts_marsh = NULL;
68 0 : _cleanup_bf_marsh_ struct bf_marsh *counters_marsh = NULL;
69 0 : _clean_bf_list_ bf_list cgens = bf_list_default(NULL, NULL);
70 0 : _clean_bf_list_ bf_list chains = bf_list_default(NULL, bf_chain_marsh);
71 0 : _clean_bf_list_ bf_list hookopts = bf_list_default(NULL, bf_hookopts_marsh);
72 0 : _clean_bf_list_ bf_list counters =
73 : bf_list_default(bf_list_free, bf_list_marsh);
74 : int r;
75 :
76 : UNUSED(request);
77 :
78 0 : r = bf_marsh_new(&marsh, NULL, 0);
79 0 : if (r < 0)
80 0 : return bf_err_r(r, "failed to get new marsh");
81 :
82 0 : r = bf_ctx_get_cgens_for_front(&cgens, BF_FRONT_CLI);
83 0 : if (r < 0)
84 0 : return bf_err_r(r, "failed to get cgen list");
85 :
86 0 : bf_list_foreach (&cgens, cgen_node) {
87 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
88 0 : _cleanup_bf_list_ bf_list *cgen_counters = NULL;
89 :
90 0 : r = bf_list_add_tail(&chains, cgen->chain);
91 0 : if (r)
92 0 : return bf_err_r(r, "failed to add chain to list");
93 :
94 0 : r = bf_list_add_tail(&hookopts, cgen->program->link->hookopts);
95 0 : if (r)
96 0 : return bf_err_r(r, "failed to add hookopts to list");
97 :
98 0 : r = bf_list_new(&cgen_counters, &bf_list_ops_default(bf_counter_free,
99 : bf_counter_marsh));
100 0 : if (r)
101 : return r;
102 :
103 0 : r = bf_cgen_get_counters(cgen, cgen_counters);
104 0 : if (r)
105 : return r;
106 :
107 0 : r = bf_list_add_tail(&counters, cgen_counters);
108 0 : if (r)
109 : return r;
110 :
111 0 : TAKE_PTR(cgen_counters);
112 : }
113 :
114 : // Marsh the chain list
115 0 : r = bf_list_marsh(&chains, &chain_marsh);
116 0 : if (r < 0)
117 0 : return bf_err_r(r, "failed to marshal chains list");
118 :
119 0 : r = bf_marsh_add_child_obj(&marsh, chain_marsh);
120 0 : if (r < 0)
121 0 : return bf_err_r(r, "failed to add chain list to marsh");
122 :
123 : // Marsh the hookopts list
124 0 : r = bf_marsh_new(&hookopts_marsh, NULL, 0);
125 0 : bf_list_foreach (&hookopts, hookopts_node) {
126 0 : struct bf_hookopts *hookopts = bf_list_node_get_data(hookopts_node);
127 0 : _cleanup_bf_marsh_ struct bf_marsh *child = NULL;
128 :
129 0 : if (hookopts) {
130 0 : r = bf_hookopts_marsh(bf_list_node_get_data(hookopts_node), &child);
131 0 : if (r < 0)
132 : return r;
133 :
134 0 : r = bf_marsh_add_child_obj(&hookopts_marsh, child);
135 0 : if (r < 0)
136 : return r;
137 : } else {
138 0 : r = bf_marsh_add_child_raw(&hookopts_marsh, NULL, 0);
139 0 : if (r)
140 : return r;
141 : }
142 : }
143 :
144 0 : r = bf_marsh_add_child_obj(&marsh, hookopts_marsh);
145 0 : if (r < 0)
146 0 : return bf_err_r(r, "failed to add chain list to marsh");
147 :
148 : // Marsh the counters list
149 0 : r = bf_list_marsh(&counters, &counters_marsh);
150 0 : if (r < 0)
151 0 : return bf_err_r(r, "failed to marshal counters list");
152 :
153 0 : r = bf_marsh_add_child_obj(&marsh, counters_marsh);
154 0 : if (r < 0)
155 0 : return bf_err_r(r, "failed to add counters list to marsh");
156 :
157 0 : return bf_response_new_success(response, (void *)marsh,
158 : bf_marsh_size(marsh));
159 : }
160 :
161 0 : int _bf_cli_ruleset_set(const struct bf_request *request,
162 : struct bf_response **response)
163 : {
164 0 : _clean_bf_list_ bf_list cgens = bf_list_default(NULL, NULL);
165 : struct bf_marsh *marsh, *list_elem = NULL;
166 : int r;
167 :
168 0 : bf_assert(request && response);
169 :
170 : // Unmarsh the list of chains
171 0 : marsh = (struct bf_marsh *)request->data;
172 0 : if (bf_marsh_size(marsh) != request->data_len) {
173 0 : return bf_err_r(
174 : -EINVAL,
175 : "request payload is expected to have the same size as the marsh");
176 : }
177 :
178 0 : bf_ctx_flush(BF_FRONT_CLI);
179 :
180 0 : while ((list_elem = bf_marsh_next_child(marsh, list_elem))) {
181 0 : _cleanup_bf_cgen_ struct bf_cgen *cgen = NULL;
182 0 : _cleanup_bf_chain_ struct bf_chain *chain = NULL;
183 0 : _free_bf_hookopts_ struct bf_hookopts *hookopts = NULL;
184 : struct bf_marsh *child = NULL;
185 :
186 0 : child = bf_marsh_next_child(list_elem, child);
187 0 : if (!child)
188 0 : return bf_err_r(-ENOENT, "expecting marsh for chain, none found");
189 :
190 0 : r = bf_chain_new_from_marsh(&chain, child);
191 0 : if (r)
192 0 : goto err_load;
193 :
194 0 : child = bf_marsh_next_child(list_elem, child);
195 0 : if (!child)
196 0 : return bf_err_r(-ENOENT, "expecting marsh for hook, none found");
197 0 : if (child->data_len) {
198 0 : r = bf_hookopts_new_from_marsh(&hookopts, child);
199 0 : if (r)
200 0 : goto err_load;
201 : }
202 :
203 0 : r = bf_cgen_new(&cgen, BF_FRONT_CLI, &chain);
204 0 : if (r)
205 0 : goto err_load;
206 :
207 0 : r = bf_cgen_set(cgen, request->ns, hookopts ? &hookopts : NULL);
208 0 : if (r) {
209 0 : bf_err_r(r, "failed to set chain '%s'", cgen->chain->name);
210 0 : goto err_load;
211 : }
212 :
213 0 : r = bf_ctx_set_cgen(cgen);
214 0 : if (r) {
215 : /* The codegen is loaded already, if the daemon runs in persistent
216 : * mode, cleaning the codegen won't be sufficient to discard the
217 : * chain, it must be unpinned. */
218 0 : bf_cgen_unload(cgen);
219 0 : goto err_load;
220 : }
221 :
222 0 : TAKE_PTR(cgen);
223 : }
224 :
225 : return 0;
226 :
227 : err_load:
228 0 : bf_ctx_flush(BF_FRONT_CLI);
229 0 : return r;
230 : }
231 :
232 0 : int _bf_cli_chain_set(const struct bf_request *request,
233 : struct bf_response **response)
234 : {
235 : struct bf_cgen *old_cgen;
236 : struct bf_marsh *marsh, *child = NULL;
237 0 : _cleanup_bf_cgen_ struct bf_cgen *new_cgen = NULL;
238 0 : _cleanup_bf_chain_ struct bf_chain *chain = NULL;
239 0 : _free_bf_hookopts_ struct bf_hookopts *hookopts = NULL;
240 : int r;
241 :
242 0 : bf_assert(request && response);
243 :
244 0 : marsh = (struct bf_marsh *)request->data;
245 0 : if (bf_marsh_size(marsh) != request->data_len) {
246 0 : return bf_err_r(
247 : -EINVAL,
248 : "request payload is expected to have the same size as the marsh");
249 : }
250 :
251 0 : child = bf_marsh_next_child(marsh, child);
252 0 : if (!child)
253 0 : return bf_err_r(-ENOENT, "expecting marsh for chain, none found");
254 0 : r = bf_chain_new_from_marsh(&chain, child);
255 0 : if (r)
256 : return r;
257 :
258 0 : child = bf_marsh_next_child(marsh, child);
259 0 : if (!child)
260 0 : return bf_err_r(-ENOENT, "expecting marsh for hookopts, none found");
261 0 : if (child->data_len) {
262 0 : r = bf_hookopts_new_from_marsh(&hookopts, child);
263 0 : if (r)
264 : return r;
265 : }
266 :
267 0 : r = bf_cgen_new(&new_cgen, BF_FRONT_CLI, &chain);
268 0 : if (r)
269 : return r;
270 :
271 0 : old_cgen = bf_ctx_get_cgen(new_cgen->chain->name);
272 0 : if (old_cgen) {
273 : /* bf_ctx_delete_cgen() can only fail if the codegen is not found,
274 : * but we know this codegen exist. */
275 0 : (void)bf_ctx_delete_cgen(old_cgen, true);
276 : }
277 :
278 0 : r = bf_cgen_set(new_cgen, request->ns, hookopts ? &hookopts : NULL);
279 0 : if (r)
280 : return r;
281 :
282 0 : r = bf_ctx_set_cgen(new_cgen);
283 0 : if (r) {
284 0 : bf_cgen_unload(new_cgen);
285 0 : return r;
286 : }
287 :
288 0 : TAKE_PTR(new_cgen);
289 :
290 0 : return r;
291 : }
292 :
293 0 : static int _bf_cli_chain_get(const struct bf_request *request,
294 : struct bf_response **response)
295 : {
296 0 : _cleanup_bf_marsh_ struct bf_marsh *marsh = NULL;
297 0 : _clean_bf_list_ bf_list counters =
298 : bf_list_default(bf_counter_free, bf_counter_marsh);
299 : struct bf_cgen *cgen;
300 : struct bf_marsh *req_marsh, *child = NULL;
301 : int r;
302 :
303 0 : req_marsh = (struct bf_marsh *)request->data;
304 0 : if (bf_marsh_size(req_marsh) != request->data_len) {
305 0 : return bf_err_r(
306 : -EINVAL,
307 : "request payload is expected to have the same size as the marsh");
308 : }
309 :
310 0 : if (!(child = bf_marsh_next_child(req_marsh, child)))
311 : return -EINVAL;
312 0 : if (child->data_len < 2)
313 0 : return bf_err_r(-EINVAL, "_bf_cli_chain_get: chain name is empty");
314 0 : if (child->data[child->data_len - 1]) {
315 0 : return bf_err_r(-EINVAL,
316 : "_bf_cli_chain_get: chain name if not nul-terminated");
317 : }
318 :
319 0 : cgen = bf_ctx_get_cgen(child->data);
320 0 : if (!cgen)
321 0 : return bf_err_r(-ENOENT, "chain '%s' not found", req_marsh->data);
322 :
323 0 : r = bf_marsh_new(&marsh, NULL, 0);
324 0 : if (r < 0)
325 0 : return bf_err_r(r, "failed to get new marsh");
326 :
327 : {
328 0 : _cleanup_bf_marsh_ struct bf_marsh *child = NULL;
329 :
330 0 : r = bf_chain_marsh(cgen->chain, &child);
331 0 : if (r)
332 : return r;
333 :
334 0 : r = bf_marsh_add_child_obj(&marsh, child);
335 0 : if (r)
336 : return r;
337 : }
338 :
339 0 : if (cgen->program->link->hookopts) {
340 0 : _cleanup_bf_marsh_ struct bf_marsh *child = NULL;
341 :
342 0 : r = bf_hookopts_marsh(cgen->program->link->hookopts, &child);
343 0 : if (r)
344 : return r;
345 :
346 0 : r = bf_marsh_add_child_obj(&marsh, child);
347 0 : if (r < 0)
348 : return r;
349 : } else {
350 0 : r = bf_marsh_add_child_raw(&marsh, NULL, 0);
351 0 : if (r)
352 : return r;
353 : }
354 :
355 : {
356 0 : _cleanup_bf_marsh_ struct bf_marsh *child = NULL;
357 :
358 0 : r = bf_cgen_get_counters(cgen, &counters);
359 0 : if (r)
360 : return r;
361 :
362 0 : r = bf_list_marsh(&counters, &child);
363 0 : if (r)
364 : return r;
365 :
366 0 : r = bf_marsh_add_child_obj(&marsh, child);
367 0 : if (r)
368 : return r;
369 : }
370 :
371 0 : return bf_response_new_success(response, (void *)marsh,
372 : bf_marsh_size(marsh));
373 : }
374 :
375 0 : int _bf_cli_chain_load(const struct bf_request *request,
376 : struct bf_response **response)
377 : {
378 : struct bf_marsh *marsh, *child = NULL;
379 0 : _cleanup_bf_cgen_ struct bf_cgen *cgen = NULL;
380 0 : _cleanup_bf_chain_ struct bf_chain *chain = NULL;
381 : int r;
382 :
383 0 : bf_assert(request && response);
384 :
385 0 : marsh = (struct bf_marsh *)request->data;
386 0 : if (bf_marsh_size(marsh) != request->data_len) {
387 0 : return bf_err_r(
388 : -EINVAL,
389 : "request payload is expected to have the same size as the marsh");
390 : }
391 :
392 0 : child = bf_marsh_next_child(marsh, child);
393 0 : if (!child)
394 0 : return bf_err_r(-ENOENT, "expecting marsh for chain, none found");
395 :
396 0 : r = bf_chain_new_from_marsh(&chain, child);
397 0 : if (r)
398 : return r;
399 :
400 0 : if (bf_ctx_get_cgen(chain->name)) {
401 0 : return bf_err_r(-EEXIST,
402 : "_bf_cli_chain_load: chain '%s' already exists",
403 : chain->name);
404 : }
405 :
406 0 : r = bf_cgen_new(&cgen, BF_FRONT_CLI, &chain);
407 0 : if (r)
408 : return r;
409 :
410 0 : r = bf_cgen_load(cgen);
411 0 : if (r)
412 : return r;
413 :
414 0 : r = bf_ctx_set_cgen(cgen);
415 0 : if (r) {
416 0 : bf_cgen_unload(cgen);
417 0 : return bf_err_r(
418 : r, "bf_ctx_set_cgen: failed to add cgen to the runtime context");
419 : }
420 :
421 0 : TAKE_PTR(cgen);
422 :
423 0 : return r;
424 : }
425 :
426 0 : int _bf_cli_chain_attach(const struct bf_request *request,
427 : struct bf_response **response)
428 : {
429 : struct bf_marsh *marsh, *child = NULL;
430 0 : _cleanup_bf_chain_ struct bf_chain *chain = NULL;
431 : struct bf_cgen *cgen = NULL;
432 : const char *name;
433 0 : _free_bf_hookopts_ struct bf_hookopts *hookopts = NULL;
434 : int r;
435 :
436 0 : bf_assert(request && response);
437 :
438 0 : marsh = (struct bf_marsh *)request->data;
439 0 : if (bf_marsh_size(marsh) != request->data_len) {
440 0 : return bf_err_r(
441 : -EINVAL,
442 : "request payload is expected to have the same size as the marsh");
443 : }
444 :
445 0 : if (!(child = bf_marsh_next_child(marsh, child)))
446 : return -EINVAL;
447 0 : if (marsh->data[marsh->data_len - 1]) {
448 0 : return bf_err_r(
449 : -EINVAL, "_bf_cli_chain_attach: chain name if not nul-terminated");
450 : }
451 0 : name = child->data;
452 :
453 0 : if (!(child = bf_marsh_next_child(marsh, child)))
454 : return -EINVAL;
455 0 : r = bf_hookopts_new_from_marsh(&hookopts, child);
456 0 : if (r)
457 : return r;
458 :
459 0 : cgen = bf_ctx_get_cgen(name);
460 0 : if (!cgen)
461 0 : return bf_err_r(-ENOENT, "chain '%s' does not exist", name);
462 0 : if (cgen->program->link->hookopts)
463 0 : return bf_err_r(-EBUSY, "chain '%s' is already linked to a hook", name);
464 :
465 0 : r = bf_hookopts_validate(hookopts, cgen->chain->hook);
466 0 : if (r)
467 0 : return bf_err_r(r, "failed to validate hook options");
468 :
469 0 : r = bf_cgen_attach(cgen, request->ns, &hookopts);
470 0 : if (r)
471 0 : return bf_err_r(r, "failed to attach codegen to hook");
472 :
473 : return r;
474 : }
475 :
476 0 : int _bf_cli_chain_update(const struct bf_request *request,
477 : struct bf_response **response)
478 : {
479 : struct bf_marsh *marsh, *child = NULL;
480 0 : _cleanup_bf_chain_ struct bf_chain *chain = NULL;
481 : struct bf_cgen *cgen = NULL;
482 : int r;
483 :
484 0 : bf_assert(request && response);
485 :
486 0 : marsh = (struct bf_marsh *)request->data;
487 0 : if (bf_marsh_size(marsh) != request->data_len) {
488 0 : return bf_err_r(
489 : -EINVAL,
490 : "request payload is expected to have the same size as the marsh");
491 : }
492 :
493 0 : child = bf_marsh_next_child(marsh, child);
494 0 : if (!child)
495 : return -EINVAL;
496 0 : r = bf_chain_new_from_marsh(&chain, child);
497 0 : if (r)
498 : return r;
499 :
500 0 : cgen = bf_ctx_get_cgen(chain->name);
501 0 : if (!cgen)
502 : return -ENOENT;
503 :
504 0 : if (!cgen->program->link->hookopts) {
505 0 : return bf_err_r(-EINVAL, "chain '%s' is not attached", chain->name);
506 : }
507 :
508 0 : r = bf_cgen_update(cgen, &chain);
509 0 : if (r)
510 : return -EINVAL;
511 :
512 : return r;
513 : }
514 :
515 0 : int _bf_cli_chain_flush(const struct bf_request *request,
516 : struct bf_response **response)
517 : {
518 : struct bf_marsh *marsh, *child = NULL;
519 : struct bf_cgen *cgen = NULL;
520 :
521 0 : bf_assert(request && response);
522 :
523 0 : marsh = (struct bf_marsh *)request->data;
524 0 : if (bf_marsh_size(marsh) != request->data_len) {
525 0 : return bf_err_r(
526 : -EINVAL,
527 : "request payload is expected to have the same size as the marsh");
528 : }
529 :
530 0 : if (!(child = bf_marsh_next_child(marsh, child)))
531 : return -EINVAL;
532 0 : if (child->data_len < 2)
533 0 : return bf_err_r(-EINVAL, "_bf_cli_chain_flush: chain name is empty");
534 0 : if (child->data[child->data_len - 1]) {
535 0 : return bf_err_r(
536 : -EINVAL, "_bf_cli_chain_flush: chain name if not nul-terminated");
537 : }
538 :
539 0 : cgen = bf_ctx_get_cgen(child->data);
540 0 : if (!cgen)
541 : return -ENOENT;
542 :
543 0 : return bf_ctx_delete_cgen(cgen, true);
544 : }
545 :
546 0 : static int _bf_cli_request_handler(struct bf_request *request,
547 : struct bf_response **response)
548 : {
549 : int r;
550 :
551 0 : bf_assert(request);
552 0 : bf_assert(response);
553 :
554 0 : switch (request->cmd) {
555 0 : case BF_REQ_RULESET_FLUSH:
556 0 : r = _bf_cli_ruleset_flush(request, response);
557 0 : break;
558 0 : case BF_REQ_RULESET_SET:
559 0 : r = _bf_cli_ruleset_set(request, response);
560 0 : break;
561 0 : case BF_REQ_RULESET_GET:
562 0 : r = _bf_cli_ruleset_get(request, response);
563 0 : break;
564 0 : case BF_REQ_CHAIN_SET:
565 0 : r = _bf_cli_chain_set(request, response);
566 0 : break;
567 0 : case BF_REQ_CHAIN_GET:
568 0 : r = _bf_cli_chain_get(request, response);
569 0 : break;
570 0 : case BF_REQ_CHAIN_LOAD:
571 0 : r = _bf_cli_chain_load(request, response);
572 0 : break;
573 0 : case BF_REQ_CHAIN_ATTACH:
574 0 : r = _bf_cli_chain_attach(request, response);
575 0 : break;
576 0 : case BF_REQ_CHAIN_UPDATE:
577 0 : r = _bf_cli_chain_update(request, response);
578 0 : break;
579 0 : case BF_REQ_CHAIN_FLUSH:
580 0 : r = _bf_cli_chain_flush(request, response);
581 0 : break;
582 0 : default:
583 0 : r = bf_err_r(-EINVAL, "unsupported command %d for CLI front-end",
584 : request->cmd);
585 : break;
586 : }
587 :
588 : /* If the callback don't need to send data back to the client, it can skip
589 : * the response creation and return a status code instead (0 on success,
590 : * negative errno value on error). The response is created based on the
591 : * status code. */
592 0 : if (!*response) {
593 0 : if (!r)
594 0 : r = bf_response_new_success(response, NULL, 0);
595 : else
596 0 : r = bf_response_new_failure(response, r);
597 : }
598 :
599 0 : return r;
600 : }
601 :
602 0 : static int _bf_cli_marsh(struct bf_marsh **marsh)
603 : {
604 : UNUSED(marsh);
605 :
606 0 : return 0;
607 : }
608 :
609 0 : static int _bf_cli_unmarsh(struct bf_marsh *marsh)
610 : {
611 : UNUSED(marsh);
612 :
613 0 : return 0;
614 : }
|