LCOV - code coverage report
Current view: top level - core - hook.c (source / functions) Coverage Total Hit
Test: lcov.out Lines: 16.1 % 112 18
Test Date: 2025-03-25 15:17:39 Functions: 26.3 % 19 5

            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 : }
        

Generated by: LCOV version 2.0-1