Branch data 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 : : 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 : : 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 : : 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 : : bf_assert(entry && rule);
294 : :
295 [ # # # # : 0 : bf_list_foreach (&rule->matchers, matcher_node) {
# # ]
296 : : 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 : : 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 : : 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 : : 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 : : 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 : : 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 : : 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 : : 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 : : 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 : : bf_assert(request);
757 : : 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 : : bf_assert(request);
798 : : 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 : 20 : static int _bf_ipt_setup(void)
827 : : {
828 : 20 : return 0;
829 : : }
830 : :
831 : 19 : static int _bf_ipt_teardown(void)
832 : : {
833 : 19 : 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 : 77 : static int _bf_ipt_pack(bf_wpack_t *pack)
877 : : {
878 : : UNUSED(pack);
879 : :
880 : 77 : return 0;
881 : : }
882 : :
883 : 2 : static int _bf_ipt_unpack(bf_rpack_node_t node)
884 : : {
885 : : UNUSED(node);
886 : :
887 : 2 : 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 : : };
|