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/xlate/front.h"
23 : #include "bpfilter/xlate/ipt/dump.h"
24 : #include "bpfilter/xlate/ipt/helpers.h"
25 : #include "core/chain.h"
26 : #include "core/counter.h"
27 : #include "core/dump.h"
28 : #include "core/front.h"
29 : #include "core/helper.h"
30 : #include "core/hook.h"
31 : #include "core/list.h"
32 : #include "core/logger.h"
33 : #include "core/marsh.h"
34 : #include "core/matcher.h"
35 : #include "core/opts.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_hook(ipt_hook), policy, NULL, NULL);
320 0 : if (r)
321 : return r;
322 :
323 0 : _chain->hook_opts.used_opts = 1 << BF_HOOK_OPT_ATTACH;
324 0 : _chain->hook_opts.attach = true;
325 :
326 0 : while (first < last) {
327 0 : _cleanup_bf_rule_ struct bf_rule *rule = NULL;
328 :
329 0 : r = _bf_ipt_entry_to_rule(first, &rule);
330 0 : if (r)
331 0 : return bf_err_r(r, "failed to create rule from ipt_entry");
332 :
333 0 : r = bf_chain_add_rule(_chain, rule);
334 0 : if (r)
335 : return r;
336 :
337 0 : TAKE_PTR(rule);
338 0 : first = ipt_get_next_rule(first);
339 : }
340 :
341 0 : *chain = TAKE_PTR(_chain);
342 :
343 0 : return 0;
344 : }
345 :
346 : struct bf_ipt_gen_ruleset_entry
347 : {
348 : struct bf_cgen *cgen;
349 : struct bf_chain *chain;
350 : };
351 :
352 : /**
353 : * Get the list of chains and codegens for @c BF_FRONT_IPT .
354 : *
355 : * @param ruleset Array of size @c NF_INET_NUMHOOKS to be filled with the
356 : * codegen and chain for every hook (if defined). Mandatory chains will
357 : * be allocated and their pointer added to this array if they are
358 : * not yet defined. Can't be NULL.
359 : * @param nrules On success, contain the total number of rules associated with
360 : * the @c BF_FRONT_IPT front-end. This is the number of rules from
361 : * iptables' perspective: each chain has an extra rule for the policy.
362 : * Can't be NULL.
363 : * @param dummy_chains On success, this list will contain pointers to the
364 : * mandatory chains created to comply with iptables' behaviour. The
365 : * caller will own this list and the pointers contained in it. Can't
366 : * be NULL.
367 : * @return 0 on success, or a negative errno value on failure.
368 : */
369 0 : static int _bf_ipt_gen_get_ruleset(struct bf_ipt_gen_ruleset_entry *ruleset,
370 : size_t *nrules, bf_list *dummy_chains)
371 : {
372 0 : _clean_bf_list_ bf_list cgens;
373 : size_t _nrules = 0;
374 : int r;
375 :
376 0 : bf_assert(ruleset);
377 :
378 0 : r = bf_ctx_get_cgens_for_front(&cgens, BF_FRONT_IPT);
379 0 : if (r)
380 0 : return bf_err_r(r, "failed to collect codegens for BF_FRONT_IPT");
381 :
382 0 : bf_list_foreach (&cgens, cgen_node) {
383 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
384 :
385 0 : ruleset[bf_hook_to_nf_hook(cgen->chain->hook)].cgen = cgen;
386 0 : ruleset[bf_hook_to_nf_hook(cgen->chain->hook)].chain = cgen->chain;
387 :
388 : /* Add the number of rules of the chain to the total number of rules,
389 : * don't forget about the chain's policy, which is a rule from
390 : * iptables' point of view. */
391 0 : _nrules += bf_list_size(&cgen->chain->rules) + 1;
392 : }
393 :
394 : /* iptables requires at least the INPUT, FORWARD, and OUTPUT chains. If
395 : * those chains are not defined, we created dummy ones just to fill the
396 : * ipt_replace structure. */
397 0 : for (enum nf_inet_hooks hook = NF_INET_LOCAL_IN; hook <= NF_INET_LOCAL_OUT;
398 0 : ++hook) {
399 0 : _cleanup_bf_chain_ struct bf_chain *chain = NULL;
400 :
401 0 : if (ruleset[hook].cgen)
402 : continue;
403 :
404 0 : r = bf_chain_new(&chain, bf_nf_hook_to_hook(hook), BF_VERDICT_ACCEPT,
405 : NULL, NULL);
406 0 : if (r)
407 0 : return bf_err_r(r,
408 : "failed to create a dummy chain for BF_FRONT_IPT");
409 :
410 0 : r = bf_list_add_tail(dummy_chains, chain);
411 0 : if (r)
412 0 : return bf_err_r(r,
413 : "failed to add BF_FRONT_IPT dummy chain to list");
414 :
415 0 : ruleset[hook].chain = TAKE_PTR(chain);
416 :
417 : // The dummy chains only contain the chain policy
418 0 : ++_nrules;
419 : }
420 :
421 0 : *nrules = _nrules;
422 :
423 0 : return 0;
424 : }
425 :
426 : /**
427 : * Generate the @c ipt_replace structure for the current ruleset.
428 : *
429 : * @param replace @c ipt_replace structure to allocate and fill. Can't be NULL.
430 : * @param with_counters If true, the rule counters in @p replace will be filled
431 : * with the correct values. Otherwise, the counters will default to 0.
432 : * @return 0 on success, or a negative errno value on failure.
433 : */
434 0 : static int _bf_ipt_gen_ipt_replace(struct ipt_replace **replace,
435 : bool with_counters)
436 : {
437 : _cleanup_free_ struct ipt_replace *_replace = NULL;
438 0 : _clean_bf_list_ bf_list dummy_chains = bf_list_default(bf_chain_free, NULL);
439 0 : struct bf_ipt_gen_ruleset_entry ruleset[NF_INET_NUMHOOKS] = {};
440 : struct ipt_entry *entry;
441 : size_t next_chain_off = 0;
442 : size_t nrules;
443 : size_t rule_size =
444 : sizeof(struct ipt_entry) + sizeof(struct xt_standard_target);
445 : size_t err_size = sizeof(struct ipt_entry) + sizeof(struct xt_error_target);
446 : struct xt_error_target *err_tgt;
447 : int r;
448 :
449 0 : bf_assert(replace);
450 :
451 0 : r = _bf_ipt_gen_get_ruleset(ruleset, &nrules, &dummy_chains);
452 0 : if (r)
453 0 : return bf_err_r(r, "failed to collect the BF_FRONT_IPT ruleset");
454 :
455 0 : _replace = calloc(1, sizeof(*_replace) + (nrules * rule_size) + err_size);
456 0 : if (!_replace)
457 : return -ENOMEM;
458 :
459 : // Total number of rules, chain policies, and error entry
460 0 : _replace->num_entries = nrules + 1;
461 0 : _replace->num_counters = nrules + 1;
462 0 : _replace->size = nrules * rule_size + err_size;
463 :
464 0 : entry = (struct ipt_entry *)(_replace + 1);
465 0 : strncpy(_replace->name, "filter", XT_TABLE_MAXNAMELEN);
466 :
467 0 : for (int hook = 0; hook < NF_INET_NUMHOOKS; ++hook) {
468 0 : struct bf_chain *chain = ruleset[hook].chain;
469 0 : struct bf_cgen *cgen = ruleset[hook].cgen;
470 :
471 0 : if (!chain)
472 0 : continue;
473 :
474 : /* Rules (struct ipt_entry) always have the same size:
475 : * sizeof(ipt_entry) + sizeof(ipt_standard_target)
476 : * Matchers and user-defined chains are not supported. */
477 :
478 0 : _replace->valid_hooks |= 1 << hook;
479 0 : _replace->hook_entry[hook] = next_chain_off;
480 0 : _replace->underflow[hook] =
481 0 : next_chain_off + bf_list_size(&chain->rules) * rule_size;
482 :
483 0 : bf_list_foreach (&chain->rules, rule_node) {
484 0 : struct bf_rule *rule = bf_list_node_get_data(rule_node);
485 :
486 0 : entry->target_offset = sizeof(struct ipt_entry);
487 0 : entry->next_offset = rule_size;
488 :
489 0 : r = _bf_rule_to_ipt_entry(rule, entry);
490 0 : if (r) {
491 0 : return bf_err_r(r,
492 : "failed to translate bf_rule into ipt_entry");
493 : }
494 :
495 0 : if (with_counters && cgen) {
496 : struct bf_counter counters;
497 :
498 0 : r = bf_cgen_get_counter(cgen, rule->index, &counters);
499 0 : if (r) {
500 0 : return bf_err_r(r,
501 : "failed to get counters for iptables rule");
502 : }
503 :
504 0 : entry->counters.bcnt = counters.bytes;
505 0 : entry->counters.pcnt = counters.packets;
506 : }
507 :
508 0 : entry = (void *)entry + rule_size;
509 : }
510 :
511 : // Fill the ipt_entry for the chain policy
512 0 : if (with_counters && cgen) {
513 : struct bf_counter counters;
514 :
515 0 : r = bf_cgen_get_counter(cgen, BF_COUNTER_POLICY, &counters);
516 0 : if (r) {
517 0 : return bf_err_r(
518 : r, "failed to get policy counters for iptables chain");
519 : }
520 :
521 0 : entry->counters.bcnt = counters.bytes;
522 0 : entry->counters.pcnt = counters.packets;
523 : }
524 :
525 0 : entry->target_offset = sizeof(struct ipt_entry);
526 0 : entry->next_offset = rule_size;
527 :
528 0 : r = _bf_verdict_to_ipt_target(chain->policy, ipt_get_target(entry));
529 0 : if (r) {
530 0 : return bf_err_r(
531 : r, "failed to convert chain policy to iptables verdict");
532 : }
533 :
534 0 : entry = (void *)entry + rule_size;
535 0 : next_chain_off += (bf_list_size(&chain->rules) + 1) * rule_size;
536 : }
537 :
538 : // There is one last entry after the chains for the error target.
539 0 : entry->target_offset = sizeof(struct ipt_entry);
540 0 : entry->next_offset = err_size;
541 :
542 : err_tgt = (struct xt_error_target *)(entry + 1);
543 0 : strcpy(err_tgt->errorname, "ERROR");
544 0 : err_tgt->target.u.target_size = sizeof(struct xt_error_target);
545 : err_tgt->target.u.user.target_size = sizeof(struct xt_error_target);
546 0 : strcpy(err_tgt->target.u.user.name, "ERROR");
547 :
548 0 : *replace = TAKE_PTR(_replace);
549 :
550 0 : bf_ipt_dump_replace(*replace, EMPTY_PREFIX);
551 :
552 0 : return 0;
553 : }
554 :
555 : /**
556 : * Translate iptables rules into bpfilter format.
557 : *
558 : * @param ipt iptables rules.
559 : * @param chains Array of chains. The array is big enough to fit one chain per
560 : * hook. Can't be NULL.
561 : * @return 0 on success, negative error code on failure.
562 : */
563 : static int
564 0 : _bf_ipt_xlate_ruleset_set(struct ipt_replace *ipt,
565 : struct bf_chain *(*chains)[NF_INET_NUMHOOKS])
566 : {
567 : int r;
568 :
569 0 : bf_assert(ipt && chains);
570 :
571 0 : for (int i = 0; i < NF_INET_NUMHOOKS; ++i) {
572 0 : _cleanup_bf_chain_ struct bf_chain *chain = NULL;
573 :
574 0 : if (!ipt_is_hook_enabled(ipt, i)) {
575 0 : bf_dbg("iptables hook %d is not enabled, skipping", i);
576 : continue;
577 : }
578 :
579 0 : r = _bf_ipt_entries_to_chain(&chain, i, ipt_get_first_rule(ipt, i),
580 0 : ipt_get_last_rule(ipt, i));
581 0 : if (r) {
582 0 : return bf_err_r(r, "failed to create chain for iptables hook %d",
583 : i);
584 : }
585 :
586 0 : (*chains)[i] = TAKE_PTR(chain);
587 : }
588 :
589 : return 0;
590 : }
591 :
592 : /**
593 : * Modify existing iptables rules.
594 : *
595 : * @todo If processing for any codegen fails, all codegens should be unloaded
596 : * and/or discarded.
597 : *
598 : * @param req The request sent to bpfilter. Can't be NULL.
599 : * @return 0 on success, negative error code on failure.
600 : */
601 0 : static int _bf_ipt_ruleset_set(const struct bf_request *req)
602 : {
603 : _cleanup_free_ struct ipt_entry *entries = NULL;
604 : struct ipt_replace *replace;
605 0 : struct bf_chain *chains[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 : if (bf_opts_is_verbose(BF_VERBOSE_DEBUG))
615 0 : bf_ipt_dump_replace(replace, EMPTY_PREFIX);
616 :
617 0 : r = _bf_ipt_xlate_ruleset_set(replace, &chains);
618 0 : if (r)
619 0 : return bf_err_r(r, "failed to translate iptables ruleset");
620 :
621 : /* Copy entries now, so we don't have to unload the programs if the copy
622 : * fails later. */
623 0 : entries = bf_memdup(replace->entries, replace->size);
624 0 : if (!entries)
625 0 : return bf_err_r(-ENOMEM, "failed to duplicate iptables ruleset");
626 :
627 0 : for (int i = 0; i < NF_INET_NUMHOOKS; i++) {
628 0 : _cleanup_bf_cgen_ struct bf_cgen *cgen = NULL;
629 0 : _cleanup_bf_chain_ struct bf_chain *chain = TAKE_PTR(chains[i]);
630 :
631 0 : if (!chain)
632 0 : continue;
633 :
634 0 : cgen = bf_ctx_get_cgen(chain->hook, &chain->hook_opts);
635 0 : if (!cgen) {
636 0 : r = bf_cgen_new(&cgen, BF_FRONT_IPT, &chain);
637 0 : if (r)
638 : return r;
639 :
640 0 : r = bf_cgen_up(cgen, req->ns);
641 0 : if (r) {
642 0 : bf_err(
643 : "failed to generate and load program for iptables hook %d, skipping",
644 : i);
645 0 : continue;
646 : }
647 :
648 0 : r = bf_ctx_set_cgen(cgen);
649 0 : if (r) {
650 0 : bf_err_r(
651 : r, "failed to store codegen for iptables hook %d, skipping",
652 : i);
653 0 : continue;
654 : }
655 :
656 0 : TAKE_PTR(cgen);
657 : } else {
658 0 : r = bf_cgen_update(cgen, &chain, req->ns);
659 0 : if (r) {
660 0 : TAKE_PTR(cgen);
661 0 : bf_err_r(
662 : r,
663 : "failed to update codegen for iptables hook %d, skipping",
664 : i);
665 0 : continue;
666 : }
667 0 : TAKE_PTR(cgen);
668 : }
669 : }
670 :
671 : return r;
672 : }
673 :
674 : /**
675 : * Set counters for a rule.
676 : *
677 : * @todo Actually update the counters.
678 : *
679 : * @param counters iptables structure containing the counters and their value.
680 : * @param len Length of the counters structure.
681 : * @return 0 on success, negative error code on failure.
682 : */
683 : static int _bf_ipt_set_counters_handler(struct xt_counters_info *counters,
684 : size_t len)
685 : {
686 0 : bf_assert(counters);
687 :
688 : UNUSED(len);
689 :
690 : return 0;
691 : }
692 :
693 0 : int _bf_ipt_get_info_handler(struct bf_request *request,
694 : struct bf_response **response)
695 : {
696 0 : _cleanup_free_ struct ipt_replace *replace = NULL;
697 0 : struct ipt_getinfo *info = (struct ipt_getinfo *)request->data;
698 : int r;
699 :
700 0 : bf_assert(request);
701 0 : bf_assert(sizeof(*info) == request->data_len);
702 :
703 0 : if (!bf_streq(info->name, "filter")) {
704 0 : return bf_err_r(-EINVAL, "can't process IPT_SO_GET_INFO for table %s",
705 : info->name);
706 : }
707 :
708 0 : r = _bf_ipt_gen_ipt_replace(&replace, false);
709 0 : if (r)
710 : return r;
711 :
712 0 : info->valid_hooks = replace->valid_hooks;
713 0 : memcpy(info->hook_entry, replace->hook_entry, sizeof(replace->hook_entry));
714 0 : memcpy(info->underflow, replace->underflow, sizeof(replace->underflow));
715 0 : info->num_entries = replace->num_entries;
716 0 : info->size = replace->size;
717 :
718 0 : return bf_response_new_success(response, (const char *)info,
719 : sizeof(struct ipt_getinfo));
720 : }
721 :
722 : /**
723 : * Get the entries of a table, including counters.
724 : *
725 : * @param request
726 : * @param response
727 : * @return 0 on success, negative errno value on failure.
728 : */
729 0 : int _bf_ipt_get_entries_handler(struct bf_request *request,
730 : struct bf_response **response)
731 : {
732 0 : _cleanup_free_ struct ipt_replace *replace = NULL;
733 : struct ipt_get_entries *entries;
734 : int r;
735 :
736 0 : bf_assert(request);
737 0 : bf_assert(response);
738 :
739 0 : entries = (struct ipt_get_entries *)request->data;
740 :
741 0 : if (!bf_streq(entries->name, "filter")) {
742 0 : return bf_err_r(-EINVAL, "can't process IPT_SO_GET_INFO for table %s",
743 : entries->name);
744 : }
745 :
746 0 : r = _bf_ipt_gen_ipt_replace(&replace, true);
747 0 : if (r)
748 : return r;
749 :
750 0 : if (entries->size != replace->size) {
751 0 : return bf_err_r(
752 : -EINVAL,
753 : "not enough space to store entries: %u available, %u required",
754 : entries->size, replace->size);
755 : }
756 :
757 0 : memcpy(entries->entrytable, replace->entries, replace->size);
758 :
759 0 : return bf_response_new_success(response, (const char *)entries,
760 0 : sizeof(*entries) + entries->size);
761 : }
762 :
763 0 : static int _bf_ipt_setup(void)
764 : {
765 0 : return 0;
766 : }
767 :
768 0 : static int _bf_ipt_teardown(void)
769 : {
770 0 : return 0;
771 : }
772 :
773 : /**
774 : * @todo Wouldn't it be better to have a separate handler for each request type?
775 : * In which case struct bf_front_ops would contain a handler for each request
776 : * type, and the front would handle custom (BF_REQ_CUSTOM) requests itself.
777 : * @todo Document that request and responses are not const: they will be free
778 : * by the daemon once the front is done with them. Hence, the front is free
779 : * to modify the requests content.
780 : * @todo Check bf_assertions: a malformed request could cause the daemon to
781 : * crash.
782 : *
783 : * @param request
784 : * @param response
785 : * @return
786 : */
787 0 : static int _bf_ipt_request_handler(struct bf_request *request,
788 : struct bf_response **response)
789 : {
790 : int r;
791 :
792 0 : switch (request->cmd) {
793 0 : case BF_REQ_RULES_SET:
794 0 : r = _bf_ipt_ruleset_set(request);
795 0 : if (r < 0)
796 : return r;
797 :
798 0 : return bf_response_new_success(response, request->data,
799 : request->data_len);
800 0 : case BF_REQ_COUNTERS_SET:
801 : r = _bf_ipt_set_counters_handler(
802 0 : (struct xt_counters_info *)request->data, request->data_len);
803 : if (r < 0)
804 : return r;
805 :
806 0 : return bf_response_new_success(response, request->data,
807 : request->data_len);
808 0 : case BF_REQ_CUSTOM:
809 0 : switch (request->ipt_cmd) {
810 0 : case IPT_SO_GET_INFO:
811 0 : return _bf_ipt_get_info_handler(request, response);
812 0 : case IPT_SO_GET_ENTRIES:
813 0 : return _bf_ipt_get_entries_handler(request, response);
814 0 : default:
815 0 : return bf_warn_r(-ENOTSUP,
816 : "unsupported custom ipt request type: %d",
817 : request->ipt_cmd);
818 : };
819 0 : default:
820 0 : return bf_warn_r(-ENOTSUP, "unsupported ipt request type: %d",
821 : request->cmd);
822 : };
823 :
824 : return 0;
825 : }
826 :
827 0 : static int _bf_ipt_marsh(struct bf_marsh **marsh)
828 : {
829 : UNUSED(marsh);
830 :
831 0 : return 0;
832 : }
833 :
834 0 : static int _bf_ipt_unmarsh(struct bf_marsh *marsh)
835 : {
836 : UNUSED(marsh);
837 :
838 0 : return 0;
839 : }
840 :
841 : const struct bf_front_ops ipt_front = {
842 : .setup = _bf_ipt_setup,
843 : .teardown = _bf_ipt_teardown,
844 : .request_handler = _bf_ipt_request_handler,
845 : .marsh = _bf_ipt_marsh,
846 : .unmarsh = _bf_ipt_unmarsh,
847 : };
|