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/chain.h>
20 : #include <bpfilter/counter.h>
21 : #include <bpfilter/dump.h>
22 : #include <bpfilter/front.h>
23 : #include <bpfilter/helper.h>
24 : #include <bpfilter/hook.h>
25 : #include <bpfilter/list.h>
26 : #include <bpfilter/logger.h>
27 : #include <bpfilter/matcher.h>
28 : #include <bpfilter/request.h>
29 : #include <bpfilter/response.h>
30 : #include <bpfilter/rule.h>
31 : #include <bpfilter/verdict.h>
32 :
33 : #include "cgen/cgen.h"
34 : #include "cgen/program.h"
35 : #include "ctx.h"
36 : #include "opts.h"
37 : #include "xlate/front.h"
38 : #include "xlate/ipt/dump.h"
39 : #include "xlate/ipt/helpers.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 : #define BF_IPT_PRIO_0 1000
94 : #define BF_IPT_PRIO_1 1001
95 :
96 : /**
97 : * Get size of an ipt_replace structure.
98 : *
99 : * @param ipt_replace_ptr Pointer to a valid ipt_replace structure.
100 : * @return Size of the structure, including variable length entries field.
101 : */
102 : #define bf_ipt_replace_size(ipt_replace_ptr) \
103 : (sizeof(struct ipt_replace) + (ipt_replace_ptr)->size)
104 :
105 : /**
106 : * Convert an iptables target to a bpfilter verdict.
107 : *
108 : * Only the NF_ACCEPT and NF_DROP standard target are supported, other targets
109 : * and user-defined chains jumps will be rejected.
110 : *
111 : * @param ipt_tgt @c iptables target to convert.
112 : * @param verdict @c bpfilter verdict, corresponding to @p ipt_tgt .
113 : * @return 0 on success, or na egative errno value on error.
114 : */
115 0 : static int _bf_ipt_target_to_verdict(struct ipt_entry_target *ipt_tgt,
116 : enum bf_verdict *verdict)
117 : {
118 0 : bf_assert(ipt_tgt && verdict);
119 :
120 0 : if (bf_streq("", ipt_tgt->u.user.name)) {
121 : struct ipt_standard_target *std_tgt =
122 : (struct xt_standard_target *)ipt_tgt;
123 :
124 0 : if (std_tgt->verdict >= 0) {
125 0 : return bf_err_r(
126 : -ENOTSUP,
127 : "iptables user-defined chains are not supported, rejecting target");
128 : }
129 :
130 0 : switch (-std_tgt->verdict - 1) {
131 0 : case NF_ACCEPT:
132 0 : *verdict = BF_VERDICT_ACCEPT;
133 0 : break;
134 0 : case NF_DROP:
135 0 : *verdict = BF_VERDICT_DROP;
136 0 : break;
137 0 : default:
138 0 : return bf_err_r(-ENOTSUP, "unsupported iptables verdict: %d",
139 : std_tgt->verdict);
140 : }
141 : } else {
142 0 : return bf_err_r(-ENOTSUP, "unsupported iptables target '%s', rejecting",
143 : ipt_tgt->u.user.name);
144 : }
145 :
146 : return 0;
147 : }
148 :
149 0 : static int _bf_verdict_to_ipt_target(enum bf_verdict verdict,
150 : struct ipt_entry_target *ipt_tgt)
151 : {
152 : struct ipt_standard_target *std_tgt = (struct xt_standard_target *)ipt_tgt;
153 :
154 0 : bf_assert(ipt_tgt);
155 :
156 0 : switch (verdict) {
157 0 : case BF_VERDICT_ACCEPT:
158 0 : std_tgt->verdict = -2;
159 0 : break;
160 0 : case BF_VERDICT_DROP:
161 0 : std_tgt->verdict = -1;
162 0 : break;
163 0 : default:
164 0 : return bf_err_r(-ENOTSUP, "unsupported verdict %d", verdict);
165 : }
166 :
167 0 : ipt_tgt->u.target_size = sizeof(*std_tgt);
168 :
169 0 : return 0;
170 : }
171 :
172 : /**
173 : * Translate an @c iptables rule into a @c bpfilter rule.
174 : *
175 : * @param entry @c iptables rule. Can't be NULL.
176 : * @param rule @c bpfilter rule. Can't be NULL. On success, points to a
177 : * valid rule.
178 : * @return 0 on success, or a negative errno value on error.
179 : */
180 0 : static int _bf_ipt_entry_to_rule(const struct ipt_entry *entry,
181 : struct bf_rule **rule)
182 : {
183 0 : _free_bf_rule_ struct bf_rule *_rule = NULL;
184 : int r;
185 :
186 0 : bf_assert(entry && rule);
187 :
188 0 : if (sizeof(*entry) < entry->target_offset)
189 0 : return bf_err_r(-ENOTSUP, "iptables modules are not supported");
190 :
191 0 : r = bf_rule_new(&_rule);
192 0 : if (r)
193 : return r;
194 :
195 0 : if (entry->ip.iniface[0] != '\0' || entry->ip.outiface[0] != '\0') {
196 0 : return bf_err_r(
197 : -ENOTSUP,
198 : "filtering on input/output interface with iptables is not supported");
199 : }
200 :
201 : // iptables always has counters enabled
202 0 : _rule->counters = true;
203 :
204 : // Match on source IPv4 address
205 0 : if (entry->ip.src.s_addr || entry->ip.smsk.s_addr) {
206 0 : struct bf_matcher_ip4_addr addr = {
207 : .addr = entry->ip.src.s_addr,
208 0 : .mask = entry->ip.smsk.s_addr,
209 : };
210 :
211 0 : r = bf_rule_add_matcher(
212 : _rule, BF_MATCHER_IP4_SADDR,
213 0 : entry->ip.invflags & IPT_INV_SRCIP ? BF_MATCHER_NE : BF_MATCHER_EQ,
214 : &addr, sizeof(addr));
215 0 : if (r)
216 0 : return r;
217 : }
218 :
219 : // Match on destination IPv4 address
220 0 : if (entry->ip.dst.s_addr || entry->ip.dmsk.s_addr) {
221 0 : struct bf_matcher_ip4_addr addr = {
222 : .addr = entry->ip.dst.s_addr,
223 0 : .mask = entry->ip.dmsk.s_addr,
224 : };
225 :
226 0 : r = bf_rule_add_matcher(
227 : _rule, BF_MATCHER_IP4_DADDR,
228 0 : entry->ip.invflags & IPT_INV_DSTIP ? BF_MATCHER_NE : BF_MATCHER_EQ,
229 : &addr, sizeof(addr));
230 0 : if (r)
231 0 : return r;
232 : }
233 :
234 : /* Match on the protocol field of the IPv4 packet (and not the L4 protocol,
235 : * as this implies L3 is IPv4). */
236 0 : if (entry->ip.proto) {
237 0 : uint8_t proto = entry->ip.proto;
238 :
239 : // Ensure we didn't cast away data, as we should not
240 0 : if (proto != entry->ip.proto) {
241 0 : return bf_err_r(
242 : -EINVAL,
243 : "protocol '%d' is an invalid protocol for IPv4's protocol field",
244 : entry->ip.proto);
245 : }
246 :
247 0 : r = bf_rule_add_matcher(_rule, BF_MATCHER_IP4_PROTO, BF_MATCHER_EQ,
248 : &proto, sizeof(proto));
249 0 : if (r)
250 : return r;
251 : }
252 :
253 0 : r = _bf_ipt_target_to_verdict(ipt_get_target(entry), &_rule->verdict);
254 0 : if (r)
255 : return r;
256 :
257 0 : *rule = TAKE_PTR(_rule);
258 :
259 0 : return 0;
260 : }
261 :
262 : /**
263 : * Translates a @ref bf_rule object into an @c ipt_entry .
264 : *
265 : * @param rule @ref bf_rule to translate. Can't be NULL.
266 : * @param entry @c ipt_entry created from the @ref bf_rule . Can't be NULL.
267 : * @return 0 on success, or a negative errno value on error.
268 : */
269 0 : static int _bf_rule_to_ipt_entry(const struct bf_rule *rule,
270 : struct ipt_entry *entry)
271 : {
272 : const struct bf_matcher_ip4_addr *addr;
273 :
274 0 : bf_assert(entry && rule);
275 :
276 0 : bf_list_foreach (&rule->matchers, matcher_node) {
277 0 : struct bf_matcher *matcher = bf_list_node_get_data(matcher_node);
278 :
279 0 : switch (bf_matcher_get_type(matcher)) {
280 0 : case BF_MATCHER_IP4_SADDR:
281 0 : if (bf_matcher_get_op(matcher) == BF_MATCHER_NE)
282 0 : entry->ip.invflags |= IPT_INV_SRCIP;
283 0 : addr = bf_matcher_payload(matcher);
284 0 : entry->ip.src.s_addr = addr->addr;
285 0 : entry->ip.smsk.s_addr = addr->mask;
286 0 : break;
287 0 : case BF_MATCHER_IP4_DADDR:
288 0 : if (bf_matcher_get_op(matcher) == BF_MATCHER_NE)
289 0 : entry->ip.invflags |= IPT_INV_DSTIP;
290 0 : addr = bf_matcher_payload(matcher);
291 0 : entry->ip.dst.s_addr = addr->addr;
292 0 : entry->ip.dmsk.s_addr = addr->mask;
293 0 : break;
294 0 : case BF_MATCHER_IP4_PROTO:
295 0 : entry->ip.proto = *(uint8_t *)bf_matcher_payload(matcher);
296 0 : break;
297 0 : default:
298 0 : return bf_err_r(
299 : -ENOTSUP, "unsupported matcher %s for BF_FRONT_IPT",
300 : bf_matcher_type_to_str(bf_matcher_get_type(matcher)));
301 : }
302 : }
303 :
304 0 : return _bf_verdict_to_ipt_target(rule->verdict, ipt_get_target(entry));
305 : }
306 :
307 0 : static int _bf_ipt_entries_to_chain(struct bf_chain **chain, int ipt_hook,
308 : struct ipt_entry *first,
309 : struct ipt_entry *last)
310 : {
311 0 : _free_bf_chain_ struct bf_chain *_chain = NULL;
312 : enum bf_verdict policy;
313 : int r;
314 :
315 0 : bf_assert(chain && first && last);
316 :
317 : // The last rule of the chain is the policy.
318 0 : r = _bf_ipt_target_to_verdict(ipt_get_target(last), &policy);
319 0 : if (r)
320 : return r;
321 :
322 0 : r = bf_chain_new(&_chain, bf_nf_hook_to_str(ipt_hook),
323 : bf_hook_from_nf_hook(ipt_hook), policy, NULL, NULL);
324 0 : if (r)
325 : return r;
326 :
327 0 : while (first < last) {
328 0 : _free_bf_rule_ struct bf_rule *rule = NULL;
329 :
330 0 : r = _bf_ipt_entry_to_rule(first, &rule);
331 0 : if (r)
332 0 : return bf_err_r(r, "failed to create rule from ipt_entry");
333 :
334 0 : r = bf_chain_add_rule(_chain, rule);
335 0 : if (r)
336 : return r;
337 :
338 0 : TAKE_PTR(rule);
339 0 : first = ipt_get_next_rule(first);
340 : }
341 :
342 0 : *chain = TAKE_PTR(_chain);
343 :
344 0 : return 0;
345 : }
346 :
347 : struct bf_ipt_gen_ruleset_entry
348 : {
349 : struct bf_cgen *cgen;
350 : struct bf_chain *chain;
351 : };
352 :
353 : /**
354 : * Get the list of chains and codegens for @c BF_FRONT_IPT .
355 : *
356 : * @param ruleset Array of size @c NF_INET_NUMHOOKS to be filled with the
357 : * codegen and chain for every hook (if defined). Mandatory chains will
358 : * be allocated and their pointer added to this array if they are
359 : * not yet defined. Can't be NULL.
360 : * @param nrules On success, contain the total number of rules associated with
361 : * the @c BF_FRONT_IPT front-end. This is the number of rules from
362 : * iptables' perspective: each chain has an extra rule for the policy.
363 : * Can't be NULL.
364 : * @param dummy_chains On success, this list will contain pointers to the
365 : * mandatory chains created to comply with iptables' behaviour. The
366 : * caller will own this list and the pointers contained in it. Can't
367 : * be NULL.
368 : * @return 0 on success, or a negative errno value on failure.
369 : */
370 0 : static int _bf_ipt_gen_get_ruleset(struct bf_ipt_gen_ruleset_entry *ruleset,
371 : size_t *nrules, bf_list *dummy_chains)
372 : {
373 0 : _clean_bf_list_ bf_list cgens = bf_list_default(NULL, NULL);
374 : size_t _nrules = 0;
375 : int r;
376 :
377 0 : bf_assert(ruleset);
378 :
379 0 : r = bf_ctx_get_cgens_for_front(&cgens, BF_FRONT_IPT);
380 0 : if (r)
381 0 : return bf_err_r(r, "failed to collect codegens for BF_FRONT_IPT");
382 :
383 0 : bf_list_foreach (&cgens, cgen_node) {
384 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
385 :
386 0 : ruleset[bf_hook_to_nf_hook(cgen->chain->hook)].cgen = cgen;
387 0 : ruleset[bf_hook_to_nf_hook(cgen->chain->hook)].chain = cgen->chain;
388 :
389 : /* Add the number of rules of the chain to the total number of rules,
390 : * don't forget about the chain's policy, which is a rule from
391 : * iptables' point of view. */
392 0 : _nrules += bf_list_size(&cgen->chain->rules) + 1;
393 : }
394 :
395 : /* iptables requires at least the INPUT, FORWARD, and OUTPUT chains. If
396 : * those chains are not defined, we created dummy ones just to fill the
397 : * ipt_replace structure. */
398 : for (enum bf_nf_inet_hooks hook = BF_NF_INET_LOCAL_IN;
399 0 : hook <= BF_NF_INET_LOCAL_OUT; ++hook) {
400 0 : _free_bf_chain_ struct bf_chain *chain = NULL;
401 :
402 0 : if (ruleset[hook].cgen)
403 : continue;
404 :
405 0 : r = bf_chain_new(&chain, bf_nf_hook_to_str(hook),
406 : bf_hook_from_nf_hook(hook), BF_VERDICT_ACCEPT, NULL,
407 : NULL);
408 0 : if (r)
409 0 : return bf_err_r(r,
410 : "failed to create a dummy chain for BF_FRONT_IPT");
411 :
412 0 : r = bf_list_add_tail(dummy_chains, chain);
413 0 : if (r)
414 0 : return bf_err_r(r,
415 : "failed to add BF_FRONT_IPT dummy chain to list");
416 :
417 0 : ruleset[hook].chain = TAKE_PTR(chain);
418 :
419 : // The dummy chains only contain the chain policy
420 0 : ++_nrules;
421 : }
422 :
423 0 : *nrules = _nrules;
424 :
425 0 : return 0;
426 : }
427 :
428 : /**
429 : * Generate the @c ipt_replace structure for the current ruleset.
430 : *
431 : * @param replace @c ipt_replace structure to allocate and fill. Can't be NULL.
432 : * @param with_counters If true, the rule counters in @p replace will be filled
433 : * with the correct values. Otherwise, the counters will default to 0.
434 : * @return 0 on success, or a negative errno value on failure.
435 : */
436 0 : static int _bf_ipt_gen_ipt_replace(struct ipt_replace **replace,
437 : bool with_counters)
438 : {
439 : _cleanup_free_ struct ipt_replace *_replace = NULL;
440 0 : _clean_bf_list_ bf_list dummy_chains = bf_list_default(bf_chain_free, NULL);
441 0 : struct bf_ipt_gen_ruleset_entry ruleset[NF_INET_NUMHOOKS] = {};
442 : struct ipt_entry *entry;
443 : size_t next_chain_off = 0;
444 : size_t nrules;
445 : size_t rule_size =
446 : sizeof(struct ipt_entry) + sizeof(struct xt_standard_target);
447 : size_t err_size = sizeof(struct ipt_entry) + sizeof(struct xt_error_target);
448 : struct xt_error_target *err_tgt;
449 : int r;
450 :
451 0 : bf_assert(replace);
452 :
453 0 : r = _bf_ipt_gen_get_ruleset(ruleset, &nrules, &dummy_chains);
454 0 : if (r)
455 0 : return bf_err_r(r, "failed to collect the BF_FRONT_IPT ruleset");
456 :
457 0 : _replace = calloc(1, sizeof(*_replace) + (nrules * rule_size) + err_size);
458 0 : if (!_replace)
459 : return -ENOMEM;
460 :
461 : // Total number of rules, chain policies, and error entry
462 0 : _replace->num_entries = nrules + 1;
463 0 : _replace->num_counters = nrules + 1;
464 0 : _replace->size = nrules * rule_size + err_size;
465 :
466 0 : entry = (struct ipt_entry *)(_replace + 1);
467 0 : strncpy(_replace->name, "filter", XT_TABLE_MAXNAMELEN);
468 :
469 0 : for (int hook = 0; hook < NF_INET_NUMHOOKS; ++hook) {
470 0 : struct bf_chain *chain = ruleset[hook].chain;
471 0 : struct bf_cgen *cgen = ruleset[hook].cgen;
472 :
473 0 : if (!chain)
474 0 : continue;
475 :
476 : /* Rules (struct ipt_entry) always have the same size:
477 : * sizeof(ipt_entry) + sizeof(ipt_standard_target)
478 : * Matchers and user-defined chains are not supported. */
479 :
480 0 : _replace->valid_hooks |= BF_FLAG(hook);
481 0 : _replace->hook_entry[hook] = next_chain_off;
482 0 : _replace->underflow[hook] =
483 0 : next_chain_off + bf_list_size(&chain->rules) * rule_size;
484 :
485 0 : bf_list_foreach (&chain->rules, rule_node) {
486 0 : struct bf_rule *rule = bf_list_node_get_data(rule_node);
487 :
488 0 : entry->target_offset = sizeof(struct ipt_entry);
489 0 : entry->next_offset = rule_size;
490 :
491 0 : r = _bf_rule_to_ipt_entry(rule, entry);
492 0 : if (r) {
493 0 : return bf_err_r(r,
494 : "failed to translate bf_rule into ipt_entry");
495 : }
496 :
497 0 : if (with_counters && cgen) {
498 : struct bf_counter counters;
499 :
500 0 : r = bf_cgen_get_counter(cgen, rule->index, &counters);
501 0 : if (r) {
502 0 : return bf_err_r(r,
503 : "failed to get counters for iptables rule");
504 : }
505 :
506 0 : entry->counters.bcnt = counters.bytes;
507 0 : entry->counters.pcnt = counters.packets;
508 : }
509 :
510 0 : entry = (void *)entry + rule_size;
511 : }
512 :
513 : // Fill the ipt_entry for the chain policy
514 0 : if (with_counters && cgen) {
515 : struct bf_counter counters;
516 :
517 0 : r = bf_cgen_get_counter(cgen, BF_COUNTER_POLICY, &counters);
518 0 : if (r) {
519 0 : return bf_err_r(
520 : r, "failed to get policy counters for iptables chain");
521 : }
522 :
523 0 : entry->counters.bcnt = counters.bytes;
524 0 : entry->counters.pcnt = counters.packets;
525 : }
526 :
527 0 : entry->target_offset = sizeof(struct ipt_entry);
528 0 : entry->next_offset = rule_size;
529 :
530 0 : r = _bf_verdict_to_ipt_target(chain->policy, ipt_get_target(entry));
531 0 : if (r) {
532 0 : return bf_err_r(
533 : r, "failed to convert chain policy to iptables verdict");
534 : }
535 :
536 0 : entry = (void *)entry + rule_size;
537 0 : next_chain_off += (bf_list_size(&chain->rules) + 1) * rule_size;
538 : }
539 :
540 : // There is one last entry after the chains for the error target.
541 0 : entry->target_offset = sizeof(struct ipt_entry);
542 0 : entry->next_offset = err_size;
543 :
544 : err_tgt = (struct xt_error_target *)(entry + 1);
545 0 : strcpy(err_tgt->errorname, "ERROR");
546 0 : err_tgt->target.u.target_size = sizeof(struct xt_error_target);
547 : err_tgt->target.u.user.target_size = sizeof(struct xt_error_target);
548 0 : strcpy(err_tgt->target.u.user.name, "ERROR");
549 :
550 0 : *replace = TAKE_PTR(_replace);
551 :
552 0 : bf_ipt_dump_replace(*replace, EMPTY_PREFIX);
553 :
554 0 : return 0;
555 : }
556 :
557 : /**
558 : * Translate iptables rules into bpfilter format.
559 : *
560 : * @param ipt iptables rules.
561 : * @param chains Array of chains. The array is big enough to fit one chain per
562 : * hook. Can't be NULL.
563 : * @return 0 on success, negative error code on failure.
564 : */
565 : static int
566 0 : _bf_ipt_xlate_ruleset_set(const struct ipt_replace *ipt,
567 : struct bf_chain *(*chains)[NF_INET_NUMHOOKS])
568 : {
569 : int r;
570 :
571 0 : bf_assert(ipt && chains);
572 :
573 0 : for (int i = 0; i < NF_INET_NUMHOOKS; ++i) {
574 0 : _free_bf_chain_ struct bf_chain *chain = NULL;
575 :
576 0 : if (!ipt_is_hook_enabled(ipt, i)) {
577 0 : bf_dbg("iptables hook %d is not enabled, skipping", i);
578 : continue;
579 : }
580 :
581 0 : r = _bf_ipt_entries_to_chain(&chain, i, ipt_get_first_rule(ipt, i),
582 0 : ipt_get_last_rule(ipt, i));
583 0 : if (r) {
584 0 : return bf_err_r(r, "failed to create chain for iptables hook %d",
585 : i);
586 : }
587 :
588 0 : (*chains)[i] = TAKE_PTR(chain);
589 : }
590 :
591 : return 0;
592 : }
593 :
594 : /**
595 : * Modify existing iptables rules.
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 : const struct ipt_replace *replace;
603 0 : struct bf_chain *chains[NF_INET_NUMHOOKS] = {};
604 0 : _clean_bf_list_ 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 = bf_request_data(req);
611 0 : if (bf_ipt_replace_size(replace) != bf_request_data_len(req))
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 bf_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 : _free_bf_cgen_ struct bf_cgen *cgen = cur_cgens[i];
636 0 : _free_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 : hookopts->family = PF_INET;
661 0 : hookopts->priorities[0] = BF_IPT_PRIO_0;
662 0 : hookopts->priorities[1] = BF_IPT_PRIO_1;
663 0 : hookopts->used_opts |=
664 : BF_FLAGS(BF_HOOKOPTS_FAMILY, BF_HOOKOPTS_PRIORITIES);
665 :
666 0 : r = bf_cgen_attach(cgen, bf_request_ns(req), &hookopts);
667 0 : if (r) {
668 0 : bf_err(
669 : "failed to load a program for iptables hook %d, skipping",
670 : i);
671 0 : continue;
672 : }
673 :
674 0 : r = bf_ctx_set_cgen(cgen);
675 0 : if (r) {
676 0 : bf_err_r(
677 : r, "failed to store codegen for iptables hook %d, skipping",
678 : i);
679 0 : continue;
680 : }
681 :
682 0 : TAKE_PTR(cgen);
683 : } else {
684 0 : r = bf_cgen_update(cgen, &chain);
685 0 : if (r) {
686 0 : TAKE_PTR(cgen);
687 0 : bf_err_r(
688 : r,
689 : "failed to update codegen for iptables hook %d, skipping",
690 : i);
691 0 : continue;
692 : }
693 0 : TAKE_PTR(cgen);
694 : }
695 : }
696 :
697 : return r;
698 : }
699 :
700 : /**
701 : * Set counters for a rule.
702 : *
703 : * @todo Actually update the counters.
704 : *
705 : * @param counters iptables structure containing the counters and their value.
706 : * @param len Length of the counters structure.
707 : * @return 0 on success, negative error code on failure.
708 : */
709 0 : static int _bf_ipt_set_counters_handler(const struct xt_counters_info *counters,
710 : size_t len)
711 : {
712 0 : bf_assert(counters);
713 :
714 : UNUSED(len);
715 :
716 0 : return 0;
717 : }
718 :
719 0 : int _bf_ipt_get_info_handler(const struct bf_request *request,
720 : struct bf_response **response)
721 : {
722 0 : _cleanup_free_ struct ipt_replace *replace = NULL;
723 : _cleanup_free_ struct ipt_getinfo *info = NULL;
724 : int r;
725 :
726 0 : bf_assert(request);
727 0 : bf_assert(sizeof(*info) == bf_request_data_len(request));
728 :
729 0 : info = bf_memdup(bf_request_data(request), bf_request_data_len(request));
730 0 : if (!info) {
731 : return -ENOMEM;
732 : }
733 :
734 0 : if (!bf_streq(info->name, "filter")) {
735 0 : return bf_err_r(-EINVAL, "can't process IPT_SO_GET_INFO for table %s",
736 : info->name);
737 : }
738 :
739 0 : r = _bf_ipt_gen_ipt_replace(&replace, false);
740 0 : if (r)
741 : return r;
742 :
743 0 : info->valid_hooks = replace->valid_hooks;
744 0 : memcpy(info->hook_entry, replace->hook_entry, sizeof(replace->hook_entry));
745 0 : memcpy(info->underflow, replace->underflow, sizeof(replace->underflow));
746 0 : info->num_entries = replace->num_entries;
747 0 : info->size = replace->size;
748 :
749 0 : return bf_response_new_success(response, (const char *)info,
750 : sizeof(struct ipt_getinfo));
751 : }
752 :
753 : /**
754 : * Get the entries of a table, including counters.
755 : *
756 : * @param request
757 : * @param response
758 : * @return 0 on success, negative errno value on failure.
759 : */
760 0 : int _bf_ipt_get_entries_handler(const struct bf_request *request,
761 : struct bf_response **response)
762 : {
763 0 : _cleanup_free_ struct ipt_replace *replace = NULL;
764 : _cleanup_free_ struct ipt_get_entries *entries = NULL;
765 : int r;
766 :
767 0 : bf_assert(request);
768 0 : bf_assert(response);
769 :
770 0 : entries = bf_memdup(bf_request_data(request), bf_request_data_len(request));
771 0 : if (!entries)
772 : return -ENOMEM;
773 :
774 0 : if (!bf_streq(entries->name, "filter")) {
775 0 : return bf_err_r(-EINVAL, "can't process IPT_SO_GET_INFO for table %s",
776 : entries->name);
777 : }
778 :
779 0 : r = _bf_ipt_gen_ipt_replace(&replace, true);
780 0 : if (r)
781 : return r;
782 :
783 0 : if (entries->size != replace->size) {
784 0 : return bf_err_r(
785 : -EINVAL,
786 : "not enough space to store entries: %u available, %u required",
787 : entries->size, replace->size);
788 : }
789 :
790 0 : memcpy(entries->entrytable, replace->entries, replace->size);
791 :
792 0 : return bf_response_new_success(response, (const char *)entries,
793 0 : sizeof(*entries) + entries->size);
794 : }
795 :
796 0 : static int _bf_ipt_setup(void)
797 : {
798 0 : return 0;
799 : }
800 :
801 0 : static int _bf_ipt_teardown(void)
802 : {
803 0 : return 0;
804 : }
805 :
806 0 : static int _bf_ipt_request_handler(const struct bf_request *request,
807 : struct bf_response **response)
808 : {
809 : int r;
810 :
811 0 : switch (bf_request_cmd(request)) {
812 0 : case BF_REQ_RULESET_SET:
813 0 : r = _bf_ipt_ruleset_set(request);
814 0 : if (r < 0)
815 : return r;
816 :
817 0 : return bf_response_new_success(response, bf_request_data(request),
818 : bf_request_data_len(request));
819 0 : case BF_REQ_COUNTERS_SET:
820 0 : r = _bf_ipt_set_counters_handler(bf_request_data(request),
821 : bf_request_data_len(request));
822 : if (r < 0)
823 : return r;
824 :
825 0 : return bf_response_new_success(response, bf_request_data(request),
826 : bf_request_data_len(request));
827 0 : case BF_REQ_CUSTOM:
828 0 : switch (bf_request_ipt_cmd(request)) {
829 0 : case IPT_SO_GET_INFO:
830 0 : return _bf_ipt_get_info_handler(request, response);
831 0 : case IPT_SO_GET_ENTRIES:
832 0 : return _bf_ipt_get_entries_handler(request, response);
833 0 : default:
834 0 : return bf_warn_r(-ENOTSUP,
835 : "unsupported custom ipt request type: %d",
836 : bf_request_ipt_cmd(request));
837 : };
838 0 : default:
839 0 : return bf_warn_r(-ENOTSUP, "unsupported ipt request type: %d",
840 : bf_request_ipt_cmd(request));
841 : };
842 :
843 : return 0;
844 : }
845 :
846 0 : static int _bf_ipt_pack(bf_wpack_t *pack)
847 : {
848 : UNUSED(pack);
849 :
850 0 : return 0;
851 : }
852 :
853 0 : static int _bf_ipt_unpack(bf_rpack_node_t node)
854 : {
855 : UNUSED(node);
856 :
857 0 : return 0;
858 : }
859 :
860 : const struct bf_front_ops ipt_front = {
861 : .setup = _bf_ipt_setup,
862 : .teardown = _bf_ipt_teardown,
863 : .request_handler = _bf_ipt_request_handler,
864 : .pack = _bf_ipt_pack,
865 : .unpack = _bf_ipt_unpack,
866 : };
|