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