From: Pablo Neira Ayuso <pablo@netfilter.org>
To: netfilter-devel@vger.kernel.org
Subject: [PATCH RFC 2/3] netfilter: nf_tables: add string set API
Date: Fri, 29 Jul 2022 11:31:28 +0200 [thread overview]
Message-ID: <20220729093129.3108-3-pablo@netfilter.org> (raw)
In-Reply-To: <20220729093129.3108-1-pablo@netfilter.org>
This patch adds 6 new netlink commands for the nftables API:
- NFT_MSG_NEWSTRSET, to create a new string set.
- NFT_MSG_DELSTRSET, to delete an existing string set.
- NFT_MSG_GETSTRSET, to fetch the list of existing string sets.
- NFT_MSG_NEWSTRING, to add patterns to a string set.
- NFT_MSG_DELSTRING, to delete patterns to a string set.
- NFT_MSG_GETSTRING, to fetch the patterns in an existing string set.
This API uses the Aho-Corasick implementation coming in the previous
patch. String sets stores two versions of the Aho-Corasick tree: the
active tree which is currently used to search for patterns and the clone
which is used for pending updates.
Tree updates are performed under per-netns nftables mutex, the tree
pointer is protected/accessed through RCU. There is a commit_update
field that specifies that there is a updated clone to replace the stale
tree from the commit step of the 2-phase commit protocol.
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/net/netfilter/nf_tables.h | 33 +
include/uapi/linux/netfilter/nf_tables.h | 39 +
net/netfilter/nf_tables_api.c | 1286 ++++++++++++++++++----
3 files changed, 1158 insertions(+), 200 deletions(-)
diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
index 64cf655c818c..c4e9a969122a 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -1158,6 +1158,7 @@ struct nft_table {
struct list_head sets;
struct list_head objects;
struct list_head flowtables;
+ struct list_head strsets;
u64 hgenerator;
u64 handle;
u32 use;
@@ -1339,6 +1340,31 @@ void nf_tables_deactivate_flowtable(const struct nft_ctx *ctx,
void nft_register_flowtable_type(struct nf_flowtable_type *type);
void nft_unregister_flowtable_type(struct nf_flowtable_type *type);
+struct ac_tree;
+
+/**
+ * struct nft_strset - nf_tables string set
+ *
+ * @list: flow table list node in table list
+ * @table: the table the strings is contained in
+ * @name: the name of this string collection
+ * @tree: the aho-corasick tree
+ * @handle: the string collection handle
+ * @genmask: generation mask
+ * @commit_update: data structure needs update on commit
+ * @use: reference counter
+ */
+struct nft_strset {
+ struct list_head list;
+ struct nft_table *table;
+ char *name;
+ struct ac_tree __rcu *tree[2];
+ u64 handle;
+ u8 genmask:2,
+ commit_update:1;
+ u32 use;
+};
+
/**
* struct nft_traceinfo - nft tracing information and state
*
@@ -1607,6 +1633,13 @@ struct nft_trans_flowtable {
#define nft_trans_flowtable_flags(trans) \
(((struct nft_trans_flowtable *)trans->data)->flags)
+struct nft_trans_strset {
+ struct nft_strset *strset;
+};
+
+#define nft_trans_strset(trans) \
+ (((struct nft_trans_strset *)trans->data)->strset)
+
int __init nft_chain_filter_init(void);
void nft_chain_filter_fini(void);
diff --git a/include/uapi/linux/netfilter/nf_tables.h b/include/uapi/linux/netfilter/nf_tables.h
index 466fd3f4447c..d7a668199611 100644
--- a/include/uapi/linux/netfilter/nf_tables.h
+++ b/include/uapi/linux/netfilter/nf_tables.h
@@ -124,6 +124,12 @@ enum nf_tables_msg_types {
NFT_MSG_NEWFLOWTABLE,
NFT_MSG_GETFLOWTABLE,
NFT_MSG_DELFLOWTABLE,
+ NFT_MSG_NEWSTRSET,
+ NFT_MSG_DELSTRSET,
+ NFT_MSG_GETSTRSET,
+ NFT_MSG_NEWSTRING,
+ NFT_MSG_DELSTRING,
+ NFT_MSG_GETSTRING,
NFT_MSG_MAX,
};
@@ -1672,6 +1678,39 @@ enum nft_flowtable_hook_attributes {
};
#define NFTA_FLOWTABLE_HOOK_MAX (__NFTA_FLOWTABLE_HOOK_MAX - 1)
+/**
+ * enum nft_strset_hook_attributes - nf_tables string set netlink attributes
+ *
+ * @NFTA_STRSET_TABLE: table name (NLA_STRING)
+ * @NFTA_STRSET_NAME: string set name (NLA_STRING)
+ * @NFTA_STRSET_LIST: list of string elements (NLA_NESTED)
+ * @NFTA_STRSET_HANDLE: string set handle (NLA_U64)
+ * @NFTA_STRSET_USE: use reference counter (NLA_U32)
+ */
+enum nft_strset_hook_attributes {
+ NFTA_STRSET_UNSPEC,
+ NFTA_STRSET_TABLE,
+ NFTA_STRSET_NAME,
+ NFTA_STRSET_LIST,
+ NFTA_STRSET_HANDLE,
+ NFTA_STRSET_USE,
+ NFTA_STRSET_PAD,
+ __NFTA_STRSET_MAX
+};
+#define NFTA_STRSET_MAX (__NFTA_STRSET_MAX - 1)
+
+/**
+ * enum nft_string_hook_attributes - nf_tables string netlink attributes
+ *
+ * @NFTA_STRING: string (NLA_STRING)
+ */
+enum nft_string_hook_attributes {
+ NFTA_STRING_UNSPEC,
+ NFTA_STRING,
+ __NFTA_STRING_MAX
+};
+#define NFTA_STRING_MAX (__NFTA_STRING_MAX - 1)
+
/**
* enum nft_osf_attributes - nftables osf expression netlink attributes
*
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 646d5fd53604..736bdf6b9671 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -20,6 +20,7 @@
#include <net/netfilter/nf_tables_core.h>
#include <net/netfilter/nf_tables.h>
#include <net/netfilter/nf_tables_offload.h>
+#include <net/netfilter/ahocorasick.h>
#include <net/net_namespace.h>
#include <net/sock.h>
@@ -566,6 +567,24 @@ static int nft_delflowtable(struct nft_ctx *ctx,
return err;
}
+static int nft_delstrset(struct nft_ctx *ctx, struct nft_strset *strset)
+{
+ struct nft_trans *trans;
+
+ trans = nft_trans_alloc_gfp(ctx, NFT_MSG_DELSTRSET,
+ sizeof(struct nft_trans_strset),
+ GFP_KERNEL);
+ if (!trans)
+ return -ENOMEM;
+
+ nft_deactivate_next(ctx->net, strset);
+ ctx->table->use--;
+ nft_trans_strset(trans) = strset;
+ nft_trans_commit_list_add_tail(ctx->net, trans);
+
+ return 0;
+}
+
static void __nft_reg_track_clobber(struct nft_regs_track *track, u8 dreg)
{
int i;
@@ -1232,6 +1251,7 @@ static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info,
INIT_LIST_HEAD(&table->sets);
INIT_LIST_HEAD(&table->objects);
INIT_LIST_HEAD(&table->flowtables);
+ INIT_LIST_HEAD(&table->strsets);
table->family = family;
table->flags = flags;
table->handle = ++table_handle;
@@ -1260,6 +1280,7 @@ static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info,
static int nft_flush_table(struct nft_ctx *ctx)
{
struct nft_flowtable *flowtable, *nft;
+ struct nft_strset *strset, *nst;
struct nft_chain *chain, *nc;
struct nft_object *obj, *ne;
struct nft_set *set, *ns;
@@ -1301,6 +1322,15 @@ static int nft_flush_table(struct nft_ctx *ctx)
goto out;
}
+ list_for_each_entry_safe(strset, nst, &ctx->table->strsets, list) {
+ if (!nft_is_active_next(ctx->net, strset))
+ continue;
+
+ err = nft_delstrset(ctx, strset);
+ if (err < 0)
+ goto out;
+ }
+
list_for_each_entry_safe(obj, ne, &ctx->table->objects, list) {
if (!nft_is_active_next(ctx->net, obj))
continue;
@@ -8074,225 +8104,999 @@ static struct notifier_block nf_tables_flowtable_notifier = {
.notifier_call = nf_tables_flowtable_event,
};
-static void nf_tables_gen_notify(struct net *net, struct sk_buff *skb,
- int event)
-{
- struct nlmsghdr *nlh = nlmsg_hdr(skb);
- struct sk_buff *skb2;
- int err;
+/*
+ * String set
+ */
- if (!nlmsg_report(nlh) &&
- !nfnetlink_has_listeners(net, NFNLGRP_NFTABLES))
- return;
+static const struct nla_policy nft_strset_policy[NFTA_STRSET_MAX + 1] = {
+ [NFTA_STRSET_TABLE] = { .type = NLA_STRING,
+ .len = NFT_NAME_MAXLEN - 1 },
+ [NFTA_STRSET_NAME] = { .type = NLA_STRING,
+ .len = NFT_NAME_MAXLEN - 1 },
+ [NFTA_STRSET_LIST] = { .type = NLA_NESTED },
+ [NFTA_STRSET_HANDLE] = { .type = NLA_U64 },
+};
- skb2 = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
- if (skb2 == NULL)
- goto err;
+static struct nft_strset *nft_strset_lookup(const struct net *net,
+ struct nft_table *table,
+ const struct nlattr *nla,
+ u8 genmask)
+{
+ struct nft_strset *strset;
- err = nf_tables_fill_gen_info(skb2, net, NETLINK_CB(skb).portid,
- nlh->nlmsg_seq);
- if (err < 0) {
- kfree_skb(skb2);
- goto err;
+ if (nla == NULL)
+ return ERR_PTR(-EINVAL);
+
+ list_for_each_entry_rcu(strset, &table->strsets, list,
+ lockdep_is_held(&nft_net->commit_mutex)) {
+ if (!nla_strcmp(nla, strset->name) &&
+ nft_active_genmask(strset, genmask))
+ return strset;
}
- nfnetlink_send(skb2, net, NETLINK_CB(skb).portid, NFNLGRP_NFTABLES,
- nlmsg_report(nlh), GFP_KERNEL);
- return;
-err:
- nfnetlink_set_err(net, NETLINK_CB(skb).portid, NFNLGRP_NFTABLES,
- -ENOBUFS);
+ return ERR_PTR(-ENOENT);
}
-static int nf_tables_getgen(struct sk_buff *skb, const struct nfnl_info *info,
- const struct nlattr * const nla[])
+static struct nft_strset *
+nft_strset_lookup_byhandle(const struct nft_table *table, u64 handle, u8 genmask)
{
- struct sk_buff *skb2;
- int err;
-
- skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
- if (skb2 == NULL)
- return -ENOMEM;
-
- err = nf_tables_fill_gen_info(skb2, info->net, NETLINK_CB(skb).portid,
- info->nlh->nlmsg_seq);
- if (err < 0)
- goto err_fill_gen_info;
+ struct nft_strset *strset;
- return nfnetlink_unicast(skb2, info->net, NETLINK_CB(skb).portid);
+ list_for_each_entry(strset, &table->strsets, list) {
+ if (strset->handle == handle &&
+ nft_active_genmask(strset, genmask))
+ return strset;
+ }
-err_fill_gen_info:
- kfree_skb(skb2);
- return err;
+ return ERR_PTR(-ENOENT);
}
-static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = {
- [NFT_MSG_NEWTABLE] = {
- .call = nf_tables_newtable,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_TABLE_MAX,
- .policy = nft_table_policy,
- },
- [NFT_MSG_GETTABLE] = {
- .call = nf_tables_gettable,
- .type = NFNL_CB_RCU,
- .attr_count = NFTA_TABLE_MAX,
- .policy = nft_table_policy,
- },
- [NFT_MSG_DELTABLE] = {
- .call = nf_tables_deltable,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_TABLE_MAX,
- .policy = nft_table_policy,
- },
- [NFT_MSG_NEWCHAIN] = {
- .call = nf_tables_newchain,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_CHAIN_MAX,
- .policy = nft_chain_policy,
- },
- [NFT_MSG_GETCHAIN] = {
- .call = nf_tables_getchain,
- .type = NFNL_CB_RCU,
- .attr_count = NFTA_CHAIN_MAX,
- .policy = nft_chain_policy,
- },
- [NFT_MSG_DELCHAIN] = {
- .call = nf_tables_delchain,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_CHAIN_MAX,
- .policy = nft_chain_policy,
- },
- [NFT_MSG_NEWRULE] = {
- .call = nf_tables_newrule,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_RULE_MAX,
- .policy = nft_rule_policy,
- },
- [NFT_MSG_GETRULE] = {
- .call = nf_tables_getrule,
- .type = NFNL_CB_RCU,
- .attr_count = NFTA_RULE_MAX,
- .policy = nft_rule_policy,
- },
- [NFT_MSG_DELRULE] = {
- .call = nf_tables_delrule,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_RULE_MAX,
- .policy = nft_rule_policy,
- },
- [NFT_MSG_NEWSET] = {
- .call = nf_tables_newset,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_SET_MAX,
- .policy = nft_set_policy,
- },
- [NFT_MSG_GETSET] = {
- .call = nf_tables_getset,
- .type = NFNL_CB_RCU,
- .attr_count = NFTA_SET_MAX,
- .policy = nft_set_policy,
- },
- [NFT_MSG_DELSET] = {
- .call = nf_tables_delset,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_SET_MAX,
- .policy = nft_set_policy,
- },
- [NFT_MSG_NEWSETELEM] = {
- .call = nf_tables_newsetelem,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_SET_ELEM_LIST_MAX,
- .policy = nft_set_elem_list_policy,
- },
- [NFT_MSG_GETSETELEM] = {
- .call = nf_tables_getsetelem,
- .type = NFNL_CB_RCU,
- .attr_count = NFTA_SET_ELEM_LIST_MAX,
- .policy = nft_set_elem_list_policy,
- },
- [NFT_MSG_DELSETELEM] = {
- .call = nf_tables_delsetelem,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_SET_ELEM_LIST_MAX,
- .policy = nft_set_elem_list_policy,
- },
- [NFT_MSG_GETGEN] = {
- .call = nf_tables_getgen,
- .type = NFNL_CB_RCU,
- },
- [NFT_MSG_NEWOBJ] = {
- .call = nf_tables_newobj,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_OBJ_MAX,
- .policy = nft_obj_policy,
- },
- [NFT_MSG_GETOBJ] = {
- .call = nf_tables_getobj,
- .type = NFNL_CB_RCU,
- .attr_count = NFTA_OBJ_MAX,
- .policy = nft_obj_policy,
- },
- [NFT_MSG_DELOBJ] = {
- .call = nf_tables_delobj,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_OBJ_MAX,
- .policy = nft_obj_policy,
- },
- [NFT_MSG_GETOBJ_RESET] = {
- .call = nf_tables_getobj,
- .type = NFNL_CB_RCU,
- .attr_count = NFTA_OBJ_MAX,
- .policy = nft_obj_policy,
- },
- [NFT_MSG_NEWFLOWTABLE] = {
- .call = nf_tables_newflowtable,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_FLOWTABLE_MAX,
- .policy = nft_flowtable_policy,
- },
- [NFT_MSG_GETFLOWTABLE] = {
- .call = nf_tables_getflowtable,
- .type = NFNL_CB_RCU,
- .attr_count = NFTA_FLOWTABLE_MAX,
- .policy = nft_flowtable_policy,
- },
- [NFT_MSG_DELFLOWTABLE] = {
- .call = nf_tables_delflowtable,
- .type = NFNL_CB_BATCH,
- .attr_count = NFTA_FLOWTABLE_MAX,
- .policy = nft_flowtable_policy,
- },
-};
-
-static int nf_tables_validate(struct net *net)
+static int nf_tables_newstrset(struct sk_buff *skb, const struct nfnl_info *info,
+ const struct nlattr * const nla[])
{
- struct nftables_pernet *nft_net = nft_pernet(net);
+ struct nftables_pernet *nft_net = nft_pernet(info->net);
+ struct netlink_ext_ack *extack = info->extack;
+ u8 genmask = nft_genmask_next(info->net);
+ u8 family = info->nfmsg->nfgen_family;
+ struct net *net = info->net;
+ struct nft_strset *strset;
+ const struct nlattr *attr;
struct nft_table *table;
+ struct nft_trans *trans;
+ struct ac_tree *tree;
+ struct nft_ctx ctx;
+ int err;
- switch (nft_net->validate_state) {
- case NFT_VALIDATE_SKIP:
- break;
- case NFT_VALIDATE_NEED:
- nft_validate_state_update(net, NFT_VALIDATE_DO);
- fallthrough;
- case NFT_VALIDATE_DO:
- list_for_each_entry(table, &nft_net->tables, list) {
- if (nft_table_validate(net, table) < 0)
- return -EAGAIN;
+ lockdep_assert_held(&nft_net->commit_mutex);
+
+ table = nft_table_lookup(net, nla[NFTA_STRSET_TABLE], family, genmask,
+ NETLINK_CB(skb).portid);
+ if (IS_ERR(table)) {
+ NL_SET_BAD_ATTR(extack, nla[NFTA_STRSET_TABLE]);
+ return PTR_ERR(table);
+ }
+
+ if (nla[NFTA_STRSET_NAME]) {
+ attr = nla[NFTA_STRSET_NAME];
+ strset = nft_strset_lookup(net, table, attr, genmask);
+ if (IS_ERR(strset)) {
+ if (PTR_ERR(strset) != -ENOENT) {
+ NL_SET_BAD_ATTR(extack, attr);
+ return PTR_ERR(strset);
+ }
+ strset = NULL;
}
- break;
+ } else {
+ return -EINVAL;
}
- return 0;
-}
+ if (strset != NULL) {
+ if (info->nlh->nlmsg_flags & NLM_F_EXCL) {
+ NL_SET_BAD_ATTR(extack, attr);
+ return -EEXIST;
+ }
+ if (info->nlh->nlmsg_flags & NLM_F_REPLACE)
+ return -EOPNOTSUPP;
-/* a drop policy has to be deferred until all rules have been activated,
- * otherwise a large ruleset that contains a drop-policy base chain will
- * cause all packets to get dropped until the full transaction has been
- * processed.
- *
- * We defer the drop policy until the transaction has been finalized.
+ return 0;
+ }
+
+ strset = kmalloc(sizeof(*strset), GFP_KERNEL_ACCOUNT);
+ if (!strset)
+ return -ENOMEM;
+
+ strset->use = 0;
+ strset->table = table;
+
+ strset->name = nla_strdup(nla[NFTA_STRSET_NAME], GFP_KERNEL_ACCOUNT);
+ if (!strset->name) {
+ err = -ENOMEM;
+ goto err_name;
+ }
+
+ tree = ac_create();
+ if (!tree) {
+ err = -ENOMEM;
+ goto err_ac_create;
+ }
+ RCU_INIT_POINTER(strset->tree[0], tree);
+ RCU_INIT_POINTER(strset->tree[1], tree);
+
+ nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla);
+
+ trans = nft_trans_alloc_gfp(&ctx, NFT_MSG_NEWSTRSET,
+ sizeof(struct nft_trans_strset),
+ GFP_KERNEL);
+ if (!trans) {
+ err = -ENOMEM;
+ goto err_trans_alloc;
+ }
+
+ nft_trans_strset(trans) = strset;
+ nft_activate_next(info->net, strset);
+ nft_trans_commit_list_add_tail(info->net, trans);
+
+ list_add_tail_rcu(&strset->list, &table->strsets);
+ table->use++;
+
+ return 0;
+
+err_trans_alloc:
+ ac_destroy(tree);
+err_ac_create:
+ kfree(strset->name);
+err_name:
+ kfree(strset);
+
+ return err;
+}
+
+static int nft_strset_fill_info(struct sk_buff *skb, struct net *net,
+ u32 portid, u32 seq, int event, u32 flags,
+ int family, const struct nft_strset *strset)
+{
+ struct nlmsghdr *nlh;
+
+ event = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, event);
+ nlh = nfnl_msg_put(skb, portid, seq, event, flags, family,
+ NFNETLINK_V0, nft_base_seq(net));
+ if (!nlh)
+ goto nla_put_failure;
+
+ if (nla_put_string(skb, NFTA_STRSET_TABLE, strset->table->name) ||
+ nla_put_string(skb, NFTA_STRSET_NAME, strset->name) ||
+ nla_put_be32(skb, NFTA_STRSET_USE, htonl(strset->use)) ||
+ nla_put_be64(skb, NFTA_STRSET_HANDLE, cpu_to_be64(strset->handle),
+ NFTA_STRSET_PAD))
+ goto nla_put_failure;
+
+ nlmsg_end(skb, nlh);
+
+ return 0;
+
+nla_put_failure:
+ nlmsg_trim(skb, nlh);
+ return -1;
+}
+
+static int nf_tables_dump_strsets(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ const struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
+ unsigned int idx = 0, s_idx = cb->args[0];
+ struct net *net = sock_net(skb->sk);
+ int family = nfmsg->nfgen_family;
+ struct nftables_pernet *nft_net;
+ struct nft_strset *strset;
+ struct nft_table *table;
+
+ rcu_read_lock();
+ nft_net = nft_pernet(net);
+ list_for_each_entry_rcu(table, &nft_net->tables, list) {
+ if (family != NFPROTO_UNSPEC && family != table->family)
+ continue;
+ list_for_each_entry_rcu(strset, &table->strsets, list) {
+ if (idx < s_idx)
+ goto cont;
+ if (idx > s_idx)
+ memset(&cb->args[1], 0,
+ sizeof(cb->args) - sizeof(cb->args[0]));
+ if (!nft_is_active(net, strset))
+ continue;
+ if (nft_strset_fill_info(skb, net, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, NFT_MSG_NEWSTRSET, 0,
+ table->family, strset) < 0)
+ goto done;
+cont:
+ idx++;
+ }
+ }
+done:
+ rcu_read_unlock();
+ cb->args[0] = idx;
+ return skb->len;
+}
+
+static int nf_tables_getstrset(struct sk_buff *skb, const struct nfnl_info *info,
+ const struct nlattr * const nla[])
+{
+ if (nla[NFTA_STRSET_TABLE] ||
+ nla[NFTA_STRSET_NAME] ||
+ nla[NFTA_STRSET_HANDLE])
+ return -EOPNOTSUPP;
+
+ if (info->nlh->nlmsg_flags & NLM_F_DUMP) {
+ struct netlink_dump_control c = {
+ .dump = nf_tables_dump_strsets,
+ .module = THIS_MODULE,
+ };
+
+ return nft_netlink_dump_start_rcu(info->sk, skb, info->nlh, &c);
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static void nft_strset_destroy(struct nft_ctx *ctx, struct nft_strset *strset)
+{
+ struct ac_tree *tree_0, *tree_1;
+
+ tree_0 = rcu_dereference_protected(strset->tree[0],
+ lockdep_commit_lock_is_held(ctx->net));
+ tree_1 = rcu_dereference_protected(strset->tree[1],
+ lockdep_commit_lock_is_held(ctx->net));
+ ac_destroy(tree_0);
+ ac_destroy(tree_1);
+ kfree(strset->name);
+ kfree(strset);
+}
+
+static int nf_tables_delstrset(struct sk_buff *skb, const struct nfnl_info *info,
+ const struct nlattr * const nla[])
+{
+ struct nftables_pernet *nft_net = nft_pernet(info->net);
+ struct netlink_ext_ack *extack = info->extack;
+ u8 genmask = nft_genmask_next(info->net);
+ u8 family = info->nfmsg->nfgen_family;
+ struct net *net = info->net;
+ struct nft_strset *strset;
+ const struct nlattr *attr;
+ struct nft_table *table;
+ struct nft_ctx ctx;
+ u64 handle = 0;
+
+ lockdep_assert_held(&nft_net->commit_mutex);
+
+ table = nft_table_lookup(net, nla[NFTA_STRSET_TABLE], family, genmask,
+ NETLINK_CB(skb).portid);
+ if (IS_ERR(table)) {
+ NL_SET_BAD_ATTR(extack, nla[NFTA_STRSET_TABLE]);
+ return PTR_ERR(table);
+ }
+
+ if (nla[NFTA_STRSET_HANDLE]) {
+ attr = nla[NFTA_STRSET_HANDLE];
+ handle = be64_to_cpu(nla_get_be64(attr));
+ strset = nft_strset_lookup_byhandle(table, handle, genmask);
+ } else if (nla[NFTA_STRSET_NAME]) {
+ attr = nla[NFTA_STRSET_NAME];
+ strset = nft_strset_lookup(net, table, attr, genmask);
+ } else
+ return -EINVAL;
+
+ if (IS_ERR(strset)) {
+ NL_SET_BAD_ATTR(extack, attr);
+ return PTR_ERR(strset);
+ }
+
+ if (strset->use > 0) {
+ NL_SET_BAD_ATTR(extack, attr);
+ return -EBUSY;
+ }
+
+ nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla);
+
+ return nft_delstrset(&ctx, strset);
+}
+
+static const struct nla_policy nft_string_policy[NFTA_STRING_MAX + 1] = {
+ [NFTA_STRING] = { .type = NLA_STRING,
+ .len = NFT_NAME_MAXLEN - 1 },
+};
+
+static struct nlattr *nft_parse_string(struct nlattr *attr)
+{
+ struct nlattr *tb[NFTA_STRING_MAX + 1];
+ int err;
+
+ err = nla_parse_nested_deprecated(tb, NFTA_STRING_MAX, attr,
+ nft_string_policy, NULL);
+ if (err < 0)
+ return ERR_PTR(err);
+
+ if (!tb[NFTA_STRING])
+ return ERR_PTR(-EINVAL);
+
+ return tb[NFTA_STRING];
+}
+
+static int nft_string_add(struct nft_ctx *ctx, struct nft_strset *strset,
+ const struct nlattr *attr,
+ struct netlink_ext_ack *extack, bool create)
+{
+ u8 next_genbit = nft_gencursor_next(ctx->net);
+ struct nlattr *tmp, *str;
+ struct ac_tree *tree;
+ int err, rem;
+
+ nla_for_each_nested(tmp, attr, rem) {
+ err = -EINVAL;
+ if (nla_type(tmp) != NFTA_LIST_ELEM)
+ return err;
+
+ str = nft_parse_string(tmp);
+ if (IS_ERR(str)) {
+ NL_SET_BAD_ATTR(extack, tmp);
+ return PTR_ERR(str);
+ }
+
+ tree = rcu_dereference_protected(strset->tree[next_genbit],
+ lockdep_commit_lock_is_held(ctx->net));
+
+ err = ac_add(tree, nla_data(str), nla_len(str), create);
+ if (err < 0) {
+ NL_SET_BAD_ATTR(extack, tmp);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int nf_tables_newstr(struct sk_buff *skb, const struct nfnl_info *info,
+ const struct nlattr * const nla[])
+{
+ struct nftables_pernet *nft_net = nft_pernet(info->net);
+ u8 next_genbit = nft_gencursor_next(info->net);
+ struct netlink_ext_ack *extack = info->extack;
+ u8 cur_genbit = info->net->nft.gencursor;
+ u8 genmask = nft_genmask_next(info->net);
+ u8 family = info->nfmsg->nfgen_family;
+ struct ac_tree *tree, *clone;
+ struct net *net = info->net;
+ const struct nlattr *attr;
+ struct nft_strset *strset;
+ struct nft_trans *trans;
+ struct nft_table *table;
+ struct nft_ctx ctx;
+ u64 handle = 0;
+
+ lockdep_assert_held(&nft_net->commit_mutex);
+
+ table = nft_table_lookup(net, nla[NFTA_STRSET_TABLE], family, genmask,
+ NETLINK_CB(skb).portid);
+ if (IS_ERR(table)) {
+ NL_SET_BAD_ATTR(extack, nla[NFTA_STRSET_TABLE]);
+ return PTR_ERR(table);
+ }
+
+ if (nla[NFTA_STRSET_HANDLE]) {
+ attr = nla[NFTA_STRSET_HANDLE];
+ handle = be64_to_cpu(nla_get_be64(attr));
+ strset = nft_strset_lookup_byhandle(table, handle, genmask);
+ } else if (nla[NFTA_STRSET_NAME]) {
+ attr = nla[NFTA_STRSET_NAME];
+ strset = nft_strset_lookup(net, table, attr, genmask);
+ } else
+ return -EINVAL;
+
+ if (IS_ERR(strset)) {
+ NL_SET_BAD_ATTR(extack, attr);
+ return PTR_ERR(strset);
+ }
+
+ nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, NULL);
+
+ if (!strset->commit_update) {
+ tree = rcu_dereference_protected(strset->tree[cur_genbit],
+ lockdep_commit_lock_is_held(net));
+ clone = ac_clone(tree);
+ if (!clone)
+ return -ENOMEM;
+
+ rcu_assign_pointer(strset->tree[next_genbit], clone);
+ strset->commit_update = 1;
+
+ trans = nft_trans_alloc_gfp(&ctx, NFT_MSG_NEWSTRING,
+ sizeof(struct nft_trans_strset),
+ GFP_KERNEL);
+ if (!trans) {
+ ac_destroy(clone);
+ return -ENOMEM;
+ }
+
+ nft_trans_strset(trans) = strset;
+ nft_trans_commit_list_add_tail(net, trans);
+ }
+
+ return nft_string_add(&ctx, strset, nla[NFTA_STRSET_LIST],
+ extack, info->nlh->nlmsg_flags & NLM_F_EXCL);
+}
+
+static int nft_string_del(struct nft_ctx *ctx, struct nft_strset *strset,
+ const struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ u8 next_genbit = nft_gencursor_next(ctx->net);
+ struct nlattr *tmp, *str;
+ struct ac_tree *tree;
+ int err, rem;
+
+ nla_for_each_nested(tmp, attr, rem) {
+ err = -EINVAL;
+ if (nla_type(tmp) != NFTA_LIST_ELEM)
+ return err;
+
+ str = nft_parse_string(tmp);
+ if (IS_ERR(str)) {
+ NL_SET_BAD_ATTR(extack, tmp);
+ return err;
+ }
+
+ err = -E2BIG;
+ if (nla_len(str) == 0 ||
+ nla_len(str) > AC_MAXLEN) {
+ NL_SET_BAD_ATTR(extack, str);
+ return err;
+ }
+
+ tree = rcu_dereference_protected(strset->tree[next_genbit],
+ lockdep_commit_lock_is_held(ctx->net));
+ ac_del(tree, nla_data(str), nla_len(str));
+ }
+
+ return 0;
+}
+
+static int nf_tables_delstr(struct sk_buff *skb, const struct nfnl_info *info,
+ const struct nlattr * const nla[])
+{
+ struct nftables_pernet *nft_net = nft_pernet(info->net);
+ u8 next_genbit = nft_gencursor_next(info->net);
+ struct netlink_ext_ack *extack = info->extack;
+ u8 cur_genbit = info->net->nft.gencursor;
+ u8 genmask = nft_genmask_next(info->net);
+ u8 family = info->nfmsg->nfgen_family;
+ struct ac_tree *tree, *clone;
+ struct net *net = info->net;
+ const struct nlattr *attr;
+ struct nft_strset *strset;
+ struct nft_trans *trans;
+ struct nft_table *table;
+ struct nft_ctx ctx;
+ u64 handle = 0;
+
+ lockdep_assert_held(&nft_net->commit_mutex);
+
+ table = nft_table_lookup(net, nla[NFTA_STRSET_TABLE], family, genmask,
+ NETLINK_CB(skb).portid);
+ if (IS_ERR(table)) {
+ NL_SET_BAD_ATTR(extack, nla[NFTA_STRSET_TABLE]);
+ return PTR_ERR(table);
+ }
+
+ if (nla[NFTA_STRSET_HANDLE]) {
+ attr = nla[NFTA_STRSET_HANDLE];
+ handle = be64_to_cpu(nla_get_be64(attr));
+ strset = nft_strset_lookup_byhandle(table, handle, genmask);
+ } else if (nla[NFTA_STRSET_NAME]) {
+ attr = nla[NFTA_STRSET_NAME];
+ strset = nft_strset_lookup(net, table, attr, genmask);
+ } else
+ return -EINVAL;
+
+ if (IS_ERR(strset)) {
+ NL_SET_BAD_ATTR(extack, attr);
+ return PTR_ERR(strset);
+ }
+
+ nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, NULL);
+
+ if (!strset->commit_update) {
+ tree = rcu_dereference_protected(strset->tree[cur_genbit],
+ lockdep_commit_lock_is_held(net));
+ clone = ac_clone(tree);
+ if (!clone)
+ return -ENOMEM;
+
+ rcu_assign_pointer(strset->tree[next_genbit], clone);
+ strset->commit_update = 1;
+
+ trans = nft_trans_alloc_gfp(&ctx, NFT_MSG_DELSTRING,
+ sizeof(struct nft_trans_strset),
+ GFP_KERNEL);
+ if (!trans) {
+ ac_destroy(clone);
+ return -ENOMEM;
+ }
+
+ nft_trans_strset(trans) = strset;
+ nft_trans_commit_list_add_tail(net, trans);
+ }
+
+ return nft_string_del(&ctx, strset, nla[NFTA_STRSET_LIST], extack);
+}
+
+struct nft_strset_dump_ctx {
+ const struct nft_strset *strset;
+ struct ac_iter *iter;
+ struct nft_ctx ctx;
+ u32 genid;
+};
+
+static int nf_tables_dump_str_start(struct netlink_callback *cb)
+{
+ struct nft_strset_dump_ctx *dump_ctx = cb->data;
+ struct net *net = sock_net(cb->skb->sk);
+ struct nftables_pernet *nft_net;
+ struct ac_tree *tree;
+ struct ac_iter *iter;
+ u8 cur_genbit;
+
+ cur_genbit = READ_ONCE(net->nft.gencursor);
+
+ tree = rcu_dereference(dump_ctx->strset->tree[cur_genbit]);
+
+ iter = ac_iter_create(tree);
+ if (!iter)
+ return -ENOMEM;
+
+ cb->data = kmemdup(dump_ctx, sizeof(*dump_ctx), GFP_ATOMIC);
+ if (!cb->data) {
+ ac_iter_destroy(iter);
+ return -ENOMEM;
+ }
+
+ nft_net = nft_pernet(net);
+
+ dump_ctx = cb->data;
+ dump_ctx->iter = iter;
+ dump_ctx->genid = nft_net->base_seq;
+
+ return 0;
+}
+
+static int nf_tables_dump_str_done(struct netlink_callback *cb)
+{
+ struct nft_strset_dump_ctx *dump_ctx = cb->data;
+
+ ac_iter_destroy(dump_ctx->iter);
+ kfree(dump_ctx);
+
+ return 0;
+}
+
+static int nft_iter_str_cb(const char *string, void *data)
+{
+ struct sk_buff *skb = data;
+ unsigned char *b = skb_tail_pointer(skb);
+ struct nlattr *nest;
+
+ nest = nla_nest_start_noflag(skb, NFTA_LIST_ELEM);
+ if (!nest)
+ goto nla_put_failure;
+
+ if (nla_put_string(skb, NFTA_STRING, string))
+ goto nla_put_failure;
+
+ nla_nest_end(skb, nest);
+
+ return 1;
+
+nla_put_failure:
+ nlmsg_trim(skb, b);
+
+ return -1;
+}
+
+static int nft_string_fill_info(struct net *net, struct sk_buff *skb,
+ const struct nft_table *table,
+ const struct nft_strset *strset,
+ struct nft_strset_dump_ctx *dump_ctx,
+ struct netlink_callback *cb)
+{
+ struct nlmsghdr *nlh;
+ struct nlattr *nest;
+ u32 portid, seq;
+ int event, ret;
+
+ event = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWSTRING);
+ portid = NETLINK_CB(cb->skb).portid;
+ seq = cb->nlh->nlmsg_seq;
+
+ nlh = nfnl_msg_put(skb, portid, seq, event, NLM_F_MULTI,
+ table->family, NFNETLINK_V0, nft_base_seq(net));
+ if (!nlh)
+ goto nla_put_failure;
+
+ if (nla_put_string(skb, NFTA_STRSET_TABLE, table->name))
+ goto nla_put_failure;
+ if (nla_put_string(skb, NFTA_STRSET_NAME, strset->name))
+ goto nla_put_failure;
+
+ nest = nla_nest_start_noflag(skb, NFTA_STRSET_LIST);
+ if (nest == NULL)
+ goto nla_put_failure;
+
+ ret = ac_iterate(dump_ctx->iter, nft_iter_str_cb, skb);
+
+ nla_nest_end(skb, nest);
+ nlmsg_end(skb, nlh);
+
+ return ret;
+
+nla_put_failure:
+ nlmsg_trim(skb, nlh);
+
+ return -1;
+}
+
+static int nf_tables_dump_str(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ struct nft_strset_dump_ctx *dump_ctx = cb->data;
+ struct net *net = sock_net(skb->sk);
+ struct nftables_pernet *nft_net;
+ bool strset_found = false;
+ struct nft_strset *strset;
+ struct nft_table *table;
+ int ret;
+
+ if (cb->args[0])
+ return 0;
+
+ rcu_read_lock();
+ nft_net = nft_pernet(net);
+ if (dump_ctx->genid != READ_ONCE(nft_net->base_seq)) {
+ rcu_read_unlock();
+ return 0;
+ }
+
+ list_for_each_entry_rcu(table, &nft_net->tables, list) {
+ if (dump_ctx->ctx.family != NFPROTO_UNSPEC &&
+ dump_ctx->ctx.family != table->family)
+ continue;
+
+ if (table != dump_ctx->ctx.table)
+ continue;
+
+ list_for_each_entry_rcu(strset, &table->strsets, list) {
+ if (strset == dump_ctx->strset) {
+ strset_found = true;
+ break;
+ }
+ }
+ break;
+ }
+
+ if (!strset_found) {
+ rcu_read_unlock();
+ return -ENOENT;
+ }
+
+ ret = nft_string_fill_info(net, skb, table, strset, dump_ctx, cb);
+ if (ret == 1)
+ cb->args[0] = 1;
+
+ nl_dump_check_consistent(cb, nlmsg_hdr(skb));
+
+ rcu_read_unlock();
+
+ return skb->len;
+}
+
+static int nf_tables_getstr(struct sk_buff *skb, const struct nfnl_info *info,
+ const struct nlattr * const nla[])
+{
+ struct netlink_ext_ack *extack = info->extack;
+ u8 genmask = nft_genmask_cur(info->net);
+ u8 family = info->nfmsg->nfgen_family;
+ struct nft_strset_dump_ctx dump_ctx;
+ struct net *net = info->net;
+ struct nft_strset *strset;
+ struct nft_table *table;
+ struct nft_ctx ctx;
+
+ if (!nla[NFTA_STRSET_TABLE] ||
+ !nla[NFTA_STRSET_NAME])
+ return -EINVAL;
+
+ table = nft_table_lookup(net, nla[NFTA_STRSET_TABLE], family, genmask,
+ NETLINK_CB(skb).portid);
+ if (!table) {
+ NL_SET_BAD_ATTR(extack, nla[NFTA_STRSET_TABLE]);
+ return PTR_ERR(table);
+ }
+
+ strset = nft_strset_lookup(net, table, nla[NFTA_STRSET_NAME], genmask);
+ if (IS_ERR(strset)) {
+ NL_SET_BAD_ATTR(extack, nla[NFTA_STRSET_HANDLE]);
+ return PTR_ERR(strset);
+ }
+
+ nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, NULL);
+
+ dump_ctx.ctx = ctx;
+ dump_ctx.strset = strset;
+
+ if (info->nlh->nlmsg_flags & NLM_F_DUMP) {
+ struct netlink_dump_control c = {
+ .start = nf_tables_dump_str_start,
+ .dump = nf_tables_dump_str,
+ .done = nf_tables_dump_str_done,
+ .data = &dump_ctx,
+ .module = THIS_MODULE,
+ };
+
+ return nft_netlink_dump_start_rcu(info->sk, skb, info->nlh, &c);
+ }
+
+ return 0;
+}
+
+static void nf_tables_gen_notify(struct net *net, struct sk_buff *skb,
+ int event)
+{
+ struct nlmsghdr *nlh = nlmsg_hdr(skb);
+ struct sk_buff *skb2;
+ int err;
+
+ if (!nlmsg_report(nlh) &&
+ !nfnetlink_has_listeners(net, NFNLGRP_NFTABLES))
+ return;
+
+ skb2 = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (skb2 == NULL)
+ goto err;
+
+ err = nf_tables_fill_gen_info(skb2, net, NETLINK_CB(skb).portid,
+ nlh->nlmsg_seq);
+ if (err < 0) {
+ kfree_skb(skb2);
+ goto err;
+ }
+
+ nfnetlink_send(skb2, net, NETLINK_CB(skb).portid, NFNLGRP_NFTABLES,
+ nlmsg_report(nlh), GFP_KERNEL);
+ return;
+err:
+ nfnetlink_set_err(net, NETLINK_CB(skb).portid, NFNLGRP_NFTABLES,
+ -ENOBUFS);
+}
+
+static int nf_tables_getgen(struct sk_buff *skb, const struct nfnl_info *info,
+ const struct nlattr * const nla[])
+{
+ struct sk_buff *skb2;
+ int err;
+
+ skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
+ if (skb2 == NULL)
+ return -ENOMEM;
+
+ err = nf_tables_fill_gen_info(skb2, info->net, NETLINK_CB(skb).portid,
+ info->nlh->nlmsg_seq);
+ if (err < 0)
+ goto err_fill_gen_info;
+
+ return nfnetlink_unicast(skb2, info->net, NETLINK_CB(skb).portid);
+
+err_fill_gen_info:
+ kfree_skb(skb2);
+ return err;
+}
+
+static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = {
+ [NFT_MSG_NEWTABLE] = {
+ .call = nf_tables_newtable,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_TABLE_MAX,
+ .policy = nft_table_policy,
+ },
+ [NFT_MSG_GETTABLE] = {
+ .call = nf_tables_gettable,
+ .type = NFNL_CB_RCU,
+ .attr_count = NFTA_TABLE_MAX,
+ .policy = nft_table_policy,
+ },
+ [NFT_MSG_DELTABLE] = {
+ .call = nf_tables_deltable,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_TABLE_MAX,
+ .policy = nft_table_policy,
+ },
+ [NFT_MSG_NEWCHAIN] = {
+ .call = nf_tables_newchain,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_CHAIN_MAX,
+ .policy = nft_chain_policy,
+ },
+ [NFT_MSG_GETCHAIN] = {
+ .call = nf_tables_getchain,
+ .type = NFNL_CB_RCU,
+ .attr_count = NFTA_CHAIN_MAX,
+ .policy = nft_chain_policy,
+ },
+ [NFT_MSG_DELCHAIN] = {
+ .call = nf_tables_delchain,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_CHAIN_MAX,
+ .policy = nft_chain_policy,
+ },
+ [NFT_MSG_NEWRULE] = {
+ .call = nf_tables_newrule,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_RULE_MAX,
+ .policy = nft_rule_policy,
+ },
+ [NFT_MSG_GETRULE] = {
+ .call = nf_tables_getrule,
+ .type = NFNL_CB_RCU,
+ .attr_count = NFTA_RULE_MAX,
+ .policy = nft_rule_policy,
+ },
+ [NFT_MSG_DELRULE] = {
+ .call = nf_tables_delrule,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_RULE_MAX,
+ .policy = nft_rule_policy,
+ },
+ [NFT_MSG_NEWSET] = {
+ .call = nf_tables_newset,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_SET_MAX,
+ .policy = nft_set_policy,
+ },
+ [NFT_MSG_GETSET] = {
+ .call = nf_tables_getset,
+ .type = NFNL_CB_RCU,
+ .attr_count = NFTA_SET_MAX,
+ .policy = nft_set_policy,
+ },
+ [NFT_MSG_DELSET] = {
+ .call = nf_tables_delset,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_SET_MAX,
+ .policy = nft_set_policy,
+ },
+ [NFT_MSG_NEWSETELEM] = {
+ .call = nf_tables_newsetelem,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_SET_ELEM_LIST_MAX,
+ .policy = nft_set_elem_list_policy,
+ },
+ [NFT_MSG_GETSETELEM] = {
+ .call = nf_tables_getsetelem,
+ .type = NFNL_CB_RCU,
+ .attr_count = NFTA_SET_ELEM_LIST_MAX,
+ .policy = nft_set_elem_list_policy,
+ },
+ [NFT_MSG_DELSETELEM] = {
+ .call = nf_tables_delsetelem,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_SET_ELEM_LIST_MAX,
+ .policy = nft_set_elem_list_policy,
+ },
+ [NFT_MSG_GETGEN] = {
+ .call = nf_tables_getgen,
+ .type = NFNL_CB_RCU,
+ },
+ [NFT_MSG_NEWOBJ] = {
+ .call = nf_tables_newobj,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_OBJ_MAX,
+ .policy = nft_obj_policy,
+ },
+ [NFT_MSG_GETOBJ] = {
+ .call = nf_tables_getobj,
+ .type = NFNL_CB_RCU,
+ .attr_count = NFTA_OBJ_MAX,
+ .policy = nft_obj_policy,
+ },
+ [NFT_MSG_DELOBJ] = {
+ .call = nf_tables_delobj,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_OBJ_MAX,
+ .policy = nft_obj_policy,
+ },
+ [NFT_MSG_GETOBJ_RESET] = {
+ .call = nf_tables_getobj,
+ .type = NFNL_CB_RCU,
+ .attr_count = NFTA_OBJ_MAX,
+ .policy = nft_obj_policy,
+ },
+ [NFT_MSG_NEWFLOWTABLE] = {
+ .call = nf_tables_newflowtable,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_FLOWTABLE_MAX,
+ .policy = nft_flowtable_policy,
+ },
+ [NFT_MSG_GETFLOWTABLE] = {
+ .call = nf_tables_getflowtable,
+ .type = NFNL_CB_RCU,
+ .attr_count = NFTA_FLOWTABLE_MAX,
+ .policy = nft_flowtable_policy,
+ },
+ [NFT_MSG_DELFLOWTABLE] = {
+ .call = nf_tables_delflowtable,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_FLOWTABLE_MAX,
+ .policy = nft_flowtable_policy,
+ },
+ [NFT_MSG_NEWSTRSET] = {
+ .call = nf_tables_newstrset,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_STRSET_MAX,
+ .policy = nft_strset_policy,
+ },
+ [NFT_MSG_GETSTRSET] = {
+ .call = nf_tables_getstrset,
+ .type = NFNL_CB_RCU,
+ .attr_count = NFTA_STRSET_MAX,
+ .policy = nft_strset_policy,
+ },
+ [NFT_MSG_DELSTRSET] = {
+ .call = nf_tables_delstrset,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_STRSET_MAX,
+ .policy = nft_strset_policy,
+ },
+ [NFT_MSG_NEWSTRING] = {
+ .call = nf_tables_newstr,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_STRSET_MAX,
+ .policy = nft_strset_policy,
+ },
+ [NFT_MSG_GETSTRING] = {
+ .call = nf_tables_getstr,
+ .type = NFNL_CB_RCU,
+ .attr_count = NFTA_STRSET_MAX,
+ .policy = nft_strset_policy,
+ },
+ [NFT_MSG_DELSTRING] = {
+ .call = nf_tables_delstr,
+ .type = NFNL_CB_BATCH,
+ .attr_count = NFTA_STRSET_MAX,
+ .policy = nft_strset_policy,
+ },
+};
+
+static int nf_tables_validate(struct net *net)
+{
+ struct nftables_pernet *nft_net = nft_pernet(net);
+ struct nft_table *table;
+
+ switch (nft_net->validate_state) {
+ case NFT_VALIDATE_SKIP:
+ break;
+ case NFT_VALIDATE_NEED:
+ nft_validate_state_update(net, NFT_VALIDATE_DO);
+ fallthrough;
+ case NFT_VALIDATE_DO:
+ list_for_each_entry(table, &nft_net->tables, list) {
+ if (nft_table_validate(net, table) < 0)
+ return -EAGAIN;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* a drop policy has to be deferred until all rules have been activated,
+ * otherwise a large ruleset that contains a drop-policy base chain will
+ * cause all packets to get dropped until the full transaction has been
+ * processed.
+ *
+ * We defer the drop policy until the transaction has been finalized.
*/
static void nft_chain_commit_drop_policy(struct nft_trans *trans)
{
@@ -8388,6 +9192,9 @@ static void nft_commit_release(struct nft_trans *trans)
else
nf_tables_flowtable_destroy(nft_trans_flowtable(trans));
break;
+ case NFT_MSG_DELSTRSET:
+ nft_strset_destroy(&trans->ctx, nft_trans_strset(trans));
+ break;
}
if (trans->put_net)
@@ -8588,6 +9395,54 @@ static void nf_tables_commit_chain(struct net *net, struct nft_chain *chain)
nf_tables_commit_chain_free_rules_old(g0);
}
+static int nft_strset_commit_update(struct net *net)
+{
+ struct nftables_pernet *nft_net = nft_pernet(net);
+ u8 next_genbit = nft_gencursor_next(net);
+ struct nft_strset *strset;
+ struct nft_table *table;
+ struct ac_tree *tree;
+ int err;
+
+ list_for_each_entry(table, &nft_net->tables, list) {
+ list_for_each_entry(strset, &table->strsets, list) {
+ tree = rcu_dereference(strset->tree[next_genbit]);
+
+ if (!strset->commit_update) {
+ if (strset->tree[0] != strset->tree[1]) {
+ ac_destroy(tree);
+ rcu_assign_pointer(strset->tree[next_genbit],
+ strset->tree[!next_genbit]);
+ }
+ continue;
+ }
+
+ err = ac_resolve_fail(tree);
+ if (err < 0)
+ return err;
+
+ strset->commit_update = 0;
+ }
+ }
+
+ return 0;
+}
+
+static void nft_strset_abort_update(struct net *net)
+{
+ struct nftables_pernet *nft_net = nft_pernet(net);
+ struct nft_strset *strset;
+ struct nft_table *table;
+
+ list_for_each_entry(table, &nft_net->tables, list) {
+ list_for_each_entry(strset, &table->strsets, list) {
+ if (!strset->commit_update)
+ continue;
+ }
+ strset->commit_update = 0;
+ }
+}
+
static void nft_obj_del(struct nft_object *obj)
{
rhltable_remove(&nft_objname_ht, &obj->rhlhead, nft_objname_ht_params);
@@ -8766,6 +9621,10 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
if (nf_tables_validate(net) < 0)
return -EAGAIN;
+ err = nft_strset_commit_update(net);
+ if (err < 0)
+ return err;
+
err = nft_flow_rule_offload_commit(net);
if (err < 0)
return err;
@@ -8966,6 +9825,13 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
&nft_trans_flowtable(trans)->hook_list);
}
break;
+ case NFT_MSG_NEWSTRSET:
+ nft_clear(net, nft_trans_strset(trans));
+ nft_trans_destroy(trans);
+ break;
+ case NFT_MSG_DELSTRSET:
+ list_del_rcu(&nft_trans_strset(trans)->list);
+ break;
}
}
@@ -9021,6 +9887,9 @@ static void nf_tables_abort_release(struct nft_trans *trans)
else
nf_tables_flowtable_destroy(nft_trans_flowtable(trans));
break;
+ case NFT_MSG_NEWSTRSET:
+ nft_strset_destroy(&trans->ctx, nft_trans_strset(trans));
+ break;
}
kfree(trans);
}
@@ -9035,6 +9904,8 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action)
nf_tables_validate(net) < 0)
return -EAGAIN;
+ nft_strset_abort_update(net);
+
list_for_each_entry_safe_reverse(trans, next, &nft_net->commit_list,
list) {
switch (trans->msg_type) {
@@ -9168,6 +10039,15 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action)
}
nft_trans_destroy(trans);
break;
+ case NFT_MSG_NEWSTRSET:
+ trans->ctx.table->use--;
+ list_del_rcu(&nft_trans_strset(trans)->list);
+ break;
+ case NFT_MSG_DELSTRSET:
+ trans->ctx.table->use++;
+ nft_clear(trans->ctx.net, nft_trans_strset(trans));
+ nft_trans_destroy(trans);
+ break;
}
}
@@ -9833,6 +10713,7 @@ static void __nft_release_hooks(struct net *net)
static void __nft_release_table(struct net *net, struct nft_table *table)
{
struct nft_flowtable *flowtable, *nf;
+ struct nft_strset *strset, *nst;
struct nft_chain *chain, *nc;
struct nft_object *obj, *ne;
struct nft_rule *rule, *nr;
@@ -9873,6 +10754,11 @@ static void __nft_release_table(struct net *net, struct nft_table *table)
table->use--;
nf_tables_chain_destroy(&ctx);
}
+ list_for_each_entry_safe(strset, nst, &table->strsets, list) {
+ list_del(&strset->list);
+ table->use--;
+ nft_strset_destroy(&ctx, strset);
+ }
nf_tables_table_destroy(&ctx);
}
--
2.30.2
next prev parent reply other threads:[~2022-07-29 9:31 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-07-29 9:31 [PATCH RFC 0/3] nf_tables string match support Pablo Neira Ayuso
2022-07-29 9:31 ` [PATCH RFC 1/3] netfilter: add Aho-Corasick string match implementation Pablo Neira Ayuso
2022-07-29 9:31 ` Pablo Neira Ayuso [this message]
2022-07-29 14:19 ` [PATCH RFC 2/3] netfilter: nf_tables: add string set API kernel test robot
2022-07-29 16:51 ` kernel test robot
2022-07-29 9:31 ` [PATCH RFC 3/3] netfilter: nf_tables: add string expression Pablo Neira Ayuso
2022-07-29 16:31 ` kernel test robot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220729093129.3108-3-pablo@netfilter.org \
--to=pablo@netfilter.org \
--cc=netfilter-devel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.