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