@@ -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);
@@ -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
*
@@ -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);
}
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(-)