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 "cgen/cgen.h"
7 :
8 : #include <errno.h>
9 : #include <stddef.h>
10 : #include <stdint.h>
11 : #include <stdlib.h>
12 : #include <string.h>
13 : #include <sys/types.h>
14 :
15 : #include <bpfilter/chain.h>
16 : #include <bpfilter/counter.h>
17 : #include <bpfilter/dump.h>
18 : #include <bpfilter/front.h>
19 : #include <bpfilter/helper.h>
20 : #include <bpfilter/hook.h>
21 : #include <bpfilter/io.h>
22 : #include <bpfilter/list.h>
23 : #include <bpfilter/logger.h>
24 : #include <bpfilter/ns.h>
25 : #include <bpfilter/pack.h>
26 :
27 : #include "cgen/dump.h"
28 : #include "cgen/prog/link.h"
29 : #include "cgen/program.h"
30 : #include "ctx.h"
31 : #include "opts.h"
32 :
33 0 : static int _bf_cgen_get_chain_pindir_fd(const char *name)
34 : {
35 0 : _cleanup_close_ int bf_fd = -1;
36 0 : _cleanup_close_ int chain_fd = -1;
37 :
38 0 : bf_assert(name);
39 :
40 0 : bf_fd = bf_ctx_get_pindir_fd();
41 0 : if (bf_fd < 0)
42 : return bf_fd;
43 :
44 0 : chain_fd = bf_opendir_at(bf_fd, name, true);
45 0 : if (chain_fd < 0)
46 : return chain_fd;
47 :
48 0 : return TAKE_FD(chain_fd);
49 : }
50 :
51 11 : int bf_cgen_new(struct bf_cgen **cgen, enum bf_front front,
52 : struct bf_chain **chain)
53 : {
54 14 : bf_assert(cgen && chain && *chain);
55 :
56 8 : *cgen = malloc(sizeof(struct bf_cgen));
57 8 : if (!*cgen)
58 : return -ENOMEM;
59 :
60 7 : (*cgen)->front = front;
61 7 : (*cgen)->program = NULL;
62 7 : (*cgen)->chain = NULL;
63 :
64 7 : if (!bf_chain_validate(*chain))
65 0 : return bf_err_r(-EINVAL, "chain '%s' is invalid", (*chain)->name);
66 7 : (*cgen)->chain = TAKE_PTR(*chain);
67 :
68 7 : return 0;
69 : }
70 :
71 1 : int bf_cgen_new_from_pack(struct bf_cgen **cgen, bf_rpack_node_t node)
72 : {
73 1 : _free_bf_cgen_ struct bf_cgen *_cgen = NULL;
74 : bf_rpack_node_t child;
75 : int r;
76 :
77 1 : bf_assert(cgen);
78 :
79 1 : _cgen = malloc(sizeof(*_cgen));
80 1 : if (!_cgen)
81 : return -ENOMEM;
82 :
83 1 : _cgen->program = NULL;
84 :
85 1 : r = bf_rpack_kv_enum(node, "front", &_cgen->front);
86 : if (r)
87 0 : return bf_rpack_key_err(r, "bf_cgen.front");
88 :
89 1 : r = bf_rpack_kv_obj(node, "chain", &child);
90 1 : if (r)
91 0 : return bf_rpack_key_err(r, "bf_cgen.chain");
92 1 : r = bf_chain_new_from_pack(&_cgen->chain, child);
93 1 : if (r)
94 0 : return bf_rpack_key_err(r, "bf_cgen.chain");
95 :
96 1 : if (!bf_chain_validate(_cgen->chain))
97 0 : return bf_err_r(-EINVAL, "chain '%s' is invalid", _cgen->chain->name);
98 :
99 1 : r = bf_rpack_kv_node(node, "program", &child);
100 1 : if (r)
101 0 : return bf_rpack_key_err(r, "bf_cgen.program");
102 1 : if (!bf_rpack_is_nil(child)) {
103 0 : _cleanup_close_ int dir_fd = -1;
104 :
105 0 : if ((dir_fd = _bf_cgen_get_chain_pindir_fd(_cgen->chain->name)) < 0) {
106 0 : return bf_err_r(dir_fd,
107 : "failed to open chain pin directory for '%s'",
108 : _cgen->chain->name);
109 : }
110 :
111 0 : r = bf_program_new_from_pack(&_cgen->program, _cgen->chain, dir_fd,
112 : child);
113 0 : if (r)
114 : return r;
115 : }
116 :
117 1 : *cgen = TAKE_PTR(_cgen);
118 :
119 1 : return 0;
120 : }
121 :
122 12 : void bf_cgen_free(struct bf_cgen **cgen)
123 : {
124 3 : _cleanup_close_ int pin_fd = -1;
125 :
126 12 : bf_assert(cgen);
127 :
128 11 : if (!*cgen)
129 : return;
130 :
131 : /* Perform a non-recursive removal of the chain's pin directory: if
132 : * the chain hasn't been pinned (e.g. due to a failure), the pin directory
133 : * will be empty and will be removed. If the chain is valid and pinned, then
134 : * the removal of the pin directory will fail, but that's alright. */
135 8 : if (bf_opts_persist() && (pin_fd = bf_ctx_get_pindir_fd()) >= 0)
136 0 : bf_rmdir_at(pin_fd, (*cgen)->chain->name, false);
137 :
138 8 : bf_program_free(&(*cgen)->program);
139 8 : bf_chain_free(&(*cgen)->chain);
140 :
141 8 : free(*cgen);
142 8 : *cgen = NULL;
143 : }
144 :
145 3 : int bf_cgen_pack(const struct bf_cgen *cgen, bf_wpack_t *pack)
146 : {
147 3 : bf_assert(cgen);
148 2 : bf_assert(pack);
149 :
150 1 : bf_wpack_kv_enum(pack, "front", cgen->front);
151 :
152 1 : bf_wpack_open_object(pack, "chain");
153 1 : bf_chain_pack(cgen->chain, pack);
154 1 : bf_wpack_close_object(pack);
155 :
156 1 : if (cgen->program) {
157 0 : bf_wpack_open_object(pack, "program");
158 0 : bf_program_pack(cgen->program, pack);
159 0 : bf_wpack_close_object(pack);
160 : } else {
161 : bf_wpack_kv_nil(pack, "program");
162 : }
163 :
164 1 : return bf_wpack_is_valid(pack) ? 0 : -EINVAL;
165 : }
166 :
167 0 : void bf_cgen_dump(const struct bf_cgen *cgen, prefix_t *prefix)
168 : {
169 0 : bf_assert(cgen);
170 0 : bf_assert(prefix);
171 :
172 0 : DUMP(prefix, "struct bf_cgen at %p", cgen);
173 :
174 0 : bf_dump_prefix_push(prefix);
175 0 : DUMP(prefix, "front: %s", bf_front_to_str(cgen->front));
176 :
177 : // Chain
178 0 : DUMP(prefix, "chain: struct bf_chain *");
179 0 : bf_dump_prefix_push(prefix);
180 0 : bf_chain_dump(cgen->chain, bf_dump_prefix_last(prefix));
181 0 : bf_dump_prefix_pop(prefix);
182 :
183 : // Programs
184 0 : if (cgen->program) {
185 0 : DUMP(bf_dump_prefix_last(prefix), "program: struct bf_program *");
186 0 : bf_dump_prefix_push(prefix);
187 0 : bf_program_dump(cgen->program, bf_dump_prefix_last(prefix));
188 0 : bf_dump_prefix_pop(prefix);
189 : } else {
190 0 : DUMP(bf_dump_prefix_last(prefix), "program: (struct bf_program *)NULL");
191 : }
192 :
193 0 : bf_dump_prefix_pop(prefix);
194 0 : }
195 :
196 0 : int bf_cgen_get_counter(const struct bf_cgen *cgen,
197 : enum bf_counter_type counter_idx,
198 : struct bf_counter *counter)
199 : {
200 0 : bf_assert(cgen && counter);
201 :
202 : /* There are two more counter than rules. The special counters must
203 : * be accessed via the specific values, to avoid confusion. */
204 0 : enum bf_counter_type rule_count = bf_list_size(&cgen->chain->rules);
205 0 : if (counter_idx == BF_COUNTER_POLICY) {
206 : counter_idx = rule_count;
207 0 : } else if (counter_idx == BF_COUNTER_ERRORS) {
208 0 : counter_idx = rule_count + 1;
209 0 : } else if (counter_idx < 0 || counter_idx >= rule_count) {
210 : return -EINVAL;
211 : }
212 :
213 0 : return bf_program_get_counter(cgen->program, counter_idx, counter);
214 : }
215 :
216 0 : int bf_cgen_set(struct bf_cgen *cgen, const struct bf_ns *ns,
217 : struct bf_hookopts **hookopts)
218 : {
219 0 : _free_bf_program_ struct bf_program *prog = NULL;
220 0 : _cleanup_close_ int pindir_fd = -1;
221 : int r;
222 :
223 0 : bf_assert(cgen);
224 :
225 0 : if (bf_opts_persist()) {
226 0 : pindir_fd = _bf_cgen_get_chain_pindir_fd(cgen->chain->name);
227 0 : if (pindir_fd < 0)
228 : return pindir_fd;
229 : }
230 :
231 0 : r = bf_program_new(&prog, cgen->chain);
232 0 : if (r < 0)
233 : return r;
234 :
235 0 : r = bf_program_generate(prog);
236 0 : if (r < 0)
237 0 : return bf_err_r(r, "failed to generate bf_program");
238 :
239 0 : r = bf_program_load(prog);
240 0 : if (r < 0)
241 0 : return bf_err_r(r, "failed to load the chain");
242 :
243 0 : if (hookopts) {
244 0 : r = bf_ns_set(ns, bf_ctx_get_ns());
245 0 : if (r)
246 0 : return bf_err_r(r, "failed to switch to the client's namespaces");
247 :
248 0 : r = bf_program_attach(prog, hookopts);
249 0 : if (r < 0)
250 0 : return bf_err_r(r, "failed to load and attach the chain");
251 :
252 0 : if (bf_ns_set(bf_ctx_get_ns(), ns))
253 0 : bf_abort("failed to restore previous namespaces, aborting");
254 : }
255 :
256 0 : if (bf_opts_persist()) {
257 0 : r = bf_program_pin(prog, pindir_fd);
258 0 : if (r)
259 : return r;
260 : }
261 :
262 0 : cgen->program = TAKE_PTR(prog);
263 :
264 0 : return r;
265 : }
266 :
267 0 : int bf_cgen_load(struct bf_cgen *cgen)
268 : {
269 0 : _free_bf_program_ struct bf_program *prog = NULL;
270 0 : _cleanup_close_ int pindir_fd = -1;
271 : int r;
272 :
273 0 : bf_assert(cgen);
274 :
275 0 : if (bf_opts_persist()) {
276 0 : pindir_fd = _bf_cgen_get_chain_pindir_fd(cgen->chain->name);
277 0 : if (pindir_fd < 0)
278 : return pindir_fd;
279 : }
280 :
281 0 : r = bf_program_new(&prog, cgen->chain);
282 0 : if (r < 0)
283 : return r;
284 :
285 0 : r = bf_program_generate(prog);
286 0 : if (r < 0)
287 0 : return bf_err_r(r, "failed to generate bf_program");
288 :
289 0 : r = bf_program_load(prog);
290 0 : if (r < 0)
291 0 : return bf_err_r(r, "failed to load the chain");
292 :
293 0 : if (bf_opts_persist()) {
294 0 : r = bf_program_pin(prog, pindir_fd);
295 0 : if (r)
296 : return r;
297 : }
298 :
299 0 : bf_info("load %s", cgen->chain->name);
300 0 : bf_cgen_dump(cgen, EMPTY_PREFIX);
301 :
302 0 : cgen->program = TAKE_PTR(prog);
303 :
304 0 : return r;
305 : }
306 :
307 0 : int bf_cgen_attach(struct bf_cgen *cgen, const struct bf_ns *ns,
308 : struct bf_hookopts **hookopts)
309 : {
310 0 : _cleanup_close_ int pindir_fd = -1;
311 : int r;
312 :
313 0 : bf_assert(cgen && ns && hookopts);
314 :
315 0 : bf_info("attaching %s to %s", cgen->chain->name,
316 : bf_hook_to_str(cgen->chain->hook));
317 0 : bf_hookopts_dump(*hookopts, EMPTY_PREFIX);
318 :
319 0 : if (bf_opts_persist()) {
320 0 : pindir_fd = _bf_cgen_get_chain_pindir_fd(cgen->chain->name);
321 0 : if (pindir_fd < 0)
322 : return pindir_fd;
323 : }
324 :
325 0 : r = bf_ns_set(ns, bf_ctx_get_ns());
326 0 : if (r)
327 0 : return bf_err_r(r, "failed to switch to the client's namespaces");
328 :
329 0 : r = bf_program_attach(cgen->program, hookopts);
330 0 : if (r < 0)
331 0 : return bf_err_r(r, "failed to attach chain '%s'", cgen->chain->name);
332 :
333 0 : if (bf_ns_set(bf_ctx_get_ns(), ns))
334 0 : bf_abort("failed to restore previous namespaces, aborting");
335 :
336 0 : if (bf_opts_persist()) {
337 0 : r = bf_link_pin(cgen->program->link, pindir_fd);
338 0 : if (r) {
339 0 : bf_program_detach(cgen->program);
340 0 : return r;
341 : }
342 : }
343 :
344 : return r;
345 : }
346 :
347 0 : int bf_cgen_update(struct bf_cgen *cgen, struct bf_chain **new_chain)
348 : {
349 0 : _free_bf_program_ struct bf_program *new_prog = NULL;
350 0 : _cleanup_close_ int pindir_fd = -1;
351 : struct bf_program *old_prog;
352 : int r;
353 :
354 0 : bf_assert(cgen && new_chain);
355 :
356 0 : old_prog = cgen->program;
357 :
358 0 : if (bf_opts_persist()) {
359 0 : pindir_fd = _bf_cgen_get_chain_pindir_fd((*new_chain)->name);
360 0 : if (pindir_fd < 0)
361 : return pindir_fd;
362 : }
363 :
364 0 : r = bf_program_new(&new_prog, *new_chain);
365 0 : if (r < 0)
366 0 : return bf_err_r(r, "failed to create a new bf_program");
367 :
368 0 : r = bf_program_generate(new_prog);
369 0 : if (r < 0) {
370 0 : return bf_err_r(r,
371 : "failed to generate the bytecode for a new bf_program");
372 : }
373 :
374 0 : r = bf_program_load(new_prog);
375 0 : if (r)
376 0 : return bf_err_r(r, "failed to load new program");
377 :
378 0 : if (bf_opts_persist())
379 0 : bf_program_unpin(old_prog, pindir_fd);
380 :
381 0 : r = bf_link_update(old_prog->link, cgen->chain->hook,
382 0 : new_prog->runtime.prog_fd);
383 0 : if (r) {
384 0 : bf_err_r(r, "failed to update bf_link object with new program");
385 0 : if (bf_opts_persist() && bf_program_pin(old_prog, pindir_fd) < 0)
386 0 : bf_err("failed to repin old program, ignoring");
387 0 : return r;
388 : }
389 :
390 : // We updated the old link, we need to store it in the new program
391 0 : bf_swap(new_prog->link, old_prog->link);
392 :
393 0 : if (bf_opts_persist()) {
394 0 : r = bf_program_pin(new_prog, pindir_fd);
395 0 : if (r)
396 0 : bf_warn_r(r, "failed to pin new prog, ignoring");
397 : }
398 :
399 0 : bf_swap(cgen->program, new_prog);
400 :
401 0 : bf_chain_free(&cgen->chain);
402 0 : cgen->chain = TAKE_PTR(*new_chain);
403 :
404 0 : return 0;
405 : }
406 :
407 0 : void bf_cgen_detach(struct bf_cgen *cgen)
408 : {
409 0 : bf_assert(cgen);
410 :
411 0 : bf_program_detach(cgen->program);
412 0 : }
413 :
414 0 : void bf_cgen_unload(struct bf_cgen *cgen)
415 : {
416 0 : _cleanup_close_ int chain_fd = -1;
417 :
418 0 : bf_assert(cgen);
419 :
420 0 : chain_fd = _bf_cgen_get_chain_pindir_fd(cgen->chain->name);
421 0 : if (chain_fd < 0) {
422 0 : bf_err_r(chain_fd, "failed to open pin directory for '%s'",
423 : cgen->chain->name);
424 : return;
425 : }
426 :
427 : // The chain's pin directory will be removed in bf_cgen_free()
428 0 : bf_program_unpin(cgen->program, chain_fd);
429 0 : bf_program_unload(cgen->program);
430 : }
431 :
432 0 : int bf_cgen_get_counters(const struct bf_cgen *cgen, bf_list *counters)
433 : {
434 0 : bf_list _counters = bf_list_default_from(*counters);
435 : int r;
436 :
437 0 : bf_assert(cgen && counters);
438 :
439 : /* Iterate over all the rules, then the policy counter (size(rules)) and
440 : * the errors counters (sizeof(rules) + 1)*/
441 0 : for (size_t i = 0; i < bf_list_size(&cgen->chain->rules) + 2; ++i) {
442 0 : _free_bf_counter_ struct bf_counter *counter = NULL;
443 0 : ssize_t idx = (ssize_t)i;
444 :
445 0 : if (i == bf_list_size(&cgen->chain->rules))
446 : idx = BF_COUNTER_POLICY;
447 0 : else if (i == bf_list_size(&cgen->chain->rules) + 1)
448 : idx = BF_COUNTER_ERRORS;
449 :
450 0 : r = bf_counter_new(&counter, 0, 0);
451 0 : if (r)
452 : return r;
453 :
454 0 : r = bf_cgen_get_counter(cgen, idx, counter);
455 0 : if (r)
456 : return r;
457 :
458 0 : r = bf_list_add_tail(&_counters, counter);
459 0 : if (r)
460 : return r;
461 :
462 0 : TAKE_PTR(counter);
463 : }
464 :
465 0 : *counters = bf_list_move(_counters);
466 :
467 0 : return 0;
468 : }
|