netfilter: nf_tables: use new transaction infrastructure to handle table
authorPablo Neira Ayuso <pablo@netfilter.org>
Thu, 3 Apr 2014 09:53:37 +0000 (11:53 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Mon, 19 May 2014 10:06:12 +0000 (12:06 +0200)
This patch speeds up rule-set updates and it also provides a way
to revert updates and leave things in consistent state in case that
the batch needs to be aborted.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/net/netfilter/nf_tables.h
net/netfilter/nf_tables_api.c

index 7b2361c559b571fcd1079b354902becc5cbd93c9..15bf745f198d7f89dae7bd59de06d17f52c56bd2 100644 (file)
@@ -436,6 +436,16 @@ struct nft_trans_chain {
 #define nft_trans_chain_policy(trans)  \
        (((struct nft_trans_chain *)trans->data)->policy)
 
+struct nft_trans_table {
+       bool            update;
+       bool            enable;
+};
+
+#define nft_trans_table_update(trans)  \
+       (((struct nft_trans_table *)trans->data)->update)
+#define nft_trans_table_enable(trans)  \
+       (((struct nft_trans_table *)trans->data)->enable)
+
 static inline struct nft_expr *nft_expr_first(const struct nft_rule *rule)
 {
        return (struct nft_expr *)&rule->data[0];
index d71125a0a34123102881837461b8ffec05c8b4cc..3e685dbb2921e62270239cc1ecc8250f6a0fdda9 100644 (file)
@@ -307,6 +307,9 @@ done:
        return skb->len;
 }
 
+/* Internal table flags */
+#define NFT_TABLE_INACTIVE     (1 << 15)
+
 static int nf_tables_gettable(struct sock *nlsk, struct sk_buff *skb,
                              const struct nlmsghdr *nlh,
                              const struct nlattr * const nla[])
@@ -333,6 +336,8 @@ static int nf_tables_gettable(struct sock *nlsk, struct sk_buff *skb,
        table = nf_tables_table_lookup(afi, nla[NFTA_TABLE_NAME]);
        if (IS_ERR(table))
                return PTR_ERR(table);
+       if (table->flags & NFT_TABLE_INACTIVE)
+               return -ENOENT;
 
        skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
        if (!skb2)
@@ -395,9 +400,9 @@ static void nf_tables_table_disable(const struct nft_af_info *afi,
 
 static int nf_tables_updtable(struct nft_ctx *ctx)
 {
-       const struct nfgenmsg *nfmsg = nlmsg_data(ctx->nlh);
-       int family = nfmsg->nfgen_family, ret = 0;
+       struct nft_trans *trans;
        u32 flags;
+       int ret = 0;
 
        if (!ctx->nla[NFTA_TABLE_FLAGS])
                return 0;
@@ -406,25 +411,48 @@ static int nf_tables_updtable(struct nft_ctx *ctx)
        if (flags & ~NFT_TABLE_F_DORMANT)
                return -EINVAL;
 
+       trans = nft_trans_alloc(ctx, NFT_MSG_NEWTABLE,
+                               sizeof(struct nft_trans_table));
+       if (trans == NULL)
+               return -ENOMEM;
+
        if ((flags & NFT_TABLE_F_DORMANT) &&
            !(ctx->table->flags & NFT_TABLE_F_DORMANT)) {
-               nf_tables_table_disable(ctx->afi, ctx->table);
-               ctx->table->flags |= NFT_TABLE_F_DORMANT;
+               nft_trans_table_enable(trans) = false;
        } else if (!(flags & NFT_TABLE_F_DORMANT) &&
                   ctx->table->flags & NFT_TABLE_F_DORMANT) {
                ret = nf_tables_table_enable(ctx->afi, ctx->table);
-               if (ret >= 0)
+               if (ret >= 0) {
                        ctx->table->flags &= ~NFT_TABLE_F_DORMANT;
+                       nft_trans_table_enable(trans) = true;
+               }
        }
        if (ret < 0)
                goto err;
 
-       nf_tables_table_notify(ctx->skb, ctx->nlh, ctx->table,
-                              NFT_MSG_NEWTABLE, family);
+       nft_trans_table_update(trans) = true;
+       list_add_tail(&trans->list, &ctx->net->nft.commit_list);
+       return 0;
 err:
+       nft_trans_destroy(trans);
        return ret;
 }
 
+static int nft_trans_table_add(struct nft_ctx *ctx, int msg_type)
+{
+       struct nft_trans *trans;
+
+       trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_table));
+       if (trans == NULL)
+               return -ENOMEM;
+
+       if (msg_type == NFT_MSG_NEWTABLE)
+               ctx->table->flags |= NFT_TABLE_INACTIVE;
+
+       list_add_tail(&trans->list, &ctx->net->nft.commit_list);
+       return 0;
+}
+
 static int nf_tables_newtable(struct sock *nlsk, struct sk_buff *skb,
                              const struct nlmsghdr *nlh,
                              const struct nlattr * const nla[])
@@ -437,6 +465,7 @@ static int nf_tables_newtable(struct sock *nlsk, struct sk_buff *skb,
        int family = nfmsg->nfgen_family;
        u32 flags = 0;
        struct nft_ctx ctx;
+       int err;
 
        afi = nf_tables_afinfo_lookup(net, family, true);
        if (IS_ERR(afi))
@@ -451,6 +480,8 @@ static int nf_tables_newtable(struct sock *nlsk, struct sk_buff *skb,
        }
 
        if (table != NULL) {
+               if (table->flags & NFT_TABLE_INACTIVE)
+                       return -ENOENT;
                if (nlh->nlmsg_flags & NLM_F_EXCL)
                        return -EEXIST;
                if (nlh->nlmsg_flags & NLM_F_REPLACE)
@@ -480,8 +511,14 @@ static int nf_tables_newtable(struct sock *nlsk, struct sk_buff *skb,
        INIT_LIST_HEAD(&table->sets);
        table->flags = flags;
 
+       nft_ctx_init(&ctx, skb, nlh, afi, table, NULL, nla);
+       err = nft_trans_table_add(&ctx, NFT_MSG_NEWTABLE);
+       if (err < 0) {
+               kfree(table);
+               module_put(afi->owner);
+               return err;
+       }
        list_add_tail(&table->list, &afi->tables);
-       nf_tables_table_notify(skb, nlh, table, NFT_MSG_NEWTABLE, family);
        return 0;
 }
 
@@ -493,7 +530,8 @@ static int nf_tables_deltable(struct sock *nlsk, struct sk_buff *skb,
        struct nft_af_info *afi;
        struct nft_table *table;
        struct net *net = sock_net(skb->sk);
-       int family = nfmsg->nfgen_family;
+       int family = nfmsg->nfgen_family, err;
+       struct nft_ctx ctx;
 
        afi = nf_tables_afinfo_lookup(net, family, false);
        if (IS_ERR(afi))
@@ -502,17 +540,27 @@ static int nf_tables_deltable(struct sock *nlsk, struct sk_buff *skb,
        table = nf_tables_table_lookup(afi, nla[NFTA_TABLE_NAME]);
        if (IS_ERR(table))
                return PTR_ERR(table);
+       if (table->flags & NFT_TABLE_INACTIVE)
+               return -ENOENT;
 
        if (!list_empty(&table->chains) || !list_empty(&table->sets))
                return -EBUSY;
 
+       nft_ctx_init(&ctx, skb, nlh, afi, table, NULL, nla);
+       err = nft_trans_table_add(&ctx, NFT_MSG_DELTABLE);
+       if (err < 0)
+               return err;
+
        list_del(&table->list);
-       nf_tables_table_notify(skb, nlh, table, NFT_MSG_DELTABLE, family);
-       kfree(table);
-       module_put(afi->owner);
        return 0;
 }
 
+static void nf_tables_table_destroy(struct nft_ctx *ctx)
+{
+       kfree(ctx->table);
+       module_put(ctx->afi->owner);
+}
+
 int nft_register_chain_type(const struct nf_chain_type *ctype)
 {
        int err = 0;
@@ -776,6 +824,8 @@ static int nf_tables_getchain(struct sock *nlsk, struct sk_buff *skb,
        table = nf_tables_table_lookup(afi, nla[NFTA_CHAIN_TABLE]);
        if (IS_ERR(table))
                return PTR_ERR(table);
+       if (table->flags & NFT_TABLE_INACTIVE)
+               return -ENOENT;
 
        chain = nf_tables_chain_lookup(table, nla[NFTA_CHAIN_NAME]);
        if (IS_ERR(chain))
@@ -1120,6 +1170,8 @@ static int nf_tables_delchain(struct sock *nlsk, struct sk_buff *skb,
        table = nf_tables_table_lookup(afi, nla[NFTA_CHAIN_TABLE]);
        if (IS_ERR(table))
                return PTR_ERR(table);
+       if (table->flags & NFT_TABLE_INACTIVE)
+               return -ENOENT;
 
        chain = nf_tables_chain_lookup(table, nla[NFTA_CHAIN_NAME]);
        if (IS_ERR(chain))
@@ -1573,6 +1625,8 @@ static int nf_tables_getrule(struct sock *nlsk, struct sk_buff *skb,
        table = nf_tables_table_lookup(afi, nla[NFTA_RULE_TABLE]);
        if (IS_ERR(table))
                return PTR_ERR(table);
+       if (table->flags & NFT_TABLE_INACTIVE)
+               return -ENOENT;
 
        chain = nf_tables_chain_lookup(table, nla[NFTA_RULE_CHAIN]);
        if (IS_ERR(chain))
@@ -1838,6 +1892,8 @@ static int nf_tables_delrule(struct sock *nlsk, struct sk_buff *skb,
        table = nf_tables_table_lookup(afi, nla[NFTA_RULE_TABLE]);
        if (IS_ERR(table))
                return PTR_ERR(table);
+       if (table->flags & NFT_TABLE_INACTIVE)
+               return -ENOENT;
 
        if (nla[NFTA_RULE_CHAIN]) {
                chain = nf_tables_chain_lookup(table, nla[NFTA_RULE_CHAIN]);
@@ -2004,6 +2060,8 @@ static int nft_ctx_init_from_setattr(struct nft_ctx *ctx,
                table = nf_tables_table_lookup(afi, nla[NFTA_SET_TABLE]);
                if (IS_ERR(table))
                        return PTR_ERR(table);
+               if (table->flags & NFT_TABLE_INACTIVE)
+                       return -ENOENT;
        }
 
        nft_ctx_init(ctx, skb, nlh, afi, table, NULL, nla);
@@ -2681,7 +2739,8 @@ static const struct nla_policy nft_set_elem_list_policy[NFTA_SET_ELEM_LIST_MAX +
 static int nft_ctx_init_from_elemattr(struct nft_ctx *ctx,
                                      const struct sk_buff *skb,
                                      const struct nlmsghdr *nlh,
-                                     const struct nlattr * const nla[])
+                                     const struct nlattr * const nla[],
+                                     bool trans)
 {
        const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
        struct nft_af_info *afi;
@@ -2695,6 +2754,8 @@ static int nft_ctx_init_from_elemattr(struct nft_ctx *ctx,
        table = nf_tables_table_lookup(afi, nla[NFTA_SET_ELEM_LIST_TABLE]);
        if (IS_ERR(table))
                return PTR_ERR(table);
+       if (!trans && (table->flags & NFT_TABLE_INACTIVE))
+               return -ENOENT;
 
        nft_ctx_init(ctx, skb, nlh, afi, table, NULL, nla);
        return 0;
@@ -2768,7 +2829,8 @@ static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb)
        if (err < 0)
                return err;
 
-       err = nft_ctx_init_from_elemattr(&ctx, cb->skb, cb->nlh, (void *)nla);
+       err = nft_ctx_init_from_elemattr(&ctx, cb->skb, cb->nlh, (void *)nla,
+                                        false);
        if (err < 0)
                return err;
 
@@ -2833,7 +2895,7 @@ static int nf_tables_getsetelem(struct sock *nlsk, struct sk_buff *skb,
        struct nft_ctx ctx;
        int err;
 
-       err = nft_ctx_init_from_elemattr(&ctx, skb, nlh, nla);
+       err = nft_ctx_init_from_elemattr(&ctx, skb, nlh, nla, false);
        if (err < 0)
                return err;
 
@@ -3033,7 +3095,7 @@ static int nf_tables_newsetelem(struct sock *nlsk, struct sk_buff *skb,
        struct nft_ctx ctx;
        int rem, err;
 
-       err = nft_ctx_init_from_elemattr(&ctx, skb, nlh, nla);
+       err = nft_ctx_init_from_elemattr(&ctx, skb, nlh, nla, true);
        if (err < 0)
                return err;
 
@@ -3111,7 +3173,7 @@ static int nf_tables_delsetelem(struct sock *nlsk, struct sk_buff *skb,
        struct nft_ctx ctx;
        int rem, err;
 
-       err = nft_ctx_init_from_elemattr(&ctx, skb, nlh, nla);
+       err = nft_ctx_init_from_elemattr(&ctx, skb, nlh, nla, false);
        if (err < 0)
                return err;
 
@@ -3131,7 +3193,7 @@ static int nf_tables_delsetelem(struct sock *nlsk, struct sk_buff *skb,
 
 static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = {
        [NFT_MSG_NEWTABLE] = {
-               .call           = nf_tables_newtable,
+               .call_batch     = nf_tables_newtable,
                .attr_count     = NFTA_TABLE_MAX,
                .policy         = nft_table_policy,
        },
@@ -3141,7 +3203,7 @@ static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = {
                .policy         = nft_table_policy,
        },
        [NFT_MSG_DELTABLE] = {
-               .call           = nf_tables_deltable,
+               .call_batch     = nf_tables_deltable,
                .attr_count     = NFTA_TABLE_MAX,
                .policy         = nft_table_policy,
        },
@@ -3246,6 +3308,28 @@ static int nf_tables_commit(struct sk_buff *skb)
 
        list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
                switch (trans->msg_type) {
+               case NFT_MSG_NEWTABLE:
+                       if (nft_trans_table_update(trans)) {
+                               if (!nft_trans_table_enable(trans)) {
+                                       nf_tables_table_disable(trans->ctx.afi,
+                                                               trans->ctx.table);
+                                       trans->ctx.table->flags |= NFT_TABLE_F_DORMANT;
+                               }
+                       } else {
+                               trans->ctx.table->flags &= ~NFT_TABLE_INACTIVE;
+                       }
+                       nf_tables_table_notify(trans->ctx.skb, trans->ctx.nlh,
+                                              trans->ctx.table,
+                                              NFT_MSG_NEWTABLE,
+                                              trans->ctx.afi->family);
+                       nft_trans_destroy(trans);
+                       break;
+               case NFT_MSG_DELTABLE:
+                       nf_tables_table_notify(trans->ctx.skb, trans->ctx.nlh,
+                                              trans->ctx.table,
+                                              NFT_MSG_DELTABLE,
+                                              trans->ctx.afi->family);
+                       break;
                case NFT_MSG_NEWCHAIN:
                        if (nft_trans_chain_update(trans))
                                nft_chain_commit_update(trans);
@@ -3310,6 +3394,9 @@ static int nf_tables_commit(struct sk_buff *skb)
        /* Now we can safely release unused old rules */
        list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
                switch (trans->msg_type) {
+               case NFT_MSG_DELTABLE:
+                       nf_tables_table_destroy(&trans->ctx);
+                       break;
                case NFT_MSG_DELCHAIN:
                        nf_tables_chain_destroy(trans->ctx.chain);
                        break;
@@ -3334,6 +3421,23 @@ static int nf_tables_abort(struct sk_buff *skb)
 
        list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
                switch (trans->msg_type) {
+               case NFT_MSG_NEWTABLE:
+                       if (nft_trans_table_update(trans)) {
+                               if (nft_trans_table_enable(trans)) {
+                                       nf_tables_table_disable(trans->ctx.afi,
+                                                               trans->ctx.table);
+                                       trans->ctx.table->flags |= NFT_TABLE_F_DORMANT;
+                               }
+                               nft_trans_destroy(trans);
+                       } else {
+                               list_del(&trans->ctx.table->list);
+                       }
+                       break;
+               case NFT_MSG_DELTABLE:
+                       list_add_tail(&trans->ctx.table->list,
+                                     &trans->ctx.afi->tables);
+                       nft_trans_destroy(trans);
+                       break;
                case NFT_MSG_NEWCHAIN:
                        if (nft_trans_chain_update(trans)) {
                                if (nft_trans_chain_stats(trans))
@@ -3377,6 +3481,9 @@ static int nf_tables_abort(struct sk_buff *skb)
 
        list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
                switch (trans->msg_type) {
+               case NFT_MSG_NEWTABLE:
+                       nf_tables_table_destroy(&trans->ctx);
+                       break;
                case NFT_MSG_NEWCHAIN:
                        nf_tables_chain_destroy(trans->ctx.chain);
                        break;