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 <linux/in.h>
7 : #include <linux/netfilter.h>
8 : #include <linux/netfilter/x_tables.h>
9 : #include <linux/netfilter_ipv4/ip_tables.h>
10 :
11 : #include <errno.h>
12 : #include <stdbool.h>
13 : #include <stddef.h>
14 : #include <stdint.h>
15 : #include <stdio.h>
16 : #include <stdlib.h>
17 : #include <string.h>
18 :
19 : #include "bpfilter/cgen/cgen.h"
20 : #include "bpfilter/cgen/program.h"
21 : #include "bpfilter/ctx.h"
22 : #include "bpfilter/opts.h"
23 : #include "bpfilter/xlate/front.h"
24 : #include "bpfilter/xlate/ipt/dump.h"
25 : #include "bpfilter/xlate/ipt/helpers.h"
26 : #include "core/chain.h"
27 : #include "core/counter.h"
28 : #include "core/dump.h"
29 : #include "core/front.h"
30 : #include "core/helper.h"
31 : #include "core/hook.h"
32 : #include "core/list.h"
33 : #include "core/logger.h"
34 : #include "core/marsh.h"
35 : #include "core/matcher.h"
36 : #include "core/request.h"
37 : #include "core/response.h"
38 : #include "core/rule.h"
39 : #include "core/verdict.h"
40 :
41 : /**
42 : * @file ipt.c
43 : *
44 : * @c iptables front-end for @c bpfilter .
45 : *
46 : * This front-end provides support for @c iptables command to @c bpfilter .
47 : *
48 : * @c iptables requires the @c INPUT , @c FORWARD , and @c OUTPUT chains to
49 : * be defined with the @c ACCEPT policy by default, which mean they have no
50 : * effect except counting the packets. @c bpfilter doesn't define those chains
51 : * by default, even with this front-end enabled. Instead, it emulates then if
52 : * they are not defined when @c iptables request the ruleset.
53 : * See @ref _bf_ipt_gen_get_ruleset .
54 : *
55 : * Before running the requests command, @c iptables will send two requests to
56 : * @c bpfilter to populate a local cache:
57 : * - @c IPT_SO_GET_INFO : fetch the ruleset size, enabled hooks, number of
58 : * rules, and offset of the rules.
59 : * - @c IPT_SO_GET_ENTRIES : same information as @c IPT_SO_GET_INFO plus the
60 : * ruleset.
61 : * @c iptables always sends the whole ruleset to @c bpfilter , even if only a
62 : * single rule has changed.
63 : *
64 : * @c bpfilter will generate the ruleset in @c iptables format on demand, as
65 : * long as the rules have been defined by @c iptables previously. @c iptables
66 : * ruleset is defined as an @c ipt_replace structure with the following fields:
67 : * - @c name : name of the table, only "filter" is supported.
68 : * - @c valid_hooks : flags of the enabled hooks (hooks with a ruleset defined).
69 : * - @c num_entries : number of @c ipt_entry in the structure (hanging off the
70 : * end in a flexible array member).
71 : * - @c size : total size of the @c ipt_entry structures.
72 : * - @c hook_entry : offset of each chain's first @c ipt_entry starting from
73 : * @c ipt_replace.entries .
74 : * - @c underflow : offset of each chain's policy @c ipt_entry starting from
75 : * @c ipt_replace.entries .
76 : * - @c num_counters : identical to @c ipt_replace.num_entries .
77 : * - @c counters : unused.
78 : * - @c entries : flexible array member of @c ipt_entry for the chains.
79 : *
80 : * The @ref bf_rule of each chain are translated into @c ipt_entry structures.
81 : * This structure is documented in the Linux kernel sources. All the
82 : * @c ipt_entry structures defined for @ref bf_rule will have the same size
83 : * because none of them will contain any matcher ( @c iptables matchers are not
84 : * supported by @c bpfilter ), however after each @c ipt_entry is located an
85 : * @c ipt_entry_target to define the rule's verdict. @c ipt_entry_target have
86 : * different sizes depending on the exact type of target (verdict, jump, ...):
87 : * @c bpfilter only supports verdict ( @c ipt_standard_target ).
88 : *
89 : * Then, a last @c ipt_entry is added for the error target, which is expected
90 : * by @c iptables .
91 : */
92 :
93 : /**
94 : * Get size of an ipt_replace structure.
95 : *
96 : * @param ipt_replace_ptr Pointer to a valid ipt_replace structure.
97 : * @return Size of the structure, including variable length entries field.
98 : */
99 : #define bf_ipt_replace_size(ipt_replace_ptr) \
100 : (sizeof(struct ipt_replace) + (ipt_replace_ptr)->size)
101 :
102 : /**
103 : * Convert an iptables target to a bpfilter verdict.
104 : *
105 : * Only the NF_ACCEPT and NF_DROP standard target are supported, other targets
106 : * and user-defined chains jumps will be rejected.
107 : *
108 : * @param ipt_tgt @c iptables target to convert.
109 : * @param verdict @c bpfilter verdict, corresponding to @p ipt_tgt .
110 : * @return 0 on success, or na egative errno value on error.
111 : */
112 0 : static int _bf_ipt_target_to_verdict(struct ipt_entry_target *ipt_tgt,
113 : enum bf_verdict *verdict)
114 : {
115 0 : bf_assert(ipt_tgt && verdict);
116 :
117 0 : if (bf_streq("", ipt_tgt->u.user.name)) {
118 : struct ipt_standard_target *std_tgt =
119 : (struct xt_standard_target *)ipt_tgt;
120 :
121 0 : if (std_tgt->verdict >= 0) {
122 0 : return bf_err_r(
123 : -ENOTSUP,
124 : "iptables user-defined chains are not supported, rejecting target");
125 : }
126 :
127 0 : switch (-std_tgt->verdict - 1) {
128 0 : case NF_ACCEPT:
129 0 : *verdict = BF_VERDICT_ACCEPT;
130 0 : break;
131 0 : case NF_DROP:
132 0 : *verdict = BF_VERDICT_DROP;
133 0 : break;
134 0 : default:
135 0 : return bf_err_r(-ENOTSUP, "unsupported iptables verdict: %d",
136 : std_tgt->verdict);
137 : }
138 : } else {
139 0 : return bf_err_r(-ENOTSUP, "unsupported iptables target '%s', rejecting",
140 : ipt_tgt->u.user.name);
141 : }
142 :
143 : return 0;
144 : }
145 :
146 0 : static int _bf_verdict_to_ipt_target(enum bf_verdict verdict,
147 : struct ipt_entry_target *ipt_tgt)
148 : {
149 : struct ipt_standard_target *std_tgt = (struct xt_standard_target *)ipt_tgt;
150 :
151 0 : bf_assert(ipt_tgt);
152 :
153 0 : bf_info("target for verdict %d", verdict);
154 0 : switch (verdict) {
155 0 : case BF_VERDICT_ACCEPT:
156 0 : std_tgt->verdict = -2;
157 0 : break;
158 0 : case BF_VERDICT_DROP:
159 0 : std_tgt->verdict = -1;
160 0 : break;
161 0 : default:
162 0 : return bf_err_r(-ENOTSUP, "unsupported verdict %d", verdict);
163 : }
164 :
165 0 : ipt_tgt->u.target_size = sizeof(*std_tgt);
166 :
167 0 : return 0;
168 : }
169 :
170 : /**
171 : * Translate an @c iptables rule into a @c bpfilter rule.
172 : *
173 : * @param entry @c iptables rule. Can't be NULL.
174 : * @param rule @c bpfilter rule. Can't be NULL. On success, points to a
175 : * valid rule.
176 : * @return 0 on success, or a negative errno value on error.
177 : */
178 0 : static int _bf_ipt_entry_to_rule(const struct ipt_entry *entry,
179 : struct bf_rule **rule)
180 : {
181 0 : _cleanup_bf_rule_ struct bf_rule *_rule = NULL;
182 : int r;
183 :
184 0 : bf_assert(entry && rule);
185 :
186 0 : if (sizeof(*entry) < entry->target_offset)
187 0 : return bf_err_r(-ENOTSUP, "iptables modules are not supported");
188 :
189 0 : r = bf_rule_new(&_rule);
190 0 : if (r)
191 : return r;
192 :
193 0 : if (entry->ip.iniface[0] != '\0' || entry->ip.outiface[0] != '\0') {
194 0 : return bf_err_r(
195 : -ENOTSUP,
196 : "filtering on input/output interface with iptables is not supported");
197 : }
198 :
199 : // iptables always has counters enabled
200 0 : _rule->counters = true;
201 :
202 : // Match on source IPv4 address
203 0 : if (entry->ip.src.s_addr || entry->ip.smsk.s_addr) {
204 0 : struct bf_matcher_ip4_addr addr = {
205 : .addr = entry->ip.src.s_addr,
206 0 : .mask = entry->ip.smsk.s_addr,
207 : };
208 :
209 0 : r = bf_rule_add_matcher(
210 : _rule, BF_MATCHER_IP4_SRC_ADDR,
211 0 : entry->ip.invflags & IPT_INV_SRCIP ? BF_MATCHER_NE : BF_MATCHER_EQ,
212 : &addr, sizeof(addr));
213 0 : if (r)
214 0 : return r;
215 : }
216 :
217 : // Match on destination IPv4 address
218 0 : if (entry->ip.dst.s_addr || entry->ip.dmsk.s_addr) {
219 0 : struct bf_matcher_ip4_addr addr = {
220 : .addr = entry->ip.dst.s_addr,
221 0 : .mask = entry->ip.dmsk.s_addr,
222 : };
223 :
224 0 : r = bf_rule_add_matcher(
225 : _rule, BF_MATCHER_IP4_DST_ADDR,
226 0 : entry->ip.invflags & IPT_INV_DSTIP ? BF_MATCHER_NE : BF_MATCHER_EQ,
227 : &addr, sizeof(addr));
228 0 : if (r)
229 0 : return r;
230 : }
231 :
232 : /* Match on the protocol field of the IPv4 packet (and not the L4 protocol,
233 : * as this implies L3 is IPv4). */
234 0 : if (entry->ip.proto) {
235 0 : uint8_t proto = entry->ip.proto;
236 :
237 : // Ensure we didn't cast away data, as we should not
238 0 : if (proto != entry->ip.proto) {
239 0 : return bf_err_r(
240 : -EINVAL,
241 : "protocol '%d' is an invalid protocol for IPv4's protocol field",
242 : entry->ip.proto);
243 : }
244 :
245 0 : r = bf_rule_add_matcher(_rule, BF_MATCHER_IP4_PROTO, BF_MATCHER_EQ,
246 : &proto, sizeof(proto));
247 0 : if (r)
248 : return r;
249 : }
250 :
251 0 : r = _bf_ipt_target_to_verdict(ipt_get_target(entry), &_rule->verdict);
252 0 : if (r)
253 : return r;
254 :
255 0 : *rule = TAKE_PTR(_rule);
256 :
257 0 : return 0;
258 : }
259 :
260 : /**
261 : * Translates a @ref bf_rule object into an @c ipt_entry .
262 : *
263 : * @param rule @ref bf_rule to translate. Can't be NULL.
264 : * @param entry @c ipt_entry created from the @ref bf_rule . Can't be NULL.
265 : * @return 0 on success, or a negative errno value on error.
266 : */
267 0 : static int _bf_rule_to_ipt_entry(const struct bf_rule *rule,
268 : struct ipt_entry *entry)
269 : {
270 : struct bf_matcher_ip4_addr *addr;
271 :
272 0 : bf_assert(entry && rule);
273 :
274 0 : bf_list_foreach (&rule->matchers, matcher_node) {
275 0 : struct bf_matcher *matcher = bf_list_node_get_data(matcher_node);
276 :
277 0 : switch (matcher->type) {
278 0 : case BF_MATCHER_IP4_SRC_ADDR:
279 0 : if (matcher->op == BF_MATCHER_NE)
280 0 : entry->ip.invflags |= IPT_INV_SRCIP;
281 : addr = (void *)&matcher->payload;
282 0 : entry->ip.src.s_addr = addr->addr;
283 0 : entry->ip.smsk.s_addr = addr->mask;
284 0 : break;
285 0 : case BF_MATCHER_IP4_DST_ADDR:
286 0 : if (matcher->op == BF_MATCHER_NE)
287 0 : entry->ip.invflags |= IPT_INV_DSTIP;
288 : addr = (void *)&matcher->payload;
289 0 : entry->ip.dst.s_addr = addr->addr;
290 0 : entry->ip.dmsk.s_addr = addr->mask;
291 0 : break;
292 0 : case BF_MATCHER_IP4_PROTO:
293 0 : entry->ip.proto = *(uint8_t *)&matcher->payload;
294 0 : break;
295 0 : default:
296 0 : return bf_err_r(-ENOTSUP, "unsupported matcher %s for BF_FRONT_IPT",
297 : bf_matcher_type_to_str(matcher->type));
298 : }
299 : }
300 :
301 0 : return _bf_verdict_to_ipt_target(rule->verdict, ipt_get_target(entry));
302 : }
303 :
304 0 : static int _bf_ipt_entries_to_chain(struct bf_chain **chain, int ipt_hook,
305 : struct ipt_entry *first,
306 : struct ipt_entry *last)
307 : {
308 0 : _cleanup_bf_chain_ struct bf_chain *_chain = NULL;
309 : enum bf_verdict policy;
310 : int r;
311 :
312 0 : bf_assert(chain && first && last);
313 :
314 : // The last rule of the chain is the policy.
315 0 : r = _bf_ipt_target_to_verdict(ipt_get_target(last), &policy);
316 0 : if (r)
317 : return r;
318 :
319 0 : r = bf_chain_new(&_chain, bf_nf_hook_to_str(ipt_hook),
320 : bf_hook_from_nf_hook(ipt_hook), policy, NULL, NULL);
321 0 : if (r)
322 : return r;
323 :
324 0 : while (first < last) {
325 0 : _cleanup_bf_rule_ struct bf_rule *rule = NULL;
326 :
327 0 : r = _bf_ipt_entry_to_rule(first, &rule);
328 0 : if (r)
329 0 : return bf_err_r(r, "failed to create rule from ipt_entry");
330 :
331 0 : r = bf_chain_add_rule(_chain, rule);
332 0 : if (r)
333 : return r;
334 :
335 0 : TAKE_PTR(rule);
336 0 : first = ipt_get_next_rule(first);
337 : }
338 :
339 0 : *chain = TAKE_PTR(_chain);
340 :
341 0 : return 0;
342 : }
343 :
344 : struct bf_ipt_gen_ruleset_entry
345 : {
346 : struct bf_cgen *cgen;
347 : struct bf_chain *chain;
348 : };
349 :
350 : /**
351 : * Get the list of chains and codegens for @c BF_FRONT_IPT .
352 : *
353 : * @param ruleset Array of size @c NF_INET_NUMHOOKS to be filled with the
354 : * codegen and chain for every hook (if defined). Mandatory chains will
355 : * be allocated and their pointer added to this array if they are
356 : * not yet defined. Can't be NULL.
357 : * @param nrules On success, contain the total number of rules associated with
358 : * the @c BF_FRONT_IPT front-end. This is the number of rules from
359 : * iptables' perspective: each chain has an extra rule for the policy.
360 : * Can't be NULL.
361 : * @param dummy_chains On success, this list will contain pointers to the
362 : * mandatory chains created to comply with iptables' behaviour. The
363 : * caller will own this list and the pointers contained in it. Can't
364 : * be NULL.
365 : * @return 0 on success, or a negative errno value on failure.
366 : */
367 0 : static int _bf_ipt_gen_get_ruleset(struct bf_ipt_gen_ruleset_entry *ruleset,
368 : size_t *nrules, bf_list *dummy_chains)
369 : {
370 0 : _clean_bf_list_ bf_list cgens;
371 : size_t _nrules = 0;
372 : int r;
373 :
374 0 : bf_assert(ruleset);
375 :
376 0 : r = bf_ctx_get_cgens_for_front(&cgens, BF_FRONT_IPT);
377 0 : if (r)
378 0 : return bf_err_r(r, "failed to collect codegens for BF_FRONT_IPT");
379 :
380 0 : bf_list_foreach (&cgens, cgen_node) {
381 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
382 :
383 0 : ruleset[bf_hook_to_nf_hook(cgen->chain->hook)].cgen = cgen;
384 0 : ruleset[bf_hook_to_nf_hook(cgen->chain->hook)].chain = cgen->chain;
385 :
386 : /* Add the number of rules of the chain to the total number of rules,
387 : * don't forget about the chain's policy, which is a rule from
388 : * iptables' point of view. */
389 0 : _nrules += bf_list_size(&cgen->chain->rules) + 1;
390 : }
391 :
392 : /* iptables requires at least the INPUT, FORWARD, and OUTPUT chains. If
393 : * those chains are not defined, we created dummy ones just to fill the
394 : * ipt_replace structure. */
395 0 : for (enum nf_inet_hooks hook = NF_INET_LOCAL_IN; hook <= NF_INET_LOCAL_OUT;
396 0 : ++hook) {
397 0 : _cleanup_bf_chain_ struct bf_chain *chain = NULL;
398 :
399 0 : if (ruleset[hook].cgen)
400 : continue;
401 :
402 0 : r = bf_chain_new(&chain, bf_nf_hook_to_str(hook),
403 : bf_hook_from_nf_hook(hook), BF_VERDICT_ACCEPT, NULL,
404 : NULL);
405 0 : if (r)
406 0 : return bf_err_r(r,
407 : "failed to create a dummy chain for BF_FRONT_IPT");
408 :
409 0 : r = bf_list_add_tail(dummy_chains, chain);
410 0 : if (r)
411 0 : return bf_err_r(r,
412 : "failed to add BF_FRONT_IPT dummy chain to list");
413 :
414 0 : ruleset[hook].chain = TAKE_PTR(chain);
415 :
416 : // The dummy chains only contain the chain policy
417 0 : ++_nrules;
418 : }
419 :
420 0 : *nrules = _nrules;
421 :
422 0 : return 0;
423 : }
424 :
425 : /**
426 : * Generate the @c ipt_replace structure for the current ruleset.
427 : *
428 : * @param replace @c ipt_replace structure to allocate and fill. Can't be NULL.
429 : * @param with_counters If true, the rule counters in @p replace will be filled
430 : * with the correct values. Otherwise, the counters will default to 0.
431 : * @return 0 on success, or a negative errno value on failure.
432 : */
433 0 : static int _bf_ipt_gen_ipt_replace(struct ipt_replace **replace,
434 : bool with_counters)
435 : {
436 : _cleanup_free_ struct ipt_replace *_replace = NULL;
437 0 : _clean_bf_list_ bf_list dummy_chains = bf_list_default(bf_chain_free, NULL);
438 0 : struct bf_ipt_gen_ruleset_entry ruleset[NF_INET_NUMHOOKS] = {};
439 : struct ipt_entry *entry;
440 : size_t next_chain_off = 0;
441 : size_t nrules;
442 : size_t rule_size =
443 : sizeof(struct ipt_entry) + sizeof(struct xt_standard_target);
444 : size_t err_size = sizeof(struct ipt_entry) + sizeof(struct xt_error_target);
445 : struct xt_error_target *err_tgt;
446 : int r;
447 :
448 0 : bf_assert(replace);
449 :
450 0 : r = _bf_ipt_gen_get_ruleset(ruleset, &nrules, &dummy_chains);
451 0 : if (r)
452 0 : return bf_err_r(r, "failed to collect the BF_FRONT_IPT ruleset");
453 :
454 0 : _replace = calloc(1, sizeof(*_replace) + (nrules * rule_size) + err_size);
455 0 : if (!_replace)
456 : return -ENOMEM;
457 :
458 : // Total number of rules, chain policies, and error entry
459 0 : _replace->num_entries = nrules + 1;
460 0 : _replace->num_counters = nrules + 1;
461 0 : _replace->size = nrules * rule_size + err_size;
462 :
463 0 : entry = (struct ipt_entry *)(_replace + 1);
464 0 : strncpy(_replace->name, "filter", XT_TABLE_MAXNAMELEN);
465 :
466 0 : for (int hook = 0; hook < NF_INET_NUMHOOKS; ++hook) {
467 0 : struct bf_chain *chain = ruleset[hook].chain;
468 0 : struct bf_cgen *cgen = ruleset[hook].cgen;
469 :
470 0 : if (!chain)
471 0 : continue;
472 :
473 : /* Rules (struct ipt_entry) always have the same size:
474 : * sizeof(ipt_entry) + sizeof(ipt_standard_target)
475 : * Matchers and user-defined chains are not supported. */
476 :
477 0 : _replace->valid_hooks |= BF_FLAG(hook);
478 0 : _replace->hook_entry[hook] = next_chain_off;
479 0 : _replace->underflow[hook] =
480 0 : next_chain_off + bf_list_size(&chain->rules) * rule_size;
481 :
482 0 : bf_list_foreach (&chain->rules, rule_node) {
483 0 : struct bf_rule *rule = bf_list_node_get_data(rule_node);
484 :
485 0 : entry->target_offset = sizeof(struct ipt_entry);
486 0 : entry->next_offset = rule_size;
487 :
488 0 : r = _bf_rule_to_ipt_entry(rule, entry);
489 0 : if (r) {
490 0 : return bf_err_r(r,
491 : "failed to translate bf_rule into ipt_entry");
492 : }
493 :
494 0 : if (with_counters && cgen) {
495 : struct bf_counter counters;
496 :
497 0 : r = bf_cgen_get_counter(cgen, rule->index, &counters);
498 0 : if (r) {
499 0 : return bf_err_r(r,
500 : "failed to get counters for iptables rule");
501 : }
502 :
503 0 : entry->counters.bcnt = counters.bytes;
504 0 : entry->counters.pcnt = counters.packets;
505 : }
506 :
507 0 : entry = (void *)entry + rule_size;
508 : }
509 :
510 : // Fill the ipt_entry for the chain policy
511 0 : if (with_counters && cgen) {
512 : struct bf_counter counters;
513 :
514 0 : r = bf_cgen_get_counter(cgen, BF_COUNTER_POLICY, &counters);
515 0 : if (r) {
516 0 : return bf_err_r(
517 : r, "failed to get policy counters for iptables chain");
518 : }
519 :
520 0 : entry->counters.bcnt = counters.bytes;
521 0 : entry->counters.pcnt = counters.packets;
522 : }
523 :
524 0 : entry->target_offset = sizeof(struct ipt_entry);
525 0 : entry->next_offset = rule_size;
526 :
527 0 : r = _bf_verdict_to_ipt_target(chain->policy, ipt_get_target(entry));
528 0 : if (r) {
529 0 : return bf_err_r(
530 : r, "failed to convert chain policy to iptables verdict");
531 : }
532 :
533 0 : entry = (void *)entry + rule_size;
534 0 : next_chain_off += (bf_list_size(&chain->rules) + 1) * rule_size;
535 : }
536 :
537 : // There is one last entry after the chains for the error target.
538 0 : entry->target_offset = sizeof(struct ipt_entry);
539 0 : entry->next_offset = err_size;
540 :
541 : err_tgt = (struct xt_error_target *)(entry + 1);
542 0 : strcpy(err_tgt->errorname, "ERROR");
543 0 : err_tgt->target.u.target_size = sizeof(struct xt_error_target);
544 : err_tgt->target.u.user.target_size = sizeof(struct xt_error_target);
545 0 : strcpy(err_tgt->target.u.user.name, "ERROR");
546 :
547 0 : *replace = TAKE_PTR(_replace);
548 :
549 0 : bf_ipt_dump_replace(*replace, EMPTY_PREFIX);
550 :
551 0 : return 0;
552 : }
553 :
554 : /**
555 : * Translate iptables rules into bpfilter format.
556 : *
557 : * @param ipt iptables rules.
558 : * @param chains Array of chains. The array is big enough to fit one chain per
559 : * hook. Can't be NULL.
560 : * @return 0 on success, negative error code on failure.
561 : */
562 : static int
563 0 : _bf_ipt_xlate_ruleset_set(struct ipt_replace *ipt,
564 : struct bf_chain *(*chains)[NF_INET_NUMHOOKS])
565 : {
566 : int r;
567 :
568 0 : bf_assert(ipt && chains);
569 :
570 0 : for (int i = 0; i < NF_INET_NUMHOOKS; ++i) {
571 0 : _cleanup_bf_chain_ struct bf_chain *chain = NULL;
572 :
573 0 : if (!ipt_is_hook_enabled(ipt, i)) {
574 0 : bf_dbg("iptables hook %d is not enabled, skipping", i);
575 : continue;
576 : }
577 :
578 0 : r = _bf_ipt_entries_to_chain(&chain, i, ipt_get_first_rule(ipt, i),
579 0 : ipt_get_last_rule(ipt, i));
580 0 : if (r) {
581 0 : return bf_err_r(r, "failed to create chain for iptables hook %d",
582 : i);
583 : }
584 :
585 0 : (*chains)[i] = TAKE_PTR(chain);
586 : }
587 :
588 : return 0;
589 : }
590 :
591 : /**
592 : * Modify existing iptables rules.
593 : *
594 : * @todo If processing for any codegen fails, all codegens should be unloaded
595 : * and/or discarded.
596 : *
597 : * @param req The request sent to bpfilter. Can't be NULL.
598 : * @return 0 on success, negative error code on failure.
599 : */
600 0 : static int _bf_ipt_ruleset_set(const struct bf_request *req)
601 : {
602 : struct ipt_replace *replace;
603 0 : struct bf_chain *chains[NF_INET_NUMHOOKS] = {};
604 0 : bf_list _cur_cgens = bf_list_default(NULL, NULL);
605 0 : struct bf_cgen *cur_cgens[NF_INET_NUMHOOKS] = {};
606 : int r;
607 :
608 0 : bf_assert(req);
609 :
610 0 : replace = (struct ipt_replace *)req->data;
611 0 : if (bf_ipt_replace_size(replace) != req->data_len)
612 : return -EINVAL;
613 :
614 0 : bf_ipt_dump_replace(replace, EMPTY_PREFIX);
615 :
616 0 : r = _bf_ipt_xlate_ruleset_set(replace, &chains);
617 0 : if (r)
618 0 : return bf_err_r(r, "failed to translate iptables ruleset");
619 :
620 0 : r = bf_ctx_get_cgens_for_front(&_cur_cgens, BF_FRONT_IPT);
621 0 : if (r)
622 0 : return bf_err_r(r, "failed to get existing bf_cgen for BF_FRONT_IPT");
623 :
624 0 : bf_list_foreach (&_cur_cgens, cgen_node) {
625 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
626 0 : enum nf_inet_hooks hook = bf_hook_to_nf_hook(cgen->chain->hook);
627 :
628 0 : if (cur_cgens[hook])
629 0 : return bf_err_r(-EEXIST,
630 : "found 2 bf_cgen for the same BF_FRONT_IPT hook!");
631 0 : cur_cgens[hook] = cgen;
632 : }
633 :
634 0 : for (int i = 0; i < NF_INET_NUMHOOKS; i++) {
635 0 : _cleanup_bf_cgen_ struct bf_cgen *cgen = cur_cgens[i];
636 0 : _cleanup_bf_chain_ struct bf_chain *chain = TAKE_PTR(chains[i]);
637 :
638 0 : _free_bf_hookopts_ struct bf_hookopts *hookopts = NULL;
639 :
640 0 : if (!chain)
641 0 : continue;
642 :
643 0 : if (!cgen) {
644 0 : r = bf_cgen_new(&cgen, BF_FRONT_IPT, &chain);
645 0 : if (r)
646 : return r;
647 :
648 0 : r = bf_cgen_load(cgen);
649 0 : if (r) {
650 0 : bf_err(
651 : "failed to generate a program for iptables hook %d, skipping",
652 : i);
653 0 : continue;
654 : }
655 :
656 0 : r = bf_hookopts_new(&hookopts);
657 0 : if (r)
658 : return r;
659 :
660 0 : r = bf_cgen_attach(cgen, req->ns, &hookopts);
661 0 : if (r) {
662 0 : bf_err(
663 : "failed to load a program for iptables hook %d, skipping",
664 : i);
665 0 : continue;
666 : }
667 :
668 0 : r = bf_ctx_set_cgen(cgen);
669 0 : if (r) {
670 0 : bf_err_r(
671 : r, "failed to store codegen for iptables hook %d, skipping",
672 : i);
673 0 : continue;
674 : }
675 :
676 0 : TAKE_PTR(cgen);
677 : } else {
678 0 : r = bf_cgen_update(cgen, &chain);
679 0 : if (r) {
680 0 : TAKE_PTR(cgen);
681 0 : bf_err_r(
682 : r,
683 : "failed to update codegen for iptables hook %d, skipping",
684 : i);
685 0 : continue;
686 : }
687 0 : TAKE_PTR(cgen);
688 : }
689 : }
690 :
691 : return r;
692 : }
693 :
694 : /**
695 : * Set counters for a rule.
696 : *
697 : * @todo Actually update the counters.
698 : *
699 : * @param counters iptables structure containing the counters and their value.
700 : * @param len Length of the counters structure.
701 : * @return 0 on success, negative error code on failure.
702 : */
703 : static int _bf_ipt_set_counters_handler(struct xt_counters_info *counters,
704 : size_t len)
705 : {
706 0 : bf_assert(counters);
707 :
708 : UNUSED(len);
709 :
710 : return 0;
711 : }
712 :
713 0 : int _bf_ipt_get_info_handler(struct bf_request *request,
714 : struct bf_response **response)
715 : {
716 0 : _cleanup_free_ struct ipt_replace *replace = NULL;
717 0 : struct ipt_getinfo *info = (struct ipt_getinfo *)request->data;
718 : int r;
719 :
720 0 : bf_assert(request);
721 0 : bf_assert(sizeof(*info) == request->data_len);
722 :
723 0 : if (!bf_streq(info->name, "filter")) {
724 0 : return bf_err_r(-EINVAL, "can't process IPT_SO_GET_INFO for table %s",
725 : info->name);
726 : }
727 :
728 0 : r = _bf_ipt_gen_ipt_replace(&replace, false);
729 0 : if (r)
730 : return r;
731 :
732 0 : info->valid_hooks = replace->valid_hooks;
733 0 : memcpy(info->hook_entry, replace->hook_entry, sizeof(replace->hook_entry));
734 0 : memcpy(info->underflow, replace->underflow, sizeof(replace->underflow));
735 0 : info->num_entries = replace->num_entries;
736 0 : info->size = replace->size;
737 :
738 0 : return bf_response_new_success(response, (const char *)info,
739 : sizeof(struct ipt_getinfo));
740 : }
741 :
742 : /**
743 : * Get the entries of a table, including counters.
744 : *
745 : * @param request
746 : * @param response
747 : * @return 0 on success, negative errno value on failure.
748 : */
749 0 : int _bf_ipt_get_entries_handler(struct bf_request *request,
750 : struct bf_response **response)
751 : {
752 0 : _cleanup_free_ struct ipt_replace *replace = NULL;
753 : struct ipt_get_entries *entries;
754 : int r;
755 :
756 0 : bf_assert(request);
757 0 : bf_assert(response);
758 :
759 0 : entries = (struct ipt_get_entries *)request->data;
760 :
761 0 : if (!bf_streq(entries->name, "filter")) {
762 0 : return bf_err_r(-EINVAL, "can't process IPT_SO_GET_INFO for table %s",
763 : entries->name);
764 : }
765 :
766 0 : r = _bf_ipt_gen_ipt_replace(&replace, true);
767 0 : if (r)
768 : return r;
769 :
770 0 : if (entries->size != replace->size) {
771 0 : return bf_err_r(
772 : -EINVAL,
773 : "not enough space to store entries: %u available, %u required",
774 : entries->size, replace->size);
775 : }
776 :
777 0 : memcpy(entries->entrytable, replace->entries, replace->size);
778 :
779 0 : return bf_response_new_success(response, (const char *)entries,
780 0 : sizeof(*entries) + entries->size);
781 : }
782 :
783 0 : static int _bf_ipt_setup(void)
784 : {
785 0 : return 0;
786 : }
787 :
788 0 : static int _bf_ipt_teardown(void)
789 : {
790 0 : return 0;
791 : }
792 :
793 : /**
794 : * @todo Wouldn't it be better to have a separate handler for each request type?
795 : * In which case struct bf_front_ops would contain a handler for each request
796 : * type, and the front would handle custom (BF_REQ_CUSTOM) requests itself.
797 : * @todo Document that request and responses are not const: they will be free
798 : * by the daemon once the front is done with them. Hence, the front is free
799 : * to modify the requests content.
800 : * @todo Check bf_assertions: a malformed request could cause the daemon to
801 : * crash.
802 : *
803 : * @param request
804 : * @param response
805 : * @return
806 : */
807 0 : static int _bf_ipt_request_handler(struct bf_request *request,
808 : struct bf_response **response)
809 : {
810 : int r;
811 :
812 0 : switch (request->cmd) {
813 0 : case BF_REQ_RULESET_SET:
814 0 : r = _bf_ipt_ruleset_set(request);
815 0 : if (r < 0)
816 : return r;
817 :
818 0 : return bf_response_new_success(response, request->data,
819 : request->data_len);
820 0 : case BF_REQ_COUNTERS_SET:
821 : r = _bf_ipt_set_counters_handler(
822 0 : (struct xt_counters_info *)request->data, request->data_len);
823 : if (r < 0)
824 : return r;
825 :
826 0 : return bf_response_new_success(response, request->data,
827 : request->data_len);
828 0 : case BF_REQ_CUSTOM:
829 0 : switch (request->ipt_cmd) {
830 0 : case IPT_SO_GET_INFO:
831 0 : return _bf_ipt_get_info_handler(request, response);
832 0 : case IPT_SO_GET_ENTRIES:
833 0 : return _bf_ipt_get_entries_handler(request, response);
834 0 : default:
835 0 : return bf_warn_r(-ENOTSUP,
836 : "unsupported custom ipt request type: %d",
837 : request->ipt_cmd);
838 : };
839 0 : default:
840 0 : return bf_warn_r(-ENOTSUP, "unsupported ipt request type: %d",
841 : request->cmd);
842 : };
843 :
844 : return 0;
845 : }
846 :
847 0 : static int _bf_ipt_marsh(struct bf_marsh **marsh)
848 : {
849 : UNUSED(marsh);
850 :
851 0 : return 0;
852 : }
853 :
854 0 : static int _bf_ipt_unmarsh(struct bf_marsh *marsh)
855 : {
856 : UNUSED(marsh);
857 :
858 0 : return 0;
859 : }
860 :
861 : const struct bf_front_ops ipt_front = {
862 : .setup = _bf_ipt_setup,
863 : .teardown = _bf_ipt_teardown,
864 : .request_handler = _bf_ipt_request_handler,
865 : .marsh = _bf_ipt_marsh,
866 : .unmarsh = _bf_ipt_unmarsh,
867 : };
|