]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - net/netfilter/nf_tables_api.c
Merge tag 'v3.16-rc1' into i2c/for-next
[karo-tx-linux.git] / net / netfilter / nf_tables_api.c
index 3e685dbb2921e62270239cc1ecc8250f6a0fdda9..624e083125b93b8755819f868c90307f04cd74b8 100644 (file)
@@ -96,13 +96,14 @@ static void nft_ctx_init(struct nft_ctx *ctx,
                         struct nft_chain *chain,
                         const struct nlattr * const *nla)
 {
-       ctx->net   = sock_net(skb->sk);
-       ctx->skb   = skb;
-       ctx->nlh   = nlh;
-       ctx->afi   = afi;
-       ctx->table = table;
-       ctx->chain = chain;
-       ctx->nla   = nla;
+       ctx->net        = sock_net(skb->sk);
+       ctx->afi        = afi;
+       ctx->table      = table;
+       ctx->chain      = chain;
+       ctx->nla        = nla;
+       ctx->portid     = NETLINK_CB(skb).portid;
+       ctx->report     = nlmsg_report(nlh);
+       ctx->seq        = nlh->nlmsg_seq;
 }
 
 static struct nft_trans *nft_trans_alloc(struct nft_ctx *ctx, int msg_type,
@@ -190,8 +191,8 @@ nf_tables_chain_type_lookup(const struct nft_af_info *afi,
 #ifdef CONFIG_MODULES
        if (autoload) {
                nfnl_unlock(NFNL_SUBSYS_NFTABLES);
-               request_module("nft-chain-%u-%*.s", afi->family,
-                              nla_len(nla)-1, (const char *)nla_data(nla));
+               request_module("nft-chain-%u-%.*s", afi->family,
+                              nla_len(nla), (const char *)nla_data(nla));
                nfnl_lock(NFNL_SUBSYS_NFTABLES);
                type = __nf_tables_chain_type_lookup(afi->family, nla);
                if (type != NULL)
@@ -235,20 +236,13 @@ nla_put_failure:
        return -1;
 }
 
-static int nf_tables_table_notify(const struct sk_buff *oskb,
-                                 const struct nlmsghdr *nlh,
-                                 const struct nft_table *table,
-                                 int event, int family)
+static int nf_tables_table_notify(const struct nft_ctx *ctx, int event)
 {
        struct sk_buff *skb;
-       u32 portid = oskb ? NETLINK_CB(oskb).portid : 0;
-       u32 seq = nlh ? nlh->nlmsg_seq : 0;
-       struct net *net = oskb ? sock_net(oskb->sk) : &init_net;
-       bool report;
        int err;
 
-       report = nlh ? nlmsg_report(nlh) : false;
-       if (!report && !nfnetlink_has_listeners(net, NFNLGRP_NFTABLES))
+       if (!ctx->report &&
+           !nfnetlink_has_listeners(ctx->net, NFNLGRP_NFTABLES))
                return 0;
 
        err = -ENOBUFS;
@@ -256,18 +250,20 @@ static int nf_tables_table_notify(const struct sk_buff *oskb,
        if (skb == NULL)
                goto err;
 
-       err = nf_tables_fill_table_info(skb, portid, seq, event, 0,
-                                       family, table);
+       err = nf_tables_fill_table_info(skb, ctx->portid, ctx->seq, event, 0,
+                                       ctx->afi->family, ctx->table);
        if (err < 0) {
                kfree_skb(skb);
                goto err;
        }
 
-       err = nfnetlink_send(skb, net, portid, NFNLGRP_NFTABLES, report,
-                            GFP_KERNEL);
+       err = nfnetlink_send(skb, ctx->net, ctx->portid, NFNLGRP_NFTABLES,
+                            ctx->report, GFP_KERNEL);
 err:
-       if (err < 0)
-               nfnetlink_set_err(net, portid, NFNLGRP_NFTABLES, err);
+       if (err < 0) {
+               nfnetlink_set_err(ctx->net, ctx->portid, NFNLGRP_NFTABLES,
+                                 err);
+       }
        return err;
 }
 
@@ -542,8 +538,7 @@ static int nf_tables_deltable(struct sock *nlsk, struct sk_buff *skb,
                return PTR_ERR(table);
        if (table->flags & NFT_TABLE_INACTIVE)
                return -ENOENT;
-
-       if (!list_empty(&table->chains) || !list_empty(&table->sets))
+       if (table->use > 0)
                return -EBUSY;
 
        nft_ctx_init(&ctx, skb, nlh, afi, table, NULL, nla);
@@ -557,6 +552,8 @@ static int nf_tables_deltable(struct sock *nlsk, struct sk_buff *skb,
 
 static void nf_tables_table_destroy(struct nft_ctx *ctx)
 {
+       BUG_ON(ctx->table->use > 0);
+
        kfree(ctx->table);
        module_put(ctx->afi->owner);
 }
@@ -721,21 +718,13 @@ nla_put_failure:
        return -1;
 }
 
-static int nf_tables_chain_notify(const struct sk_buff *oskb,
-                                 const struct nlmsghdr *nlh,
-                                 const struct nft_table *table,
-                                 const struct nft_chain *chain,
-                                 int event, int family)
+static int nf_tables_chain_notify(const struct nft_ctx *ctx, int event)
 {
        struct sk_buff *skb;
-       u32 portid = oskb ? NETLINK_CB(oskb).portid : 0;
-       struct net *net = oskb ? sock_net(oskb->sk) : &init_net;
-       u32 seq = nlh ? nlh->nlmsg_seq : 0;
-       bool report;
        int err;
 
-       report = nlh ? nlmsg_report(nlh) : false;
-       if (!report && !nfnetlink_has_listeners(net, NFNLGRP_NFTABLES))
+       if (!ctx->report &&
+           !nfnetlink_has_listeners(ctx->net, NFNLGRP_NFTABLES))
                return 0;
 
        err = -ENOBUFS;
@@ -743,18 +732,21 @@ static int nf_tables_chain_notify(const struct sk_buff *oskb,
        if (skb == NULL)
                goto err;
 
-       err = nf_tables_fill_chain_info(skb, portid, seq, event, 0, family,
-                                       table, chain);
+       err = nf_tables_fill_chain_info(skb, ctx->portid, ctx->seq, event, 0,
+                                       ctx->afi->family, ctx->table,
+                                       ctx->chain);
        if (err < 0) {
                kfree_skb(skb);
                goto err;
        }
 
-       err = nfnetlink_send(skb, net, portid, NFNLGRP_NFTABLES, report,
-                            GFP_KERNEL);
+       err = nfnetlink_send(skb, ctx->net, ctx->portid, NFNLGRP_NFTABLES,
+                            ctx->report, GFP_KERNEL);
 err:
-       if (err < 0)
-               nfnetlink_set_err(net, portid, NFNLGRP_NFTABLES, err);
+       if (err < 0) {
+               nfnetlink_set_err(ctx->net, ctx->portid, NFNLGRP_NFTABLES,
+                                 err);
+       }
        return err;
 }
 
@@ -1137,6 +1129,7 @@ static int nf_tables_newchain(struct sock *nlsk, struct sk_buff *skb,
        if (err < 0)
                goto err2;
 
+       table->use++;
        list_add_tail(&chain->list, &table->chains);
        return 0;
 err2:
@@ -1178,7 +1171,7 @@ static int nf_tables_delchain(struct sock *nlsk, struct sk_buff *skb,
                return PTR_ERR(chain);
        if (chain->flags & NFT_CHAIN_INACTIVE)
                return -ENOENT;
-       if (!list_empty(&chain->rules) || chain->use > 0)
+       if (chain->use > 0)
                return -EBUSY;
 
        nft_ctx_init(&ctx, skb, nlh, afi, table, chain, nla);
@@ -1186,6 +1179,7 @@ static int nf_tables_delchain(struct sock *nlsk, struct sk_buff *skb,
        if (err < 0)
                return err;
 
+       table->use--;
        list_del(&chain->list);
        return 0;
 }
@@ -1475,22 +1469,15 @@ nla_put_failure:
        return -1;
 }
 
-static int nf_tables_rule_notify(const struct sk_buff *oskb,
-                                const struct nlmsghdr *nlh,
-                                const struct nft_table *table,
-                                const struct nft_chain *chain,
+static int nf_tables_rule_notify(const struct nft_ctx *ctx,
                                 const struct nft_rule *rule,
-                                int event, u32 flags, int family)
+                                int event)
 {
        struct sk_buff *skb;
-       u32 portid = NETLINK_CB(oskb).portid;
-       struct net *net = oskb ? sock_net(oskb->sk) : &init_net;
-       u32 seq = nlh->nlmsg_seq;
-       bool report;
        int err;
 
-       report = nlmsg_report(nlh);
-       if (!report && !nfnetlink_has_listeners(net, NFNLGRP_NFTABLES))
+       if (!ctx->report &&
+           !nfnetlink_has_listeners(ctx->net, NFNLGRP_NFTABLES))
                return 0;
 
        err = -ENOBUFS;
@@ -1498,18 +1485,21 @@ static int nf_tables_rule_notify(const struct sk_buff *oskb,
        if (skb == NULL)
                goto err;
 
-       err = nf_tables_fill_rule_info(skb, portid, seq, event, flags,
-                                      family, table, chain, rule);
+       err = nf_tables_fill_rule_info(skb, ctx->portid, ctx->seq, event, 0,
+                                      ctx->afi->family, ctx->table,
+                                      ctx->chain, rule);
        if (err < 0) {
                kfree_skb(skb);
                goto err;
        }
 
-       err = nfnetlink_send(skb, net, portid, NFNLGRP_NFTABLES, report,
-                            GFP_KERNEL);
+       err = nfnetlink_send(skb, ctx->net, ctx->portid, NFNLGRP_NFTABLES,
+                            ctx->report, GFP_KERNEL);
 err:
-       if (err < 0)
-               nfnetlink_set_err(net, portid, NFNLGRP_NFTABLES, err);
+       if (err < 0) {
+               nfnetlink_set_err(ctx->net, ctx->portid, NFNLGRP_NFTABLES,
+                                 err);
+       }
        return err;
 }
 
@@ -1827,6 +1817,7 @@ static int nf_tables_newrule(struct sock *nlsk, struct sk_buff *skb,
                err = -ENOMEM;
                goto err3;
        }
+       chain->use++;
        return 0;
 
 err3:
@@ -1854,6 +1845,7 @@ nf_tables_delrule_one(struct nft_ctx *ctx, struct nft_rule *rule)
                if (nft_trans_rule_add(ctx, NFT_MSG_DELRULE, rule) == NULL)
                        return -ENOMEM;
                nft_rule_disactivate_next(ctx->net, rule);
+               ctx->chain->use--;
                return 0;
        }
        return -ENOENT;
@@ -2022,7 +2014,8 @@ nft_select_set_ops(const struct nlattr * const nla[],
 
 static const struct nla_policy nft_set_policy[NFTA_SET_MAX + 1] = {
        [NFTA_SET_TABLE]                = { .type = NLA_STRING },
-       [NFTA_SET_NAME]                 = { .type = NLA_STRING },
+       [NFTA_SET_NAME]                 = { .type = NLA_STRING,
+                                           .len = IFNAMSIZ - 1 },
        [NFTA_SET_FLAGS]                = { .type = NLA_U32 },
        [NFTA_SET_KEY_TYPE]             = { .type = NLA_U32 },
        [NFTA_SET_KEY_LEN]              = { .type = NLA_U32 },
@@ -2148,8 +2141,8 @@ static int nf_tables_fill_set(struct sk_buff *skb, const struct nft_ctx *ctx,
        struct nfgenmsg *nfmsg;
        struct nlmsghdr *nlh;
        struct nlattr *desc;
-       u32 portid = NETLINK_CB(ctx->skb).portid;
-       u32 seq = ctx->nlh->nlmsg_seq;
+       u32 portid = ctx->portid;
+       u32 seq = ctx->seq;
 
        event |= NFNL_SUBSYS_NFTABLES << 8;
        nlh = nlmsg_put(skb, portid, seq, event, sizeof(struct nfgenmsg),
@@ -2198,19 +2191,18 @@ nla_put_failure:
 
 static int nf_tables_set_notify(const struct nft_ctx *ctx,
                                const struct nft_set *set,
-                               int event)
+                               int event, gfp_t gfp_flags)
 {
        struct sk_buff *skb;
-       u32 portid = NETLINK_CB(ctx->skb).portid;
-       bool report;
+       u32 portid = ctx->portid;
        int err;
 
-       report = nlmsg_report(ctx->nlh);
-       if (!report && !nfnetlink_has_listeners(ctx->net, NFNLGRP_NFTABLES))
+       if (!ctx->report &&
+           !nfnetlink_has_listeners(ctx->net, NFNLGRP_NFTABLES))
                return 0;
 
        err = -ENOBUFS;
-       skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       skb = nlmsg_new(NLMSG_GOODSIZE, gfp_flags);
        if (skb == NULL)
                goto err;
 
@@ -2220,8 +2212,8 @@ static int nf_tables_set_notify(const struct nft_ctx *ctx,
                goto err;
        }
 
-       err = nfnetlink_send(skb, ctx->net, portid, NFNLGRP_NFTABLES, report,
-                            GFP_KERNEL);
+       err = nfnetlink_send(skb, ctx->net, portid, NFNLGRP_NFTABLES,
+                            ctx->report, gfp_flags);
 err:
        if (err < 0)
                nfnetlink_set_err(ctx->net, portid, NFNLGRP_NFTABLES, err);
@@ -2601,6 +2593,7 @@ static int nf_tables_newset(struct sock *nlsk, struct sk_buff *skb,
                goto err2;
 
        list_add_tail(&set->list, &table->sets);
+       table->use++;
        return 0;
 
 err2:
@@ -2620,7 +2613,7 @@ static void nft_set_destroy(struct nft_set *set)
 static void nf_tables_set_destroy(const struct nft_ctx *ctx, struct nft_set *set)
 {
        list_del(&set->list);
-       nf_tables_set_notify(ctx, set, NFT_MSG_DELSET);
+       nf_tables_set_notify(ctx, set, NFT_MSG_DELSET, GFP_ATOMIC);
        nft_set_destroy(set);
 }
 
@@ -2655,6 +2648,7 @@ static int nf_tables_delset(struct sock *nlsk, struct sk_buff *skb,
                return err;
 
        list_del(&set->list);
+       ctx.table->use--;
        return 0;
 }
 
@@ -2963,14 +2957,12 @@ static int nf_tables_setelem_notify(const struct nft_ctx *ctx,
                                    const struct nft_set_elem *elem,
                                    int event, u16 flags)
 {
-       const struct sk_buff *oskb = ctx->skb;
-       struct net *net = sock_net(oskb->sk);
-       u32 portid = NETLINK_CB(oskb).portid;
-       bool report = nlmsg_report(ctx->nlh);
+       struct net *net = ctx->net;
+       u32 portid = ctx->portid;
        struct sk_buff *skb;
        int err;
 
-       if (!report && !nfnetlink_has_listeners(net, NFNLGRP_NFTABLES))
+       if (!ctx->report && !nfnetlink_has_listeners(net, NFNLGRP_NFTABLES))
                return 0;
 
        err = -ENOBUFS;
@@ -2985,7 +2977,7 @@ static int nf_tables_setelem_notify(const struct nft_ctx *ctx,
                goto err;
        }
 
-       err = nfnetlink_send(skb, net, portid, NFNLGRP_NFTABLES, report,
+       err = nfnetlink_send(skb, net, portid, NFNLGRP_NFTABLES, ctx->report,
                             GFP_KERNEL);
 err:
        if (err < 0)
@@ -2993,7 +2985,21 @@ err:
        return err;
 }
 
-static int nft_add_set_elem(const struct nft_ctx *ctx, struct nft_set *set,
+static struct nft_trans *nft_trans_elem_alloc(struct nft_ctx *ctx,
+                                             int msg_type,
+                                             struct nft_set *set)
+{
+       struct nft_trans *trans;
+
+       trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_elem));
+       if (trans == NULL)
+               return NULL;
+
+       nft_trans_elem_set(trans) = set;
+       return trans;
+}
+
+static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
                            const struct nlattr *attr)
 {
        struct nlattr *nla[NFTA_SET_ELEM_MAX + 1];
@@ -3001,6 +3007,7 @@ static int nft_add_set_elem(const struct nft_ctx *ctx, struct nft_set *set,
        struct nft_set_elem elem;
        struct nft_set_binding *binding;
        enum nft_registers dreg;
+       struct nft_trans *trans;
        int err;
 
        if (set->size && set->nelems == set->size)
@@ -3068,14 +3075,20 @@ static int nft_add_set_elem(const struct nft_ctx *ctx, struct nft_set *set,
                }
        }
 
+       trans = nft_trans_elem_alloc(ctx, NFT_MSG_NEWSETELEM, set);
+       if (trans == NULL)
+               goto err3;
+
        err = set->ops->insert(set, &elem);
        if (err < 0)
-               goto err3;
-       set->nelems++;
+               goto err4;
 
-       nf_tables_setelem_notify(ctx, set, &elem, NFT_MSG_NEWSETELEM, 0);
+       nft_trans_elem(trans) = elem;
+       list_add_tail(&trans->list, &ctx->net->nft.commit_list);
        return 0;
 
+err4:
+       kfree(trans);
 err3:
        if (nla[NFTA_SET_ELEM_DATA] != NULL)
                nft_data_uninit(&elem.data, d2.type);
@@ -3093,7 +3106,7 @@ static int nf_tables_newsetelem(struct sock *nlsk, struct sk_buff *skb,
        const struct nlattr *attr;
        struct nft_set *set;
        struct nft_ctx ctx;
-       int rem, err;
+       int rem, err = 0;
 
        err = nft_ctx_init_from_elemattr(&ctx, skb, nlh, nla, true);
        if (err < 0)
@@ -3115,17 +3128,20 @@ static int nf_tables_newsetelem(struct sock *nlsk, struct sk_buff *skb,
        nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) {
                err = nft_add_set_elem(&ctx, set, attr);
                if (err < 0)
-                       return err;
+                       break;
+
+               set->nelems++;
        }
-       return 0;
+       return err;
 }
 
-static int nft_del_setelem(const struct nft_ctx *ctx, struct nft_set *set,
+static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set,
                           const struct nlattr *attr)
 {
        struct nlattr *nla[NFTA_SET_ELEM_MAX + 1];
        struct nft_data_desc desc;
        struct nft_set_elem elem;
+       struct nft_trans *trans;
        int err;
 
        err = nla_parse_nested(nla, NFTA_SET_ELEM_MAX, attr,
@@ -3149,10 +3165,12 @@ static int nft_del_setelem(const struct nft_ctx *ctx, struct nft_set *set,
        if (err < 0)
                goto err2;
 
-       set->ops->remove(set, &elem);
-       set->nelems--;
+       trans = nft_trans_elem_alloc(ctx, NFT_MSG_DELSETELEM, set);
+       if (trans == NULL)
+               goto err2;
 
-       nf_tables_setelem_notify(ctx, set, &elem, NFT_MSG_DELSETELEM, 0);
+       nft_trans_elem(trans) = elem;
+       list_add_tail(&trans->list, &ctx->net->nft.commit_list);
 
        nft_data_uninit(&elem.key, NFT_DATA_VALUE);
        if (set->flags & NFT_SET_MAP)
@@ -3171,7 +3189,7 @@ static int nf_tables_delsetelem(struct sock *nlsk, struct sk_buff *skb,
        const struct nlattr *attr;
        struct nft_set *set;
        struct nft_ctx ctx;
-       int rem, err;
+       int rem, err = 0;
 
        err = nft_ctx_init_from_elemattr(&ctx, skb, nlh, nla, false);
        if (err < 0)
@@ -3186,9 +3204,11 @@ static int nf_tables_delsetelem(struct sock *nlsk, struct sk_buff *skb,
        nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) {
                err = nft_del_setelem(&ctx, set, attr);
                if (err < 0)
-                       return err;
+                       break;
+
+               set->nelems--;
        }
-       return 0;
+       return err;
 }
 
 static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = {
@@ -3290,10 +3310,35 @@ static void nft_chain_commit_update(struct nft_trans *trans)
        }
 }
 
+/* Schedule objects for release via rcu to make sure no packets are accesing
+ * removed rules.
+ */
+static void nf_tables_commit_release_rcu(struct rcu_head *rt)
+{
+       struct nft_trans *trans = container_of(rt, struct nft_trans, rcu_head);
+
+       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;
+       case NFT_MSG_DELRULE:
+               nf_tables_rule_destroy(&trans->ctx, nft_trans_rule(trans));
+               break;
+       case NFT_MSG_DELSET:
+               nft_set_destroy(nft_trans_set(trans));
+               break;
+       }
+       kfree(trans);
+}
+
 static int nf_tables_commit(struct sk_buff *skb)
 {
        struct net *net = sock_net(skb->sk);
        struct nft_trans *trans, *next;
+       struct nft_set *set;
 
        /* Bump generation counter, invalidate any dump in progress */
        net->nft.genctr++;
@@ -3318,39 +3363,23 @@ static int nf_tables_commit(struct sk_buff *skb)
                        } 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);
+                       nf_tables_table_notify(&trans->ctx, NFT_MSG_NEWTABLE);
                        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);
+                       nf_tables_table_notify(&trans->ctx, NFT_MSG_DELTABLE);
                        break;
                case NFT_MSG_NEWCHAIN:
                        if (nft_trans_chain_update(trans))
                                nft_chain_commit_update(trans);
-                       else {
+                       else
                                trans->ctx.chain->flags &= ~NFT_CHAIN_INACTIVE;
-                               trans->ctx.table->use++;
-                       }
-                       nf_tables_chain_notify(trans->ctx.skb, trans->ctx.nlh,
-                                              trans->ctx.table,
-                                              trans->ctx.chain,
-                                              NFT_MSG_NEWCHAIN,
-                                              trans->ctx.afi->family);
+
+                       nf_tables_chain_notify(&trans->ctx, NFT_MSG_NEWCHAIN);
                        nft_trans_destroy(trans);
                        break;
                case NFT_MSG_DELCHAIN:
-                       trans->ctx.table->use--;
-                       nf_tables_chain_notify(trans->ctx.skb, trans->ctx.nlh,
-                                              trans->ctx.table,
-                                              trans->ctx.chain,
-                                              NFT_MSG_DELCHAIN,
-                                              trans->ctx.afi->family);
+                       nf_tables_chain_notify(&trans->ctx, NFT_MSG_DELCHAIN);
                        if (!(trans->ctx.table->flags & NFT_TABLE_F_DORMANT) &&
                            trans->ctx.chain->flags & NFT_BASE_CHAIN) {
                                nf_unregister_hooks(nft_base_chain(trans->ctx.chain)->ops,
@@ -3359,65 +3388,92 @@ static int nf_tables_commit(struct sk_buff *skb)
                        break;
                case NFT_MSG_NEWRULE:
                        nft_rule_clear(trans->ctx.net, nft_trans_rule(trans));
-                       nf_tables_rule_notify(trans->ctx.skb, trans->ctx.nlh,
-                                             trans->ctx.table,
-                                             trans->ctx.chain,
+                       nf_tables_rule_notify(&trans->ctx,
                                              nft_trans_rule(trans),
-                                             NFT_MSG_NEWRULE, 0,
-                                             trans->ctx.afi->family);
+                                             NFT_MSG_NEWRULE);
                        nft_trans_destroy(trans);
                        break;
                case NFT_MSG_DELRULE:
                        list_del_rcu(&nft_trans_rule(trans)->list);
-                       nf_tables_rule_notify(trans->ctx.skb, trans->ctx.nlh,
-                                             trans->ctx.table,
-                                             trans->ctx.chain,
-                                             nft_trans_rule(trans), NFT_MSG_DELRULE, 0,
-                                             trans->ctx.afi->family);
+                       nf_tables_rule_notify(&trans->ctx,
+                                             nft_trans_rule(trans),
+                                             NFT_MSG_DELRULE);
                        break;
                case NFT_MSG_NEWSET:
                        nft_trans_set(trans)->flags &= ~NFT_SET_INACTIVE;
+                       /* This avoids hitting -EBUSY when deleting the table
+                        * from the transaction.
+                        */
+                       if (nft_trans_set(trans)->flags & NFT_SET_ANONYMOUS &&
+                           !list_empty(&nft_trans_set(trans)->bindings))
+                               trans->ctx.table->use--;
+
                        nf_tables_set_notify(&trans->ctx, nft_trans_set(trans),
-                                            NFT_MSG_NEWSET);
+                                            NFT_MSG_NEWSET, GFP_KERNEL);
                        nft_trans_destroy(trans);
                        break;
                case NFT_MSG_DELSET:
                        nf_tables_set_notify(&trans->ctx, nft_trans_set(trans),
-                                            NFT_MSG_DELSET);
+                                            NFT_MSG_DELSET, GFP_KERNEL);
+                       break;
+               case NFT_MSG_NEWSETELEM:
+                       nf_tables_setelem_notify(&trans->ctx,
+                                                nft_trans_elem_set(trans),
+                                                &nft_trans_elem(trans),
+                                                NFT_MSG_NEWSETELEM, 0);
+                       nft_trans_destroy(trans);
+                       break;
+               case NFT_MSG_DELSETELEM:
+                       nf_tables_setelem_notify(&trans->ctx,
+                                                nft_trans_elem_set(trans),
+                                                &nft_trans_elem(trans),
+                                                NFT_MSG_DELSETELEM, 0);
+                       set = nft_trans_elem_set(trans);
+                       set->ops->get(set, &nft_trans_elem(trans));
+                       set->ops->remove(set, &nft_trans_elem(trans));
+                       nft_trans_destroy(trans);
                        break;
                }
        }
 
-       /* Make sure we don't see any packet traversing old rules */
-       synchronize_rcu();
-
-       /* 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;
-               case NFT_MSG_DELRULE:
-                       nf_tables_rule_destroy(&trans->ctx,
-                                              nft_trans_rule(trans));
-                       break;
-               case NFT_MSG_DELSET:
-                       nft_set_destroy(nft_trans_set(trans));
-                       break;
-               }
-               nft_trans_destroy(trans);
+               list_del(&trans->list);
+               trans->ctx.nla = NULL;
+               call_rcu(&trans->rcu_head, nf_tables_commit_release_rcu);
        }
 
        return 0;
 }
 
+/* Schedule objects for release via rcu to make sure no packets are accesing
+ * aborted rules.
+ */
+static void nf_tables_abort_release_rcu(struct rcu_head *rt)
+{
+       struct nft_trans *trans = container_of(rt, struct nft_trans, rcu_head);
+
+       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;
+       case NFT_MSG_NEWRULE:
+               nf_tables_rule_destroy(&trans->ctx, nft_trans_rule(trans));
+               break;
+       case NFT_MSG_NEWSET:
+               nft_set_destroy(nft_trans_set(trans));
+               break;
+       }
+       kfree(trans);
+}
+
 static int nf_tables_abort(struct sk_buff *skb)
 {
        struct net *net = sock_net(skb->sk);
        struct nft_trans *trans, *next;
+       struct nft_set *set;
 
        list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
                switch (trans->msg_type) {
@@ -3445,6 +3501,7 @@ static int nf_tables_abort(struct sk_buff *skb)
 
                                nft_trans_destroy(trans);
                        } else {
+                               trans->ctx.table->use--;
                                list_del(&trans->ctx.chain->list);
                                if (!(trans->ctx.table->flags & NFT_TABLE_F_DORMANT) &&
                                    trans->ctx.chain->flags & NFT_BASE_CHAIN) {
@@ -3454,48 +3511,49 @@ static int nf_tables_abort(struct sk_buff *skb)
                        }
                        break;
                case NFT_MSG_DELCHAIN:
+                       trans->ctx.table->use++;
                        list_add_tail(&trans->ctx.chain->list,
                                      &trans->ctx.table->chains);
                        nft_trans_destroy(trans);
                        break;
                case NFT_MSG_NEWRULE:
+                       trans->ctx.chain->use--;
                        list_del_rcu(&nft_trans_rule(trans)->list);
                        break;
                case NFT_MSG_DELRULE:
+                       trans->ctx.chain->use++;
                        nft_rule_clear(trans->ctx.net, nft_trans_rule(trans));
                        nft_trans_destroy(trans);
                        break;
                case NFT_MSG_NEWSET:
+                       trans->ctx.table->use--;
                        list_del(&nft_trans_set(trans)->list);
                        break;
                case NFT_MSG_DELSET:
+                       trans->ctx.table->use++;
                        list_add_tail(&nft_trans_set(trans)->list,
                                      &trans->ctx.table->sets);
                        nft_trans_destroy(trans);
                        break;
-               }
-       }
-
-       /* Make sure we don't see any packet accessing aborted rules */
-       synchronize_rcu();
-
-       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;
-               case NFT_MSG_NEWRULE:
-                       nf_tables_rule_destroy(&trans->ctx,
-                                              nft_trans_rule(trans));
+               case NFT_MSG_NEWSETELEM:
+                       nft_trans_elem_set(trans)->nelems--;
+                       set = nft_trans_elem_set(trans);
+                       set->ops->get(set, &nft_trans_elem(trans));
+                       set->ops->remove(set, &nft_trans_elem(trans));
+                       nft_trans_destroy(trans);
                        break;
-               case NFT_MSG_NEWSET:
-                       nft_set_destroy(nft_trans_set(trans));
+               case NFT_MSG_DELSETELEM:
+                       nft_trans_elem_set(trans)->nelems++;
+                       nft_trans_destroy(trans);
                        break;
                }
-               nft_trans_destroy(trans);
+       }
+
+       list_for_each_entry_safe_reverse(trans, next,
+                                        &net->nft.commit_list, list) {
+               list_del(&trans->list);
+               trans->ctx.nla = NULL;
+               call_rcu(&trans->rcu_head, nf_tables_abort_release_rcu);
        }
 
        return 0;