Line data Source code
1 : /* SPDX-License-Identifier: GPL-2.0-only */
2 : /*
3 : * Copyright (c) 2023 Meta Platforms, Inc. and affiliates.
4 : */
5 :
6 : #include "core/hook.h"
7 :
8 : #include <linux/bpf.h>
9 : #include <linux/netfilter.h>
10 :
11 : #include <errno.h>
12 : #include <limits.h>
13 : #include <stdbool.h>
14 : #include <stdint.h>
15 : #include <stdlib.h>
16 : #include <string.h>
17 :
18 : #include "core/dump.h"
19 : #include "core/helper.h"
20 : #include "core/list.h"
21 : #include "core/logger.h"
22 :
23 : /// Maximum length of the chain name: @c BPF_OBJ_NAME_LEN minus the length
24 : /// of the BPF object suffix.
25 : #define BF_CHAIN_NAME_LEN 11
26 :
27 : static const char *_bf_hook_strs[] = {
28 : [BF_HOOK_XDP] = "BF_HOOK_XDP",
29 : [BF_HOOK_TC_INGRESS] = "BF_HOOK_TC_INGRESS",
30 : [BF_HOOK_NF_PRE_ROUTING] = "BF_HOOK_NF_PRE_ROUTING",
31 : [BF_HOOK_NF_LOCAL_IN] = "BF_HOOK_NF_LOCAL_IN",
32 : [BF_HOOK_CGROUP_INGRESS] = "BF_HOOK_CGROUP_INGRESS",
33 : [BF_HOOK_CGROUP_EGRESS] = "BF_HOOK_CGROUP_EGRESS",
34 : [BF_HOOK_NF_FORWARD] = "BF_HOOK_NF_FORWARD",
35 : [BF_HOOK_NF_LOCAL_OUT] = "BF_HOOK_NF_LOCAL_OUT",
36 : [BF_HOOK_NF_POST_ROUTING] = "BF_HOOK_NF_POST_ROUTING",
37 : [BF_HOOK_TC_EGRESS] = "BF_HOOK_TC_EGRESS",
38 : };
39 :
40 : static_assert(ARRAY_SIZE(_bf_hook_strs) == _BF_HOOK_MAX,
41 : "missing entries in hooks_str array");
42 :
43 12 : const char *bf_hook_to_str(enum bf_hook hook)
44 : {
45 12 : bf_assert(0 <= hook && hook < _BF_HOOK_MAX);
46 :
47 10 : return _bf_hook_strs[hook];
48 : }
49 :
50 14 : int bf_hook_from_str(const char *str, enum bf_hook *hook)
51 : {
52 14 : bf_assert(str);
53 13 : bf_assert(hook);
54 :
55 77 : for (size_t i = 0; i < _BF_HOOK_MAX; ++i) {
56 75 : if (bf_streq(_bf_hook_strs[i], str)) {
57 10 : *hook = i;
58 10 : return 0;
59 : }
60 : }
61 :
62 : return -EINVAL;
63 : }
64 :
65 12 : unsigned int bf_hook_to_bpf_prog_type(enum bf_hook hook)
66 : {
67 : static const unsigned int prog_type[] = {
68 : [BF_HOOK_XDP] = BPF_PROG_TYPE_XDP,
69 : [BF_HOOK_TC_INGRESS] = BPF_PROG_TYPE_SCHED_CLS,
70 : [BF_HOOK_NF_PRE_ROUTING] = BPF_PROG_TYPE_NETFILTER,
71 : [BF_HOOK_NF_LOCAL_IN] = BPF_PROG_TYPE_NETFILTER,
72 : [BF_HOOK_CGROUP_INGRESS] = BPF_PROG_TYPE_CGROUP_SKB,
73 : [BF_HOOK_CGROUP_EGRESS] = BPF_PROG_TYPE_CGROUP_SKB,
74 : [BF_HOOK_NF_FORWARD] = BPF_PROG_TYPE_NETFILTER,
75 : [BF_HOOK_NF_LOCAL_OUT] = BPF_PROG_TYPE_NETFILTER,
76 : [BF_HOOK_NF_POST_ROUTING] = BPF_PROG_TYPE_NETFILTER,
77 : [BF_HOOK_TC_EGRESS] = BPF_PROG_TYPE_SCHED_CLS,
78 : };
79 :
80 12 : bf_assert(0 <= hook && hook < _BF_HOOK_MAX);
81 : static_assert(ARRAY_SIZE(prog_type) == _BF_HOOK_MAX,
82 : "missing entries in prog_type array");
83 :
84 10 : return prog_type[hook];
85 : }
86 :
87 12 : enum bpf_attach_type bf_hook_to_attach_type(enum bf_hook hook)
88 : {
89 : static const enum bpf_attach_type hooks[] = {
90 : [BF_HOOK_XDP] = 0,
91 : [BF_HOOK_TC_INGRESS] = BPF_TCX_INGRESS,
92 : [BF_HOOK_NF_PRE_ROUTING] = BPF_NETFILTER,
93 : [BF_HOOK_NF_LOCAL_IN] = BPF_NETFILTER,
94 : [BF_HOOK_CGROUP_INGRESS] = BPF_CGROUP_INET_INGRESS,
95 : [BF_HOOK_CGROUP_EGRESS] = BPF_CGROUP_INET_EGRESS,
96 : [BF_HOOK_NF_FORWARD] = BPF_NETFILTER,
97 : [BF_HOOK_NF_LOCAL_OUT] = BPF_NETFILTER,
98 : [BF_HOOK_NF_POST_ROUTING] = BPF_NETFILTER,
99 : [BF_HOOK_TC_EGRESS] = BPF_TCX_EGRESS,
100 : };
101 :
102 12 : bf_assert(0 <= hook && hook < _BF_HOOK_MAX);
103 : static_assert(ARRAY_SIZE(hooks) == _BF_HOOK_MAX,
104 : "missing entries in hooks array");
105 :
106 10 : return hooks[hook];
107 : }
108 :
109 0 : enum nf_inet_hooks bf_hook_to_nf_hook(enum bf_hook hook)
110 : {
111 0 : switch (hook) {
112 : case BF_HOOK_NF_PRE_ROUTING:
113 : return NF_INET_PRE_ROUTING;
114 : case BF_HOOK_NF_LOCAL_IN:
115 : return NF_INET_LOCAL_IN;
116 0 : case BF_HOOK_NF_FORWARD:
117 0 : return NF_INET_FORWARD;
118 0 : case BF_HOOK_NF_LOCAL_OUT:
119 0 : return NF_INET_LOCAL_OUT;
120 0 : case BF_HOOK_NF_POST_ROUTING:
121 0 : return NF_INET_POST_ROUTING;
122 0 : default:
123 0 : bf_assert(0);
124 : }
125 :
126 : // This is required to silence a compiler warning, but will never be reached.
127 0 : return 0;
128 : }
129 :
130 0 : enum bf_hook bf_nf_hook_to_hook(enum nf_inet_hooks hook)
131 : {
132 : switch (hook) {
133 : case NF_INET_PRE_ROUTING:
134 : return BF_HOOK_NF_PRE_ROUTING;
135 : case NF_INET_LOCAL_IN:
136 : return BF_HOOK_NF_LOCAL_IN;
137 : case NF_INET_FORWARD:
138 : return BF_HOOK_NF_FORWARD;
139 : case NF_INET_LOCAL_OUT:
140 : return BF_HOOK_NF_LOCAL_OUT;
141 : case NF_INET_POST_ROUTING:
142 : return BF_HOOK_NF_POST_ROUTING;
143 0 : default:
144 0 : bf_assert(0);
145 : }
146 :
147 : // This is required to silence a compiler warning, but will never be reached.
148 0 : return 0;
149 : }
150 :
151 0 : static int _bf_hook_opt_ifindex_parse(struct bf_hook_opts *opts,
152 : const char *raw_opt)
153 : {
154 : unsigned long ifindex;
155 :
156 0 : errno = 0;
157 0 : ifindex = strtoul(raw_opt, NULL, 0);
158 0 : if (errno != 0) {
159 0 : return bf_err_r(-errno, "failed to parse hook options ifindex=%s",
160 : raw_opt);
161 : }
162 :
163 0 : if (ifindex > UINT_MAX)
164 0 : return bf_err_r(-E2BIG, "ifindex is too big: %lu", ifindex);
165 :
166 0 : opts->ifindex = (uint32_t)ifindex;
167 :
168 0 : return 0;
169 : }
170 :
171 0 : static void _bf_hook_opt_ifindex_dump(const struct bf_hook_opts *opts,
172 : prefix_t *prefix)
173 : {
174 0 : DUMP(prefix, "ifindex: %d", opts->ifindex);
175 0 : }
176 :
177 0 : static int _bf_hook_opt_cgroup_parse(struct bf_hook_opts *opts,
178 : const char *raw_opt)
179 : {
180 0 : opts->cgroup = strdup(raw_opt);
181 0 : if (!opts->cgroup)
182 0 : return bf_err_r(-ENOMEM, "failed to copy cgroup path '%s'", raw_opt);
183 :
184 : return 0;
185 : }
186 :
187 0 : static void _bf_hook_opt_cgroup_dump(const struct bf_hook_opts *opts,
188 : prefix_t *prefix)
189 : {
190 0 : DUMP(prefix, "cgroup: %s", opts->cgroup);
191 0 : }
192 :
193 0 : static int _bf_hook_opt_name_parse(struct bf_hook_opts *opts,
194 : const char *raw_opt)
195 : {
196 0 : if (strlen(raw_opt) > BF_CHAIN_NAME_LEN) {
197 0 : return bf_err_r(E2BIG, "a chain name should be at most %d characters",
198 : BF_CHAIN_NAME_LEN);
199 : }
200 :
201 0 : opts->name = strdup(raw_opt);
202 0 : if (!opts->name)
203 0 : return bf_err_r(-ENOMEM, "failed to copy chain name '%s'", raw_opt);
204 :
205 : return 0;
206 : }
207 :
208 0 : static void _bf_hook_opt_name_dump(const struct bf_hook_opts *opts,
209 : prefix_t *prefix)
210 : {
211 0 : DUMP(prefix, "name: %s", opts->name);
212 0 : }
213 :
214 0 : static int _bf_hook_opt_attach_parse(struct bf_hook_opts *opts,
215 : const char *raw_opt)
216 : {
217 0 : if (bf_streq(raw_opt, "yes"))
218 0 : opts->attach = true;
219 0 : else if (bf_streq(raw_opt, "no"))
220 0 : opts->attach = false;
221 : else
222 0 : return bf_err_r(-EINVAL, "unknown attach value '%s'", raw_opt);
223 :
224 : return 0;
225 : }
226 :
227 0 : static void _bf_hook_opt_attach_dump(const struct bf_hook_opts *opts,
228 : prefix_t *prefix)
229 : {
230 0 : DUMP(prefix, "attach: %s", opts->attach ? "yes" : "no");
231 0 : }
232 :
233 : static struct bf_hook_opt_support
234 : {
235 : uint32_t required;
236 : uint32_t supported;
237 : } _bf_hook_opts_support[] = {
238 : [BF_HOOK_XDP] =
239 : {
240 : .required = 1 << BF_HOOK_OPT_IFINDEX,
241 : .supported = 1 << BF_HOOK_OPT_IFINDEX | 1 << BF_HOOK_OPT_NAME |
242 : 1 << BF_HOOK_OPT_ATTACH,
243 : },
244 : [BF_HOOK_TC_INGRESS] =
245 : {
246 : .required = 1 << BF_HOOK_OPT_IFINDEX,
247 : .supported = 1 << BF_HOOK_OPT_IFINDEX | 1 << BF_HOOK_OPT_NAME |
248 : 1 << BF_HOOK_OPT_ATTACH,
249 : },
250 : [BF_HOOK_NF_PRE_ROUTING] =
251 : {
252 : .supported = 1 << BF_HOOK_OPT_NAME | 1 << BF_HOOK_OPT_ATTACH,
253 : },
254 : [BF_HOOK_NF_LOCAL_IN] =
255 : {
256 : .supported = 1 << BF_HOOK_OPT_NAME | 1 << BF_HOOK_OPT_ATTACH,
257 : },
258 : [BF_HOOK_CGROUP_INGRESS] =
259 : {
260 : .required = 1 << BF_HOOK_OPT_CGROUP,
261 : .supported = 1 << BF_HOOK_OPT_CGROUP | 1 << BF_HOOK_OPT_NAME |
262 : 1 << BF_HOOK_OPT_ATTACH,
263 : },
264 : [BF_HOOK_CGROUP_EGRESS] =
265 : {
266 : .required = 1 << BF_HOOK_OPT_CGROUP,
267 : .supported = 1 << BF_HOOK_OPT_CGROUP | 1 << BF_HOOK_OPT_NAME |
268 : 1 << BF_HOOK_OPT_ATTACH,
269 : },
270 : [BF_HOOK_NF_FORWARD] =
271 : {
272 : .supported = 1 << BF_HOOK_OPT_NAME | 1 << BF_HOOK_OPT_ATTACH,
273 : },
274 : [BF_HOOK_NF_LOCAL_OUT] =
275 : {
276 : .supported = 1 << BF_HOOK_OPT_NAME | 1 << BF_HOOK_OPT_ATTACH,
277 : },
278 : [BF_HOOK_NF_POST_ROUTING] =
279 : {
280 : .supported = 1 << BF_HOOK_OPT_NAME | 1 << BF_HOOK_OPT_ATTACH,
281 : },
282 : [BF_HOOK_TC_EGRESS] =
283 : {
284 : .required = 1 << BF_HOOK_OPT_IFINDEX,
285 : .supported = 1 << BF_HOOK_OPT_IFINDEX | 1 << BF_HOOK_OPT_NAME |
286 : 1 << BF_HOOK_OPT_ATTACH,
287 : },
288 : };
289 :
290 : static_assert(ARRAY_SIZE(_bf_hook_opts_support) == _BF_HOOK_MAX,
291 : "missing entries in hook options support array");
292 :
293 : static struct bf_hook_opt_ops
294 : {
295 : const char *name;
296 : enum bf_hook_opt opt;
297 : int (*parse)(struct bf_hook_opts *opts, const char *raw_opt);
298 : void (*dump)(const struct bf_hook_opts *opts, prefix_t *prefix);
299 : } _bf_hook_opt_ops[] = {
300 : {
301 : .name = "ifindex",
302 : .opt = BF_HOOK_OPT_IFINDEX,
303 : .parse = _bf_hook_opt_ifindex_parse,
304 : .dump = _bf_hook_opt_ifindex_dump,
305 : },
306 : {
307 : .name = "cgroup",
308 : .opt = BF_HOOK_OPT_CGROUP,
309 : .parse = _bf_hook_opt_cgroup_parse,
310 : .dump = _bf_hook_opt_cgroup_dump,
311 : },
312 : {
313 : .name = "name",
314 : .opt = BF_HOOK_OPT_NAME,
315 : .parse = _bf_hook_opt_name_parse,
316 : .dump = _bf_hook_opt_name_dump,
317 : },
318 : {
319 : .name = "attach",
320 : .opt = BF_HOOK_OPT_ATTACH,
321 : .parse = _bf_hook_opt_attach_parse,
322 : .dump = _bf_hook_opt_attach_dump,
323 : },
324 : };
325 :
326 : static_assert(ARRAY_SIZE(_bf_hook_opt_ops) == _BF_HOOK_OPT_MAX,
327 : "missing entries in hook option ops array");
328 :
329 : #define _bf_hook_opt_is_supported(hook, opt) \
330 : (_bf_hook_opts_support[hook].supported & (1 << (opt)))
331 : #define _bf_hook_opt_is_required(hook, opt) \
332 : (_bf_hook_opts_support[hook].required & (1 << (opt)))
333 : #define _bf_hook_opt_is_used(opts, opt) ((opts)->used_opts & (1 << (opt)))
334 :
335 0 : static struct bf_hook_opt_ops *_bf_hook_opts_get_ops(const char *key,
336 : size_t len)
337 : {
338 : int r;
339 :
340 0 : bf_assert(key && len > 0);
341 :
342 0 : for (int i = 0; i < _BF_HOOK_OPT_MAX; ++i) {
343 0 : r = strncmp(_bf_hook_opt_ops[i].name, key, len);
344 0 : if (r == 0)
345 0 : return &_bf_hook_opt_ops[i];
346 : }
347 :
348 : return NULL;
349 : }
350 :
351 0 : static int _bf_hook_opts_process_opts(struct bf_hook_opts *opts,
352 : enum bf_hook hook, bf_list *raw_opts)
353 : {
354 : const char *value;
355 : const struct bf_hook_opt_ops *ops;
356 : int r;
357 :
358 0 : bf_assert(opts);
359 :
360 0 : if (!raw_opts)
361 : return 0;
362 :
363 0 : bf_list_foreach (raw_opts, raw_opt_node) {
364 0 : const char *raw_opt = bf_list_node_get_data(raw_opt_node);
365 :
366 0 : value = strchr(raw_opt, '=');
367 :
368 0 : ops = _bf_hook_opts_get_ops(raw_opt, value - raw_opt);
369 0 : if (!ops)
370 0 : return bf_err_r(-ENOTSUP, "unknown option '%s', ignoring", raw_opt);
371 :
372 0 : if (!_bf_hook_opt_is_supported(hook, ops->opt)) {
373 0 : return bf_err_r(-ENOTSUP, "hook '%s' doesn't support option '%s'",
374 : bf_hook_to_str(hook), ops->name);
375 : }
376 :
377 0 : r = ops->parse(opts, value + 1);
378 0 : if (r < 0)
379 : return r;
380 :
381 0 : opts->used_opts |= (1 << ops->opt);
382 : }
383 :
384 : return 0;
385 : }
386 :
387 0 : int bf_hook_opts_init(struct bf_hook_opts *opts, enum bf_hook hook,
388 : bf_list *raw_opts)
389 : {
390 : int r;
391 :
392 0 : bf_assert(opts);
393 :
394 0 : *opts = (struct bf_hook_opts) {
395 : .used_opts = 1 << BF_HOOK_OPT_ATTACH,
396 : .attach = true,
397 : };
398 :
399 0 : r = _bf_hook_opts_process_opts(opts, hook, raw_opts);
400 0 : if (r < 0)
401 : return r;
402 :
403 0 : for (int i = 0; i < _BF_HOOK_OPT_MAX; ++i) {
404 0 : if (_bf_hook_opt_is_required(hook, i) &&
405 0 : !_bf_hook_opt_is_used(opts, i)) {
406 0 : return bf_err_r(-EINVAL, "hook '%s' requires option '%s'",
407 : bf_hook_to_str(hook), _bf_hook_opt_ops[i].name);
408 : }
409 : }
410 :
411 : return 0;
412 : }
413 :
414 9 : void bf_hook_opts_clean(struct bf_hook_opts *opts)
415 : {
416 : freep((void *)&opts->cgroup);
417 : freep((void *)&opts->name);
418 9 : }
419 :
420 0 : void bf_hook_opts_dump(const struct bf_hook_opts *opts, prefix_t *prefix,
421 : enum bf_hook hook)
422 : {
423 0 : DUMP(prefix, "struct bf_hook_opts at %p", opts);
424 0 : bf_dump_prefix_push(prefix);
425 :
426 0 : for (int i = 0; i < _BF_HOOK_OPT_MAX; ++i) {
427 : struct bf_hook_opt_ops *ops = &_bf_hook_opt_ops[i];
428 0 : if (i == _BF_HOOK_OPT_MAX - 1)
429 0 : bf_dump_prefix_last(prefix);
430 :
431 0 : if (!_bf_hook_opt_is_supported(hook, i)) {
432 0 : DUMP(prefix, "%s: <unsupported>", ops->name);
433 0 : } else if (!_bf_hook_opt_is_used(opts, i)) {
434 0 : DUMP(prefix, "%s: <unset>", ops->name);
435 : } else {
436 0 : ops->dump(opts, prefix);
437 : }
438 : }
439 :
440 0 : bf_dump_prefix_pop(prefix);
441 0 : }
|