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