All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH nft,v3] mnl: revisit hook listing
@ 2021-08-03 23:11 Pablo Neira Ayuso
  0 siblings, 0 replies; only message in thread
From: Pablo Neira Ayuso @ 2021-08-03 23:11 UTC (permalink / raw)
  To: netfilter-devel; +Cc: fw

Update this command to display the hook datapath for a packet depending
on its family. This includes:

- Group 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 hook is not shown.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 include/linux/netfilter/nfnetlink_hook.h |  14 +-
 include/rule.h                           |   1 +
 src/mnl.c                                | 342 ++++++++++++++++-------
 src/parser_bison.y                       |  25 +-
 src/rule.c                               |   2 +
 5 files changed, 264 insertions(+), 120 deletions(-)

diff --git a/include/linux/netfilter/nfnetlink_hook.h b/include/linux/netfilter/nfnetlink_hook.h
index d8ac8278571b..bbcd285b22e1 100644
--- a/include/linux/netfilter/nfnetlink_hook.h
+++ b/include/linux/netfilter/nfnetlink_hook.h
@@ -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 */
diff --git a/include/rule.h b/include/rule.h
index 357326a3fceb..be31695636df 100644
--- a/include/rule.h
+++ b/include/rule.h
@@ -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,
diff --git a/src/mnl.c b/src/mnl.c
index f28d6605835f..b30c27aab84a 100644
--- 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,50 @@ 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_NETDEV:
+			hook->num = NF_NETDEV_INGRESS;
+			break;
+		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, data->hook_list);
 
-	basehook_list_add_tail(hook, head);
 	return MNL_CB_OK;
 }
 
@@ -2102,14 +2156,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 +2174,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) {
-		int prio = hook->prio;
+	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;
+
+		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;
 }
diff --git a/src/parser_bison.y b/src/parser_bison.y
index b83ac9a298f5..83f0250a8744 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -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;
 			}
diff --git a/src/rule.c b/src/rule.c
index 877eae3cd85d..3e59f27c69be 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -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;
 		}
-- 
2.20.1


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2021-08-03 23:11 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-03 23:11 [PATCH nft,v3] mnl: revisit hook listing Pablo Neira Ayuso

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.