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 <stdio.h>
16 : #include <stdlib.h>
17 : #include <string.h>
18 : #include <sys/socket.h>
19 :
20 : #include "core/dump.h"
21 : #include "core/flavor.h"
22 : #include "core/helper.h"
23 : #include "core/list.h"
24 : #include "core/logger.h"
25 : #include "core/marsh.h"
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 : static_assert(ARRAY_SIZE(_bf_hook_strs) == _BF_HOOK_MAX,
40 : "missing entries in bf_hook strings array");
41 :
42 12 : const char *bf_hook_to_str(enum bf_hook hook)
43 : {
44 12 : return _bf_hook_strs[hook];
45 : }
46 :
47 4 : enum bf_hook bf_hook_from_str(const char *str)
48 : {
49 4 : bf_assert(str);
50 :
51 33 : for (enum bf_hook hook = 0; hook < _BF_HOOK_MAX; ++hook) {
52 30 : if (bf_streq(_bf_hook_strs[hook], str))
53 : return hook;
54 : }
55 :
56 : return -EINVAL;
57 : }
58 :
59 14 : enum bf_flavor bf_hook_to_flavor(enum bf_hook hook)
60 : {
61 : static const enum bf_flavor flavors[] = {
62 : [BF_HOOK_XDP] = BF_FLAVOR_XDP,
63 : [BF_HOOK_TC_INGRESS] = BF_FLAVOR_TC,
64 : [BF_HOOK_NF_PRE_ROUTING] = BF_FLAVOR_NF,
65 : [BF_HOOK_NF_LOCAL_IN] = BF_FLAVOR_NF,
66 : [BF_HOOK_CGROUP_INGRESS] = BF_FLAVOR_CGROUP,
67 : [BF_HOOK_CGROUP_EGRESS] = BF_FLAVOR_CGROUP,
68 : [BF_HOOK_NF_FORWARD] = BF_FLAVOR_NF,
69 : [BF_HOOK_NF_LOCAL_OUT] = BF_FLAVOR_NF,
70 : [BF_HOOK_NF_POST_ROUTING] = BF_FLAVOR_NF,
71 : [BF_HOOK_TC_EGRESS] = BF_FLAVOR_TC,
72 : };
73 :
74 : static_assert(ARRAY_SIZE(flavors) == _BF_HOOK_MAX,
75 : "missing entries in bf_flavor array");
76 :
77 14 : return flavors[hook];
78 : }
79 :
80 10 : enum bpf_attach_type bf_hook_to_bpf_attach_type(enum bf_hook hook)
81 : {
82 : static const enum bpf_attach_type attach_types[] = {
83 : [BF_HOOK_XDP] = 0,
84 : [BF_HOOK_TC_INGRESS] = BPF_TCX_INGRESS,
85 : [BF_HOOK_NF_PRE_ROUTING] = BPF_NETFILTER,
86 : [BF_HOOK_NF_LOCAL_IN] = BPF_NETFILTER,
87 : [BF_HOOK_CGROUP_INGRESS] = BPF_CGROUP_INET_INGRESS,
88 : [BF_HOOK_CGROUP_EGRESS] = BPF_CGROUP_INET_EGRESS,
89 : [BF_HOOK_NF_FORWARD] = BPF_NETFILTER,
90 : [BF_HOOK_NF_LOCAL_OUT] = BPF_NETFILTER,
91 : [BF_HOOK_NF_POST_ROUTING] = BPF_NETFILTER,
92 : [BF_HOOK_TC_EGRESS] = BPF_TCX_EGRESS,
93 : };
94 :
95 : static_assert(ARRAY_SIZE(attach_types) == _BF_HOOK_MAX,
96 : "missing entries in bpf_attach_type array");
97 :
98 10 : return attach_types[hook];
99 : }
100 :
101 10 : enum bpf_prog_type bf_hook_to_bpf_prog_type(enum bf_hook hook)
102 : {
103 : static const enum bpf_prog_type prog_types[] = {
104 : [BF_HOOK_XDP] = BPF_PROG_TYPE_XDP,
105 : [BF_HOOK_TC_INGRESS] = BPF_PROG_TYPE_SCHED_CLS,
106 : [BF_HOOK_NF_PRE_ROUTING] = BPF_PROG_TYPE_NETFILTER,
107 : [BF_HOOK_NF_LOCAL_IN] = BPF_PROG_TYPE_NETFILTER,
108 : [BF_HOOK_CGROUP_INGRESS] = BPF_PROG_TYPE_CGROUP_SKB,
109 : [BF_HOOK_CGROUP_EGRESS] = BPF_PROG_TYPE_CGROUP_SKB,
110 : [BF_HOOK_NF_FORWARD] = BPF_PROG_TYPE_NETFILTER,
111 : [BF_HOOK_NF_LOCAL_OUT] = BPF_PROG_TYPE_NETFILTER,
112 : [BF_HOOK_NF_POST_ROUTING] = BPF_PROG_TYPE_NETFILTER,
113 : [BF_HOOK_TC_EGRESS] = BPF_PROG_TYPE_SCHED_CLS,
114 : };
115 :
116 : static_assert(ARRAY_SIZE(prog_types) == _BF_HOOK_MAX,
117 : "missing entries in bpf_prog_type array");
118 :
119 10 : return prog_types[hook];
120 : }
121 :
122 7 : enum nf_inet_hooks bf_hook_to_nf_hook(enum bf_hook hook)
123 : {
124 7 : switch (hook) {
125 : case BF_HOOK_NF_PRE_ROUTING:
126 : return NF_INET_PRE_ROUTING;
127 1 : case BF_HOOK_NF_LOCAL_IN:
128 1 : return NF_INET_LOCAL_IN;
129 1 : case BF_HOOK_NF_FORWARD:
130 1 : return NF_INET_FORWARD;
131 1 : case BF_HOOK_NF_LOCAL_OUT:
132 1 : return NF_INET_LOCAL_OUT;
133 1 : case BF_HOOK_NF_POST_ROUTING:
134 1 : return NF_INET_POST_ROUTING;
135 2 : default:
136 2 : bf_warn("bf_hook %s (%d) is not an nf_inet_hooks value",
137 : bf_hook_to_str(hook), hook);
138 : return -EINVAL;
139 : }
140 : }
141 :
142 6 : enum bf_hook bf_hook_from_nf_hook(enum nf_inet_hooks hook)
143 : {
144 : switch (hook) {
145 : case NF_INET_PRE_ROUTING:
146 : return BF_HOOK_NF_PRE_ROUTING;
147 : case NF_INET_LOCAL_IN:
148 : return BF_HOOK_NF_LOCAL_IN;
149 : case NF_INET_FORWARD:
150 : return BF_HOOK_NF_FORWARD;
151 : case NF_INET_LOCAL_OUT:
152 : return BF_HOOK_NF_LOCAL_OUT;
153 : case NF_INET_POST_ROUTING:
154 : return BF_HOOK_NF_POST_ROUTING;
155 1 : default:
156 1 : bf_warn("nf_inet_hooks %s (%d) is not a bf_hook value",
157 : bf_nf_hook_to_str(hook), hook);
158 : return -EINVAL;
159 : }
160 : }
161 :
162 6 : const char *bf_nf_hook_to_str(enum nf_inet_hooks hook)
163 : {
164 6 : switch (hook) {
165 : case NF_INET_PRE_ROUTING:
166 : return "nf_prerouting";
167 1 : case NF_INET_LOCAL_IN:
168 1 : return "nf_input";
169 1 : case NF_INET_FORWARD:
170 1 : return "nf_forward";
171 1 : case NF_INET_LOCAL_OUT:
172 1 : return "nf_output";
173 1 : case NF_INET_POST_ROUTING:
174 1 : return "nf_postrouting";
175 1 : default:
176 1 : bf_warn("unknown nf_inet_hooks value %d", hook);
177 : return NULL;
178 : }
179 : }
180 :
181 0 : static int _bf_hookopts_ifindex_parse(struct bf_hookopts *hookopts,
182 : const char *raw_opt)
183 : {
184 : unsigned long ifindex;
185 :
186 0 : bf_assert(hookopts && raw_opt);
187 :
188 0 : errno = 0;
189 0 : ifindex = strtoul(raw_opt, NULL, 0);
190 0 : if (errno != 0) {
191 0 : return bf_err_r(-errno, "failed to parse bf_hookopts type ifindex=%s",
192 : raw_opt);
193 : }
194 :
195 0 : if (ifindex > INT_MAX)
196 0 : return bf_err_r(-E2BIG, "ifindex is too big: %lu", ifindex);
197 :
198 0 : hookopts->ifindex = (int)ifindex;
199 0 : hookopts->used_opts |= BF_FLAG(BF_HOOKOPTS_IFINDEX);
200 :
201 0 : return 0;
202 : }
203 :
204 0 : static void _bf_hookopts_ifindex_dump(const struct bf_hookopts *hookopts,
205 : prefix_t *prefix)
206 : {
207 0 : bf_assert(hookopts && prefix);
208 :
209 0 : DUMP(prefix, "ifindex: %d", hookopts->ifindex);
210 0 : }
211 :
212 0 : static int _bf_hookopts_cgpath_parse(struct bf_hookopts *hookopts,
213 : const char *raw_opt)
214 : {
215 0 : bf_assert(hookopts && raw_opt);
216 :
217 0 : hookopts->cgpath = strdup(raw_opt);
218 0 : if (!hookopts->cgpath) {
219 0 : return bf_err_r(-ENOMEM, "failed to copy hook option cgpath=%s",
220 : raw_opt);
221 : }
222 :
223 0 : hookopts->used_opts |= BF_FLAG(BF_HOOKOPTS_CGPATH);
224 :
225 0 : return 0;
226 : }
227 :
228 0 : static void _bf_hookopts_cgpath_dump(const struct bf_hookopts *hookopts,
229 : prefix_t *prefix)
230 : {
231 0 : bf_assert(hookopts && prefix);
232 :
233 0 : DUMP(prefix, "cgpath: %s", hookopts->cgpath);
234 0 : }
235 :
236 7 : static int _bf_hookopts_family_parse(struct bf_hookopts *hookopts,
237 : const char *raw_opt)
238 : {
239 7 : bf_assert(hookopts && raw_opt);
240 :
241 4 : if (bf_streq("inet4", raw_opt))
242 1 : hookopts->family = PF_INET;
243 3 : else if (bf_streq("inet6", raw_opt))
244 1 : hookopts->family = PF_INET6;
245 : else
246 2 : return bf_err_r(-ENOTSUP, "unknown netfilter family '%s'", raw_opt);
247 :
248 2 : hookopts->used_opts |= BF_FLAG(BF_HOOKOPTS_FAMILY);
249 :
250 2 : return 0;
251 : }
252 :
253 0 : static void _bf_hookopts_family_dump(const struct bf_hookopts *hookopts,
254 : prefix_t *prefix)
255 : {
256 0 : bf_assert(hookopts && prefix);
257 :
258 0 : DUMP(prefix, "family: %s", hookopts->family == PF_INET ? "inet4" : "inet6");
259 0 : }
260 :
261 14 : static int _bf_hookopts_priorities_parse(struct bf_hookopts *hookopts,
262 : const char *raw_opt)
263 : {
264 : unsigned long priorities[2];
265 : _cleanup_free_ char *copy = NULL;
266 : char *right, *end;
267 :
268 14 : bf_assert(hookopts && raw_opt);
269 :
270 11 : copy = strdup(raw_opt);
271 11 : if (!copy)
272 : return -ENOMEM;
273 :
274 11 : end = copy + strlen(copy);
275 :
276 11 : right = strchr(copy, '-');
277 11 : if (!right)
278 1 : goto err_parsing;
279 :
280 10 : *right = '\0';
281 10 : ++right;
282 10 : if (end <= right)
283 1 : goto err_parsing;
284 :
285 9 : errno = 0;
286 9 : priorities[0] = strtoul(copy, NULL, 0);
287 9 : if (errno != 0)
288 0 : goto err_parsing;
289 :
290 9 : priorities[1] = strtoul(right, NULL, 0);
291 9 : if (errno != 0)
292 0 : goto err_parsing;
293 :
294 9 : if (priorities[0] > INT_MAX || priorities[1] > INT_MAX)
295 0 : return bf_err_r(-EINVAL, "priorities can't be bigger than %d", INT_MAX);
296 :
297 9 : if (priorities[0] == priorities[1])
298 1 : return bf_err_r(-EINVAL, "priorities must be different");
299 :
300 8 : if (!priorities[0] || !priorities[1])
301 5 : return bf_err_r(-EINVAL, "priorities can't be 0");
302 :
303 3 : hookopts->priorities[0] = (int)priorities[0];
304 3 : hookopts->priorities[1] = (int)priorities[1];
305 3 : hookopts->used_opts |= BF_FLAG(BF_HOOKOPTS_PRIORITIES);
306 :
307 3 : return 0;
308 :
309 2 : err_parsing:
310 2 : return bf_err_r(-EINVAL, "failed to parse '%s', expecting '$INT-$INT'",
311 : raw_opt);
312 : }
313 :
314 0 : static void _bf_hookopts_priorities_dump(const struct bf_hookopts *hookopts,
315 : prefix_t *prefix)
316 : {
317 0 : bf_assert(hookopts && prefix);
318 :
319 0 : DUMP(prefix, "priorities: %d, %d", hookopts->priorities[0],
320 : hookopts->priorities[1]);
321 0 : }
322 :
323 : static struct bf_hookopts_ops
324 : {
325 : const char *name;
326 : enum bf_hookopts_type type;
327 : uint32_t supported_by;
328 : uint32_t required_by;
329 : int (*parse)(struct bf_hookopts *, const char *);
330 : void (*dump)(const struct bf_hookopts *, prefix_t *);
331 : } _bf_hookopts_ops[] = {
332 : [BF_HOOKOPTS_IFINDEX] = {.name = "ifindex",
333 : .type = BF_HOOKOPTS_IFINDEX,
334 : .required_by =
335 : BF_FLAGS(BF_FLAVOR_XDP, BF_FLAVOR_TC),
336 : .supported_by = 0,
337 : .parse = _bf_hookopts_ifindex_parse,
338 : .dump = _bf_hookopts_ifindex_dump},
339 : [BF_HOOKOPTS_CGPATH] = {.name = "cgpath",
340 : .type = BF_HOOKOPTS_CGPATH,
341 : .required_by = BF_FLAGS(BF_FLAVOR_CGROUP),
342 : .supported_by = 0,
343 : .parse = _bf_hookopts_cgpath_parse,
344 : .dump = _bf_hookopts_cgpath_dump},
345 : [BF_HOOKOPTS_FAMILY] = {.name = "family",
346 : .type = BF_HOOKOPTS_FAMILY,
347 : .required_by = BF_FLAGS(BF_FLAVOR_NF),
348 : .supported_by = 0,
349 : .parse = _bf_hookopts_family_parse,
350 : .dump = _bf_hookopts_family_dump},
351 : [BF_HOOKOPTS_PRIORITIES] = {.name = "priorities",
352 : .type = BF_HOOKOPTS_PRIORITIES,
353 : .required_by = BF_FLAGS(BF_FLAVOR_NF),
354 : .supported_by = 0,
355 : .parse = _bf_hookopts_priorities_parse,
356 : .dump = _bf_hookopts_priorities_dump},
357 : };
358 :
359 : static_assert(ARRAY_SIZE(_bf_hookopts_ops) == _BF_HOOKOPTS_MAX,
360 : "missing entries in bf_hookopts_ops array");
361 :
362 : #define _bf_hookopts_is_required(type, flavor) \
363 : (_bf_hookopts_ops[type].required_by & BF_FLAG(flavor))
364 :
365 : #define _bf_hookopts_is_supported(type, flavor) \
366 : ((_bf_hookopts_ops[type].supported_by & BF_FLAG(flavor)) || \
367 : _bf_hookopts_is_required((type), (flavor)))
368 :
369 0 : static struct bf_hookopts_ops *_bf_hookopts_get_ops(const char *key)
370 : {
371 0 : bf_assert(key);
372 :
373 0 : for (enum bf_hookopts_type type = 0; type < _BF_HOOKOPTS_MAX; ++type) {
374 0 : if (bf_streq(_bf_hookopts_ops[type].name, key))
375 0 : return &_bf_hookopts_ops[type];
376 : }
377 :
378 : return NULL;
379 : }
380 :
381 0 : int bf_hookopts_new(struct bf_hookopts **hookopts)
382 : {
383 0 : bf_assert(hookopts);
384 :
385 0 : *hookopts = calloc(1, sizeof(struct bf_hookopts));
386 0 : if (!*hookopts)
387 0 : return bf_err_r(-ENOMEM, "failed to allocate a new bf_hookopts object");
388 :
389 : return 0;
390 : }
391 :
392 0 : int bf_hookopts_new_from_marsh(struct bf_hookopts **hookopts,
393 : const struct bf_marsh *marsh)
394 : {
395 0 : _free_bf_hookopts_ struct bf_hookopts *_hookopts = NULL;
396 : struct bf_marsh *child = NULL;
397 : int r;
398 :
399 0 : bf_assert(hookopts && marsh);
400 :
401 0 : r = bf_hookopts_new(&_hookopts);
402 0 : if (r)
403 : return r;
404 :
405 0 : if (!(child = bf_marsh_next_child(marsh, child)))
406 0 : return bf_err_r(-EINVAL, "bf_hookopts: missing used_opts field");
407 0 : memcpy(&_hookopts->used_opts, child->data, sizeof(_hookopts->used_opts));
408 :
409 0 : if (!(child = bf_marsh_next_child(marsh, child)))
410 0 : return bf_err_r(-EINVAL, "bf_hookopts: missing ifindex field");
411 0 : memcpy(&_hookopts->ifindex, child->data, sizeof(_hookopts->ifindex));
412 :
413 0 : if (!(child = bf_marsh_next_child(marsh, child)))
414 0 : return bf_err_r(-EINVAL, "bf_hookopts: missing cgpath field");
415 0 : if (child->data_len) {
416 0 : _hookopts->cgpath = strdup(child->data);
417 0 : if (!_hookopts->cgpath)
418 : return -ENOMEM;
419 : }
420 :
421 0 : if (!(child = bf_marsh_next_child(marsh, child)))
422 0 : return bf_err_r(-EINVAL, "bf_hookopts: missing family field");
423 0 : memcpy(&_hookopts->family, child->data, sizeof(_hookopts->family));
424 :
425 0 : if (!(child = bf_marsh_next_child(marsh, child)))
426 0 : return bf_err_r(-EINVAL, "bf_hookopts: missing priorities field");
427 0 : memcpy(&_hookopts->priorities, child->data, sizeof(_hookopts->priorities));
428 :
429 0 : if (bf_marsh_next_child(marsh, child))
430 0 : return bf_err_r(-E2BIG, "too many serialized fields for bf_hookopts");
431 :
432 0 : *hookopts = TAKE_PTR(_hookopts);
433 :
434 0 : return 0;
435 : }
436 :
437 0 : void bf_hookopts_clean(struct bf_hookopts *hookopts)
438 : {
439 : freep((void *)&hookopts->cgpath);
440 0 : }
441 :
442 4 : void bf_hookopts_free(struct bf_hookopts **hookopts)
443 : {
444 4 : bf_assert(hookopts);
445 :
446 4 : if (!*hookopts)
447 : return;
448 :
449 0 : bf_hookopts_clean(*hookopts);
450 : freep((void *)hookopts);
451 : }
452 :
453 0 : int bf_hookopts_marsh(const struct bf_hookopts *hookopts,
454 : struct bf_marsh **marsh)
455 : {
456 0 : _cleanup_bf_marsh_ struct bf_marsh *_marsh = NULL;
457 : int r = 0;
458 :
459 0 : bf_assert(hookopts && marsh);
460 :
461 0 : r = bf_marsh_new(&_marsh, NULL, 0);
462 0 : if (r)
463 0 : return bf_err_r(r, "failed to create a new marsh for bf_hookopts");
464 :
465 0 : r = bf_marsh_add_child_raw(&_marsh, &hookopts->used_opts,
466 : sizeof(hookopts->used_opts));
467 0 : if (r)
468 0 : return bf_err_r(r, "failed to marsh bf_hookopts.used_opts");
469 :
470 0 : r = bf_marsh_add_child_raw(&_marsh, &hookopts->ifindex,
471 : sizeof(hookopts->ifindex));
472 0 : if (r)
473 0 : return bf_err_r(r, "failed to marsh bf_hookopts.ifindex");
474 :
475 0 : r = bf_marsh_add_child_raw(&_marsh, hookopts->cgpath,
476 0 : hookopts->cgpath ? strlen(hookopts->cgpath) + 1 :
477 : 0);
478 0 : if (r)
479 0 : return bf_err_r(r, "failed to marsh bf_hookopts.cgpath");
480 :
481 0 : r = bf_marsh_add_child_raw(&_marsh, &hookopts->family,
482 : sizeof(hookopts->family));
483 0 : if (r)
484 0 : return bf_err_r(r, "failed to marsh bf_hookopts.family");
485 :
486 0 : r = bf_marsh_add_child_raw(&_marsh, hookopts->priorities,
487 : sizeof(hookopts->priorities));
488 0 : if (r)
489 0 : return bf_err_r(r, "failed to marsh bf_hookopts.priorities");
490 :
491 0 : *marsh = TAKE_PTR(_marsh);
492 :
493 0 : return 0;
494 : }
495 :
496 0 : void bf_hookopts_dump(const struct bf_hookopts *hookopts, prefix_t *prefix)
497 : {
498 0 : bf_assert(hookopts && prefix);
499 :
500 0 : DUMP(prefix, "struct bf_hookopts at %p", hookopts);
501 :
502 0 : bf_dump_prefix_push(prefix);
503 0 : DUMP(prefix, "used_opts: 0x%08x", hookopts->used_opts);
504 :
505 0 : for (enum bf_hookopts_type type = 0; type < _BF_HOOKOPTS_MAX; ++type) {
506 0 : if (type == _BF_HOOKOPTS_MAX - 1)
507 0 : bf_dump_prefix_last(prefix);
508 :
509 0 : if (bf_hookopts_is_used(hookopts, type))
510 0 : _bf_hookopts_ops[type].dump(hookopts, prefix);
511 : else
512 0 : DUMP(prefix, "%s: <unset>", _bf_hookopts_ops[type].name);
513 : }
514 :
515 0 : bf_dump_prefix_pop(prefix);
516 0 : }
517 :
518 0 : int bf_hookopts_parse_opt(struct bf_hookopts *hookopts, const char *raw_opt)
519 : {
520 : char *value;
521 : struct bf_hookopts_ops *ops;
522 : int r;
523 :
524 0 : bf_assert(hookopts && raw_opt);
525 :
526 0 : value = strchr(raw_opt, '=');
527 0 : if (!value)
528 : return -ENOENT;
529 :
530 0 : *value = '\0';
531 0 : ++value;
532 :
533 0 : ops = _bf_hookopts_get_ops(raw_opt);
534 0 : if (!ops) {
535 0 : return bf_err_r(-ENOTSUP, "unknown hook option '%s', ignoring",
536 : raw_opt);
537 : }
538 :
539 0 : r = ops->parse(hookopts, value);
540 0 : if (r < 0)
541 : return r;
542 :
543 : return 0;
544 : }
545 :
546 0 : int bf_hookopts_parse_opts(struct bf_hookopts *hookopts, bf_list *raw_opts)
547 : {
548 : int r;
549 :
550 0 : bf_assert(hookopts && raw_opts);
551 :
552 0 : if (!raw_opts)
553 : return 0;
554 :
555 0 : bf_list_foreach (raw_opts, raw_opt_node) {
556 0 : r = bf_hookopts_parse_opt(hookopts,
557 0 : bf_list_node_get_data(raw_opt_node));
558 0 : if (r)
559 : return r;
560 : }
561 :
562 : return 0;
563 : }
564 :
565 0 : int bf_hookopts_validate(const struct bf_hookopts *hookopts, enum bf_hook hook)
566 : {
567 0 : enum bf_flavor flavor = bf_hook_to_flavor(hook);
568 :
569 0 : bf_assert(hookopts);
570 :
571 0 : for (enum bf_hookopts_type type = 0; type < _BF_HOOKOPTS_MAX; ++type) {
572 : struct bf_hookopts_ops *ops = &_bf_hookopts_ops[type];
573 0 : bool is_used = bf_hookopts_is_used(hookopts, type);
574 0 : bool is_required = _bf_hookopts_is_required(type, flavor);
575 0 : bool is_supported = _bf_hookopts_is_supported(type, flavor);
576 :
577 0 : if (is_required && !is_used) {
578 0 : return bf_err_r(-EINVAL,
579 : "hook option '%s' is required for '%s' chains",
580 : ops->name, bf_hook_to_str(hook));
581 : }
582 :
583 0 : if (is_used && !(is_supported | is_required)) {
584 0 : return bf_err_r(-ENOTSUP,
585 : "hook option '%s' is not supported for '%s' chains",
586 : ops->name, bf_hook_to_str(hook));
587 : }
588 : }
589 :
590 : return 0;
591 : }
|