]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
mnl: revisit hook listing
authorPablo Neira Ayuso <pablo@netfilter.org>
Mon, 2 Aug 2021 10:32:52 +0000 (12:32 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Fri, 6 Aug 2021 11:13:06 +0000 (13:13 +0200)
Update this command to display the hook datapath for a packet depending
on its family.

This patch also includes:

- Group of existing hooks based on the hook location.
- Order hooks by priority, from INT_MIN to INT_MAX.
- Do not add sign to priority zero.
- Refresh include/linux/netfilter/nfnetlink_hook.h cache copy.
- Use NFNLA_CHAIN_* attributes to print the chain family, table and name.
  If NFNLA_CHAIN_* attributes are not available, display the hookfn name.
- Update syntax: remove optional hook parameter, promote the 'device'
  argument.

The following example shows the hook datapath for IPv4 packets coming in
from netdevice 'eth0':

 # nft list hooks ip device eth0
 family ip {
        hook ingress {
                +0000000010 chain netdev x y [nf_tables]
                +0000000300 chain inet m w  [nf_tables]
        }
        hook input {
                -0000000100 chain ip a b [nf_tables]
                +0000000300 chain inet m z [nf_tables]
        }
        hook forward {
                -0000000225 selinux_ipv4_forward
                 0000000000 chain ip a c [nf_tables]
        }
        hook output {
                -0000000225 selinux_ipv4_output
        }
        hook postrouting {
                +0000000225 selinux_ipv4_postroute
        }
 }

Note that the listing above includes the existing netdev and inet
hooks/chains which *might* interfer in the travel of an incoming IPv4
packet. This allows users to debug the pipeline, basically, to
understand in what order the hooks/chains are evaluated for the IPv4
packets.

If the netdevice is not specified, then the ingress hooks are not
shown.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/linux/netfilter/nfnetlink_hook.h
include/rule.h
src/mnl.c
src/parser_bison.y
src/rule.c

index d8ac8278571bfb7da96433c166612f3b0f6777c8..bbcd285b22e1093d2db91f72c1fd280b4f9a49a2 100644 (file)
@@ -8,10 +8,10 @@ enum nfnl_hook_msg_types {
 };
 
 /**
- * enum nfnl_hook_attributes - nf_tables netfilter hook netlink attributes
+ * enum nfnl_hook_attributes - netfilter hook netlink attributes
  *
  * @NFNLA_HOOK_HOOKNUM: netfilter hook number (NLA_U32)
- * @NFNLAA_HOOK_PRIORITY: netfilter hook priority (NLA_U32)
+ * @NFNLA_HOOK_PRIORITY: netfilter hook priority (NLA_U32)
  * @NFNLA_HOOK_DEV: netdevice name (NLA_STRING)
  * @NFNLA_HOOK_FUNCTION_NAME: hook function name (NLA_STRING)
  * @NFNLA_HOOK_MODULE_NAME: kernel module that registered this hook (NLA_STRING)
@@ -43,6 +43,15 @@ enum nfnl_hook_chain_info_attributes {
 };
 #define NFNLA_HOOK_INFO_MAX (__NFNLA_HOOK_INFO_MAX - 1)
 
+enum nfnl_hook_chain_desc_attributes {
+       NFNLA_CHAIN_UNSPEC,
+       NFNLA_CHAIN_TABLE,
+       NFNLA_CHAIN_FAMILY,
+       NFNLA_CHAIN_NAME,
+       __NFNLA_CHAIN_MAX,
+};
+#define NFNLA_CHAIN_MAX (__NFNLA_CHAIN_MAX - 1)
+
 /**
  * enum nfnl_hook_chaintype - chain type
  *
@@ -51,4 +60,5 @@ enum nfnl_hook_chain_info_attributes {
 enum nfnl_hook_chaintype {
        NFNL_HOOK_TYPE_NFTABLES = 0x1,
 };
+
 #endif /* _NFNL_HOOK_H */
index 357326a3fcebb1732cc79d8fd1ac352307821fe7..be31695636df40b9dd10c4a3499b5c9598f8ca59 100644 (file)
@@ -270,6 +270,7 @@ extern struct chain *chain_binding_lookup(const struct table *table,
                                          const char *chain_name);
 
 extern const char *family2str(unsigned int family);
+#define __NF_ARP_INGRESS       255
 extern const char *hooknum2str(unsigned int family, unsigned int hooknum);
 extern const char *chain_policy2str(uint32_t policy);
 extern void chain_print_plain(const struct chain *chain,
index f28d6605835ffc7ef3eabb2741183dcebc5a9862..2d5afdfe8c7f7f62e53d05d06335efbef96f21a9 100644 (file)
--- a/src/mnl.c
+++ b/src/mnl.c
@@ -44,6 +44,9 @@ struct basehook {
        const char *hookfn;
        const char *table;
        const char *chain;
+       int family;
+       int chain_family;
+       uint32_t num;
        int prio;
 };
 
@@ -1948,6 +1951,20 @@ static void basehook_free(struct basehook *b)
 
 static void basehook_list_add_tail(struct basehook *b, struct list_head *head)
 {
+       struct basehook *hook;
+
+       list_for_each_entry(hook, head, list) {
+               if (hook->family != b->family)
+                       continue;
+               if (hook->num != b->num)
+                       continue;
+               if (hook->prio < b->prio)
+                       continue;
+
+               list_add(&b->list, &hook->list);
+               return;
+       }
+
        list_add_tail(&b->list, head);
 }
 
@@ -2016,15 +2033,19 @@ static int dump_nf_attr_chain_cb(const struct nlattr *attr, void *data)
        int type = mnl_attr_get_type(attr);
        const struct nlattr **tb = data;
 
-       if (mnl_attr_type_valid(attr, NFTA_CHAIN_MAX) < 0)
+       if (mnl_attr_type_valid(attr, NFNLA_CHAIN_MAX) < 0)
                return MNL_CB_OK;
 
        switch(type) {
-       case NFTA_CHAIN_TABLE:
-       case NFTA_CHAIN_NAME:
+       case NFNLA_CHAIN_TABLE:
+       case NFNLA_CHAIN_NAME:
                 if (mnl_attr_validate(attr, MNL_TYPE_NUL_STRING) < 0)
                         return MNL_CB_ERROR;
                break;
+       case NFNLA_CHAIN_FAMILY:
+                if (mnl_attr_validate(attr, MNL_TYPE_U8) < 0)
+                        return MNL_CB_ERROR;
+               break;
        default:
                return MNL_CB_OK;
        }
@@ -2033,11 +2054,16 @@ static int dump_nf_attr_chain_cb(const struct nlattr *attr, void *data)
        return MNL_CB_OK;
 }
 
-static int dump_nf_hooks(const struct nlmsghdr *nlh, void *data)
+struct dump_nf_hook_data {
+       struct list_head *hook_list;
+       int family;
+};
+
+static int dump_nf_hooks(const struct nlmsghdr *nlh, void *_data)
 {
        const struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh);
        struct nlattr *tb[NFNLA_HOOK_MAX + 1] = {};
-       struct list_head *head = data;
+       struct dump_nf_hook_data *data = _data;
        struct basehook *hook;
 
        /* NB: Don't check the nft generation ID, this is not
@@ -2067,22 +2093,47 @@ static int dump_nf_hooks(const struct nlmsghdr *nlh, void *data)
 
                type = ntohl(mnl_attr_get_u32(nested[NFNLA_HOOK_INFO_TYPE]));
                if (type == NFNL_HOOK_TYPE_NFTABLES) {
-                       struct nlattr *info[NFTA_CHAIN_MAX + 1] = {};
+                       struct nlattr *info[NFNLA_CHAIN_MAX + 1] = {};
                        const char *tablename, *chainname;
 
                        if (mnl_attr_parse_nested(nested[NFNLA_HOOK_INFO_DESC], dump_nf_attr_chain_cb, info) < 0)
                                return -1;
 
-                       tablename = mnl_attr_get_str(info[NFTA_CHAIN_TABLE]);
-                       chainname = mnl_attr_get_str(info[NFTA_CHAIN_NAME]);
+                       tablename = mnl_attr_get_str(info[NFNLA_CHAIN_TABLE]);
+                       chainname = mnl_attr_get_str(info[NFNLA_CHAIN_NAME]);
                        if (tablename && chainname) {
                                hook->table = xstrdup(tablename);
                                hook->chain = xstrdup(chainname);
                        }
+                       hook->chain_family = mnl_attr_get_u8(info[NFNLA_CHAIN_FAMILY]);
+               }
+       }
+       if (tb[NFNLA_HOOK_HOOKNUM])
+               hook->num = ntohl(mnl_attr_get_u32(tb[NFNLA_HOOK_HOOKNUM]));
+
+       hook->family = nfg->nfgen_family;
+
+       /* Netdev hooks potentially interfer with this family datapath. */
+       if (hook->family == NFPROTO_NETDEV) {
+               switch (data->family) {
+               case NFPROTO_IPV4:
+               case NFPROTO_IPV6:
+               case NFPROTO_INET:
+               case NFPROTO_BRIDGE:
+                       hook->family = data->family;
+                       hook->num = NF_INET_INGRESS;
+                       break;
+               case NFPROTO_ARP:
+                       if (hook->chain_family == NFPROTO_NETDEV) {
+                               hook->family = data->family;
+                               hook->num = __NF_ARP_INGRESS;
+                       }
+                       break;
                }
        }
 
-       basehook_list_add_tail(hook, head);
+       basehook_list_add_tail(hook, data->hook_list);
+
        return MNL_CB_OK;
 }
 
@@ -2102,14 +2153,17 @@ static struct nlmsghdr *nf_hook_dump_request(char *buf, uint8_t family, uint32_t
        return nlh;
 }
 
-static int __mnl_nft_dump_nf_hooks(struct netlink_ctx *ctx, uint8_t family, uint8_t hooknum, const char *devname)
+static int __mnl_nft_dump_nf_hooks(struct netlink_ctx *ctx, uint8_t query_family,
+                                  uint8_t family, uint8_t hooknum,
+                                  const char *devname,
+                                  struct list_head *hook_list)
 {
        char buf[MNL_SOCKET_BUFFER_SIZE];
-       struct basehook *hook, *tmp;
+       struct dump_nf_hook_data data = {
+               .hook_list      = hook_list,
+               .family         = query_family,
+       };
        struct nlmsghdr *nlh;
-       LIST_HEAD(hook_list);
-       FILE *fp;
-       int ret;
 
        nlh = nf_hook_dump_request(buf, family, ctx->seqnum);
        if (devname)
@@ -2117,130 +2171,219 @@ static int __mnl_nft_dump_nf_hooks(struct netlink_ctx *ctx, uint8_t family, uint
 
        mnl_attr_put_u32(nlh, NFNLA_HOOK_HOOKNUM, htonl(hooknum));
 
-       ret = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, dump_nf_hooks, &hook_list);
-       if (ret)
-               return ret;
+       return nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, dump_nf_hooks, &data);
+}
 
-       if (list_empty(&hook_list))
-               return 0;
+static void print_hooks(struct netlink_ctx *ctx, int family, struct list_head *hook_list)
+{
+       struct basehook *hook, *tmp, *prev = NULL;
+       bool same, family_in_use = false;
+       int prio;
+       FILE *fp;
 
        fp = ctx->nft->output.output_fp;
-       fprintf(fp, "family %s hook %s", family2str(family), hooknum2str(family, hooknum));
-       if (devname)
-               fprintf(fp, " device %s", devname);
 
-       fprintf(fp, " {\n");
+       list_for_each_entry_safe(hook, tmp, hook_list, list) {
+               if (hook->family == family) {
+                       family_in_use = true;
+                       break;
+               }
+       }
+
+       if (!family_in_use)
+               return;
+
+       fprintf(fp, "family %s {\n", family2str(family));
+
+       list_for_each_entry_safe(hook, tmp, hook_list, list) {
+               if (hook->family != family)
+                       continue;
+
+               if (prev) {
+                       if (prev->num == hook->num) {
+                               fprintf(fp, "\n");
+                               same = true;
+                       } else {
+                               same = false;
+                               fprintf(fp, "\n\t}\n");
+                       }
+               } else {
+                       same = false;
+               }
+               prev = hook;
 
-       list_for_each_entry_safe(hook, tmp, &hook_list, list) {
-               int prio = hook->prio;
+               if (!same) {
+                       fprintf(fp, "\thook %s {\n",
+                               hooknum2str(family, hook->num));
+               }
 
+               prio = hook->prio;
                if (prio < 0)
-                       fprintf(fp, "\t%011d", prio); /* outputs a '-' sign */
+                       fprintf(fp, "\t\t%011d", prio); /* outputs a '-' sign */
+               else if (prio == 0)
+                       fprintf(fp, "\t\t %010u", prio);
                else
-                       fprintf(fp, "\t+%010u", prio);
+                       fprintf(fp, "\t\t+%010u", prio);
 
-               if (hook->hookfn) {
+               if (hook->table && hook->chain)
+                       fprintf(fp, " chain %s %s %s", family2str(hook->chain_family), hook->table, hook->chain);
+               else if (hook->hookfn) {
                        fprintf(fp, " %s", hook->hookfn);
-                       if (hook->module_name)
-                               fprintf(fp, " [%s]", hook->module_name);
                }
+               if (hook->module_name)
+                       fprintf(fp, " [%s]", hook->module_name);
+       }
 
-               if (hook->table && hook->chain)
-                       fprintf(fp, "\t# nft table %s %s chain %s", family2str(family), hook->table, hook->chain);
+       fprintf(fp, "\n\t}\n");
+       fprintf(fp, "}\n");
+}
 
-               fprintf(fp, "\n");
-               basehook_free(hook);
+#define HOOK_FAMILY_MAX        5
+
+static uint8_t hook_family[HOOK_FAMILY_MAX] = {
+       NFPROTO_IPV4,
+       NFPROTO_IPV6,
+       NFPROTO_BRIDGE,
+       NFPROTO_ARP,
+};
+
+static int mnl_nft_dump_nf(struct netlink_ctx *ctx, int family, int hook,
+                          const char *devname, struct list_head *hook_list,
+                          int *ret)
+{
+       int i, err;
+
+       /* show ingress in first place in hook listing. */
+       err = __mnl_nft_dump_nf_hooks(ctx, family, NFPROTO_NETDEV, NF_NETDEV_INGRESS, devname, hook_list);
+       if (err < 0)
+               *ret = err;
+
+       for (i = 0; i <= NF_INET_POST_ROUTING; i++) {
+               err = __mnl_nft_dump_nf_hooks(ctx, family, family, i, devname, hook_list);
+               if (err < 0)
+                       *ret = err;
        }
 
-       fprintf(fp, "}\n");
-       return ret;
+       return err;
+}
+
+static int mnl_nft_dump_nf_arp(struct netlink_ctx *ctx, int family, int hook,
+                              const char *devname, struct list_head *hook_list,
+                              int *ret)
+{
+       int err;
+
+       /* show ingress in first place in hook listing. */
+       err = __mnl_nft_dump_nf_hooks(ctx, family, NFPROTO_NETDEV, NF_NETDEV_INGRESS, devname, hook_list);
+       if (err < 0)
+               *ret = err;
+
+       err = __mnl_nft_dump_nf_hooks(ctx, family, family, NF_ARP_IN, devname, hook_list);
+       if (err < 0)
+               *ret = err;
+       err = __mnl_nft_dump_nf_hooks(ctx, family, family, NF_ARP_OUT, devname, hook_list);
+       if (err < 0)
+               *ret = err;
+
+       return err;
+}
+
+static int mnl_nft_dump_nf_netdev(struct netlink_ctx *ctx, int family, int hook,
+                                 const char *devname, struct list_head *hook_list,
+                                 int *ret)
+{
+       int err;
+
+       err = __mnl_nft_dump_nf_hooks(ctx, family, NFPROTO_NETDEV, NF_NETDEV_INGRESS, devname, hook_list);
+       if (err < 0)
+               *ret = err;
+
+       return err;
+}
+
+static int mnl_nft_dump_nf_decnet(struct netlink_ctx *ctx, int family, int hook,
+                                 const char *devname, struct list_head *hook_list,
+                                 int *ret)
+{
+       int i, err;
+
+       /* show ingress in first place in hook listing. */
+       err = __mnl_nft_dump_nf_hooks(ctx, family, NFPROTO_NETDEV, NF_NETDEV_INGRESS, devname, hook_list);
+       if (err < 0)
+               *ret = err;
+
+#define NF_DN_NUMHOOKS         7
+       for (i = 0; i < NF_DN_NUMHOOKS; i++) {
+               err = __mnl_nft_dump_nf_hooks(ctx, family, family, i, devname, hook_list);
+               if (err < 0) {
+                       *ret = err;
+                       return err;
+               }
+       }
+
+       return err;
+}
+
+static void release_hook_list(struct list_head *hook_list)
+{
+       struct basehook *hook, *next;
+
+       list_for_each_entry_safe(hook, next, hook_list, list)
+               basehook_free(hook);
 }
 
 int mnl_nft_dump_nf_hooks(struct netlink_ctx *ctx, int family, int hook, const char *devname)
 {
+       LIST_HEAD(hook_list);
        unsigned int i;
-       int ret, err;
+       int ret;
 
        errno = 0;
        ret = 0;
 
        switch (family) {
        case NFPROTO_UNSPEC:
-               if (devname)
-                       return mnl_nft_dump_nf_hooks(ctx, NFPROTO_NETDEV, NF_INET_INGRESS, devname);
-
-               err = mnl_nft_dump_nf_hooks(ctx, NFPROTO_INET, hook, NULL);
-               if (err < 0 && errno != EPROTONOSUPPORT)
-                       ret = err;
-               err = mnl_nft_dump_nf_hooks(ctx, NFPROTO_ARP, hook, NULL);
-               if (err < 0 && errno != EPROTONOSUPPORT)
-                       ret = err;
-               err = mnl_nft_dump_nf_hooks(ctx, NFPROTO_BRIDGE, hook, NULL);
-               if (err < 0 && errno != EPROTONOSUPPORT)
-                       ret = err;
-               err = mnl_nft_dump_nf_hooks(ctx, NFPROTO_DECNET, hook, NULL);
-               if (err < 0 && errno != EPROTONOSUPPORT)
-                       ret = err;
+               mnl_nft_dump_nf(ctx, NFPROTO_IPV4, hook, devname, &hook_list, &ret);
+               mnl_nft_dump_nf(ctx, NFPROTO_IPV6, hook, devname, &hook_list, &ret);
+               mnl_nft_dump_nf(ctx, NFPROTO_BRIDGE, hook, devname, &hook_list, &ret);
+               mnl_nft_dump_nf_decnet(ctx, NFPROTO_DECNET, hook, devname, &hook_list, &ret);
                break;
        case NFPROTO_INET:
-               if (devname) {
-                       err = __mnl_nft_dump_nf_hooks(ctx, family, NF_INET_INGRESS, devname);
-                       if (err < 0)
-                               ret = err;
-               }
-
-               err = mnl_nft_dump_nf_hooks(ctx, NFPROTO_IPV4, hook, NULL);
-               if (err < 0)
-                       ret = err;
-               err = mnl_nft_dump_nf_hooks(ctx, NFPROTO_IPV6, hook, NULL);
-               if (err < 0)
-                       ret = err;
-
+               mnl_nft_dump_nf(ctx, NFPROTO_IPV4, hook, devname, &hook_list, &ret);
+               mnl_nft_dump_nf(ctx, NFPROTO_IPV6, hook, devname, &hook_list, &ret);
                break;
        case NFPROTO_IPV4:
        case NFPROTO_IPV6:
        case NFPROTO_BRIDGE:
-               if (hook >= 0)
-                       return __mnl_nft_dump_nf_hooks(ctx, family, hook, devname);
-
-               for (i = 0; i <= NF_INET_POST_ROUTING; i++) {
-                       err = __mnl_nft_dump_nf_hooks(ctx, family, i, NULL);
-                       if (err < 0)
-                               err = ret;
-               }
+               mnl_nft_dump_nf(ctx, family, hook, devname, &hook_list, &ret);
                break;
        case NFPROTO_ARP:
-               if (hook >= 0)
-                       return __mnl_nft_dump_nf_hooks(ctx, family, hook, devname);
-
-               err = __mnl_nft_dump_nf_hooks(ctx, family, NF_ARP_IN, devname);
-               if (err < 0)
-                       ret = err;
-               err = __mnl_nft_dump_nf_hooks(ctx, family, NF_ARP_OUT, devname);
-               if (err < 0)
-                       ret = err;
+               mnl_nft_dump_nf_arp(ctx, family, hook, devname, &hook_list, &ret);
                break;
        case NFPROTO_NETDEV:
-               if (hook >= 0)
-                       return __mnl_nft_dump_nf_hooks(ctx, family, hook, devname);
-
-               err = __mnl_nft_dump_nf_hooks(ctx, family, NF_INET_INGRESS, devname);
-               if (err < 0)
-                       ret = err;
+               mnl_nft_dump_nf_netdev(ctx, family, hook, devname, &hook_list, &ret);
                break;
        case NFPROTO_DECNET:
-               if (hook >= 0)
-                       return __mnl_nft_dump_nf_hooks(ctx, family, hook, devname);
-#define NF_DN_NUMHOOKS         7
-               for (i = 0; i < NF_DN_NUMHOOKS; i++) {
-                       err = __mnl_nft_dump_nf_hooks(ctx, family, i, devname);
-                       if (err < 0) {
-                               ret = err;
-                               break;
-                       }
-               }
+               mnl_nft_dump_nf_decnet(ctx, family, hook, devname, &hook_list, &ret);
+               break;
+       }
+
+       switch (family) {
+       case NFPROTO_UNSPEC:
+               for (i = 0; i < HOOK_FAMILY_MAX; i++)
+                       print_hooks(ctx, hook_family[i], &hook_list);
+               break;
+       case NFPROTO_INET:
+               print_hooks(ctx, NFPROTO_IPV4, &hook_list);
+               print_hooks(ctx, NFPROTO_IPV6, &hook_list);
+               break;
+       default:
+               print_hooks(ctx, family, &hook_list);
                break;
        }
 
+       release_hook_list(&hook_list);
+       ret = 0;
+
        return ret;
 }
index b83ac9a298f5a7f9d065919fcb8672f1e9a5c305..83f0250a87449f29cf2e6fccca89d2b8ca6bde49 100644 (file)
@@ -1474,11 +1474,7 @@ list_cmd         :       TABLE           table_spec
                        }
                        ;
 
-basehook_device_name   :       /* NULL */
-                       {
-                               $$ = NULL;
-                       }
-                       |       DEVICE STRING
+basehook_device_name   :       DEVICE STRING
                        {
                                $$ = $2;
                        }
@@ -1488,22 +1484,11 @@ basehook_spec           :       ruleset_spec
                        {
                                $$ = $1;
                        }
-                       |       ruleset_spec    STRING  basehook_device_name
+                       |       ruleset_spec    basehook_device_name
                        {
-                               const char *name = chain_hookname_lookup($2);
-
-                               if (name == NULL) {
-                                       erec_queue(error(&@2, "unknown chain hook"),
-                                                  state->msgs);
-                                       xfree($3);
-                                       YYERROR;
-                               }
-
-                               $1.chain.name = $2;
-                               $1.chain.location = @2;
-                               if ($3) {
-                                       $1.obj.name = $3;
-                                       $1.obj.location = @3;
+                               if ($2) {
+                                       $1.obj.name = $2;
+                                       $1.obj.location = @2;
                                }
                                $$ = $1;
                        }
index 877eae3cd85dc83634665f56a3132ae7e95b078a..3e59f27c69be2720c0a06a4cba91e700705c6240 100644 (file)
@@ -822,6 +822,8 @@ const char *hooknum2str(unsigned int family, unsigned int hooknum)
                        return "forward";
                case NF_ARP_OUT:
                        return "output";
+               case __NF_ARP_INGRESS:
+                       return "ingress";
                default:
                        break;
                }