Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[linux-2.6-microblaze.git] / net / netfilter / nf_tables_api.c
index 896d4a3..f5745e4 100644 (file)
@@ -75,6 +75,7 @@ static void nft_ctx_init(struct nft_ctx *ctx,
 {
        ctx->net        = net;
        ctx->family     = family;
+       ctx->level      = 0;
        ctx->table      = table;
        ctx->chain      = chain;
        ctx->nla        = nla;
@@ -1597,7 +1598,6 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
        struct nft_base_chain *basechain;
        struct nft_stats *stats = NULL;
        struct nft_chain_hook hook;
-       const struct nlattr *name;
        struct nf_hook_ops *ops;
        struct nft_trans *trans;
        int err;
@@ -1645,12 +1645,11 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
                        return PTR_ERR(stats);
        }
 
+       err = -ENOMEM;
        trans = nft_trans_alloc(ctx, NFT_MSG_NEWCHAIN,
                                sizeof(struct nft_trans_chain));
-       if (trans == NULL) {
-               free_percpu(stats);
-               return -ENOMEM;
-       }
+       if (trans == NULL)
+               goto err;
 
        nft_trans_chain_stats(trans) = stats;
        nft_trans_chain_update(trans) = true;
@@ -1660,19 +1659,37 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
        else
                nft_trans_chain_policy(trans) = -1;
 
-       name = nla[NFTA_CHAIN_NAME];
-       if (nla[NFTA_CHAIN_HANDLE] && name) {
-               nft_trans_chain_name(trans) =
-                       nla_strdup(name, GFP_KERNEL);
-               if (!nft_trans_chain_name(trans)) {
-                       kfree(trans);
-                       free_percpu(stats);
-                       return -ENOMEM;
+       if (nla[NFTA_CHAIN_HANDLE] &&
+           nla[NFTA_CHAIN_NAME]) {
+               struct nft_trans *tmp;
+               char *name;
+
+               err = -ENOMEM;
+               name = nla_strdup(nla[NFTA_CHAIN_NAME], GFP_KERNEL);
+               if (!name)
+                       goto err;
+
+               err = -EEXIST;
+               list_for_each_entry(tmp, &ctx->net->nft.commit_list, list) {
+                       if (tmp->msg_type == NFT_MSG_NEWCHAIN &&
+                           tmp->ctx.table == table &&
+                           nft_trans_chain_update(tmp) &&
+                           nft_trans_chain_name(tmp) &&
+                           strcmp(name, nft_trans_chain_name(tmp)) == 0) {
+                               kfree(name);
+                               goto err;
+                       }
                }
+
+               nft_trans_chain_name(trans) = name;
        }
        list_add_tail(&trans->list, &ctx->net->nft.commit_list);
 
        return 0;
+err:
+       free_percpu(stats);
+       kfree(trans);
+       return err;
 }
 
 static int nf_tables_newchain(struct net *net, struct sock *nlsk,
@@ -2254,6 +2271,39 @@ done:
        return skb->len;
 }
 
+static int nf_tables_dump_rules_start(struct netlink_callback *cb)
+{
+       const struct nlattr * const *nla = cb->data;
+       struct nft_rule_dump_ctx *ctx = NULL;
+
+       if (nla[NFTA_RULE_TABLE] || nla[NFTA_RULE_CHAIN]) {
+               ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC);
+               if (!ctx)
+                       return -ENOMEM;
+
+               if (nla[NFTA_RULE_TABLE]) {
+                       ctx->table = nla_strdup(nla[NFTA_RULE_TABLE],
+                                                       GFP_ATOMIC);
+                       if (!ctx->table) {
+                               kfree(ctx);
+                               return -ENOMEM;
+                       }
+               }
+               if (nla[NFTA_RULE_CHAIN]) {
+                       ctx->chain = nla_strdup(nla[NFTA_RULE_CHAIN],
+                                               GFP_ATOMIC);
+                       if (!ctx->chain) {
+                               kfree(ctx->table);
+                               kfree(ctx);
+                               return -ENOMEM;
+                       }
+               }
+       }
+
+       cb->data = ctx;
+       return 0;
+}
+
 static int nf_tables_dump_rules_done(struct netlink_callback *cb)
 {
        struct nft_rule_dump_ctx *ctx = cb->data;
@@ -2283,38 +2333,13 @@ static int nf_tables_getrule(struct net *net, struct sock *nlsk,
 
        if (nlh->nlmsg_flags & NLM_F_DUMP) {
                struct netlink_dump_control c = {
+                       .start= nf_tables_dump_rules_start,
                        .dump = nf_tables_dump_rules,
                        .done = nf_tables_dump_rules_done,
                        .module = THIS_MODULE,
+                       .data = (void *)nla,
                };
 
-               if (nla[NFTA_RULE_TABLE] || nla[NFTA_RULE_CHAIN]) {
-                       struct nft_rule_dump_ctx *ctx;
-
-                       ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC);
-                       if (!ctx)
-                               return -ENOMEM;
-
-                       if (nla[NFTA_RULE_TABLE]) {
-                               ctx->table = nla_strdup(nla[NFTA_RULE_TABLE],
-                                                       GFP_ATOMIC);
-                               if (!ctx->table) {
-                                       kfree(ctx);
-                                       return -ENOMEM;
-                               }
-                       }
-                       if (nla[NFTA_RULE_CHAIN]) {
-                               ctx->chain = nla_strdup(nla[NFTA_RULE_CHAIN],
-                                                       GFP_ATOMIC);
-                               if (!ctx->chain) {
-                                       kfree(ctx->table);
-                                       kfree(ctx);
-                                       return -ENOMEM;
-                               }
-                       }
-                       c.data = ctx;
-               }
-
                return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
        }
 
@@ -2384,6 +2409,9 @@ int nft_chain_validate(const struct nft_ctx *ctx, const struct nft_chain *chain)
        struct nft_rule *rule;
        int err;
 
+       if (ctx->level == NFT_JUMP_STACK_SIZE)
+               return -EMLINK;
+
        list_for_each_entry(rule, &chain->rules, list) {
                if (!nft_is_active_next(ctx->net, rule))
                        continue;
@@ -3161,6 +3189,18 @@ done:
        return skb->len;
 }
 
+static int nf_tables_dump_sets_start(struct netlink_callback *cb)
+{
+       struct nft_ctx *ctx_dump = NULL;
+
+       ctx_dump = kmemdup(cb->data, sizeof(*ctx_dump), GFP_ATOMIC);
+       if (ctx_dump == NULL)
+               return -ENOMEM;
+
+       cb->data = ctx_dump;
+       return 0;
+}
+
 static int nf_tables_dump_sets_done(struct netlink_callback *cb)
 {
        kfree(cb->data);
@@ -3188,18 +3228,12 @@ static int nf_tables_getset(struct net *net, struct sock *nlsk,
 
        if (nlh->nlmsg_flags & NLM_F_DUMP) {
                struct netlink_dump_control c = {
+                       .start = nf_tables_dump_sets_start,
                        .dump = nf_tables_dump_sets,
                        .done = nf_tables_dump_sets_done,
+                       .data = &ctx,
                        .module = THIS_MODULE,
                };
-               struct nft_ctx *ctx_dump;
-
-               ctx_dump = kmalloc(sizeof(*ctx_dump), GFP_ATOMIC);
-               if (ctx_dump == NULL)
-                       return -ENOMEM;
-
-               *ctx_dump = ctx;
-               c.data = ctx_dump;
 
                return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
        }
@@ -3849,6 +3883,15 @@ nla_put_failure:
        return -ENOSPC;
 }
 
+static int nf_tables_dump_set_start(struct netlink_callback *cb)
+{
+       struct nft_set_dump_ctx *dump_ctx = cb->data;
+
+       cb->data = kmemdup(dump_ctx, sizeof(*dump_ctx), GFP_ATOMIC);
+
+       return cb->data ? 0 : -ENOMEM;
+}
+
 static int nf_tables_dump_set_done(struct netlink_callback *cb)
 {
        kfree(cb->data);
@@ -4002,20 +4045,17 @@ static int nf_tables_getsetelem(struct net *net, struct sock *nlsk,
 
        if (nlh->nlmsg_flags & NLM_F_DUMP) {
                struct netlink_dump_control c = {
+                       .start = nf_tables_dump_set_start,
                        .dump = nf_tables_dump_set,
                        .done = nf_tables_dump_set_done,
                        .module = THIS_MODULE,
                };
-               struct nft_set_dump_ctx *dump_ctx;
-
-               dump_ctx = kmalloc(sizeof(*dump_ctx), GFP_ATOMIC);
-               if (!dump_ctx)
-                       return -ENOMEM;
-
-               dump_ctx->set = set;
-               dump_ctx->ctx = ctx;
+               struct nft_set_dump_ctx dump_ctx = {
+                       .set = set,
+                       .ctx = ctx,
+               };
 
-               c.data = dump_ctx;
+               c.data = &dump_ctx;
                return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
        }
 
@@ -4975,38 +5015,42 @@ done:
        return skb->len;
 }
 
-static int nf_tables_dump_obj_done(struct netlink_callback *cb)
+static int nf_tables_dump_obj_start(struct netlink_callback *cb)
 {
-       struct nft_obj_filter *filter = cb->data;
+       const struct nlattr * const *nla = cb->data;
+       struct nft_obj_filter *filter = NULL;
 
-       if (filter) {
-               kfree(filter->table);
-               kfree(filter);
+       if (nla[NFTA_OBJ_TABLE] || nla[NFTA_OBJ_TYPE]) {
+               filter = kzalloc(sizeof(*filter), GFP_ATOMIC);
+               if (!filter)
+                       return -ENOMEM;
+
+               if (nla[NFTA_OBJ_TABLE]) {
+                       filter->table = nla_strdup(nla[NFTA_OBJ_TABLE], GFP_ATOMIC);
+                       if (!filter->table) {
+                               kfree(filter);
+                               return -ENOMEM;
+                       }
+               }
+
+               if (nla[NFTA_OBJ_TYPE])
+                       filter->type = ntohl(nla_get_be32(nla[NFTA_OBJ_TYPE]));
        }
 
+       cb->data = filter;
        return 0;
 }
 
-static struct nft_obj_filter *
-nft_obj_filter_alloc(const struct nlattr * const nla[])
+static int nf_tables_dump_obj_done(struct netlink_callback *cb)
 {
-       struct nft_obj_filter *filter;
-
-       filter = kzalloc(sizeof(*filter), GFP_ATOMIC);
-       if (!filter)
-               return ERR_PTR(-ENOMEM);
+       struct nft_obj_filter *filter = cb->data;
 
-       if (nla[NFTA_OBJ_TABLE]) {
-               filter->table = nla_strdup(nla[NFTA_OBJ_TABLE], GFP_ATOMIC);
-               if (!filter->table) {
-                       kfree(filter);
-                       return ERR_PTR(-ENOMEM);
-               }
+       if (filter) {
+               kfree(filter->table);
+               kfree(filter);
        }
-       if (nla[NFTA_OBJ_TYPE])
-               filter->type = ntohl(nla_get_be32(nla[NFTA_OBJ_TYPE]));
 
-       return filter;
+       return 0;
 }
 
 /* called with rcu_read_lock held */
@@ -5027,21 +5071,13 @@ static int nf_tables_getobj(struct net *net, struct sock *nlsk,
 
        if (nlh->nlmsg_flags & NLM_F_DUMP) {
                struct netlink_dump_control c = {
+                       .start = nf_tables_dump_obj_start,
                        .dump = nf_tables_dump_obj,
                        .done = nf_tables_dump_obj_done,
                        .module = THIS_MODULE,
+                       .data = (void *)nla,
                };
 
-               if (nla[NFTA_OBJ_TABLE] ||
-                   nla[NFTA_OBJ_TYPE]) {
-                       struct nft_obj_filter *filter;
-
-                       filter = nft_obj_filter_alloc(nla);
-                       if (IS_ERR(filter))
-                               return -ENOMEM;
-
-                       c.data = filter;
-               }
                return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
        }
 
@@ -5320,8 +5356,6 @@ static int nf_tables_flowtable_parse_hook(const struct nft_ctx *ctx,
                flowtable->ops[i].priv          = &flowtable->data;
                flowtable->ops[i].hook          = flowtable->data.type->hook;
                flowtable->ops[i].dev           = dev_array[i];
-               flowtable->dev_name[i]          = kstrdup(dev_array[i]->name,
-                                                         GFP_KERNEL);
        }
 
        return err;
@@ -5479,10 +5513,8 @@ static int nf_tables_newflowtable(struct net *net, struct sock *nlsk,
 err6:
        i = flowtable->ops_len;
 err5:
-       for (k = i - 1; k >= 0; k--) {
-               kfree(flowtable->dev_name[k]);
+       for (k = i - 1; k >= 0; k--)
                nf_unregister_net_hook(net, &flowtable->ops[k]);
-       }
 
        kfree(flowtable->ops);
 err4:
@@ -5581,9 +5613,10 @@ static int nf_tables_fill_flowtable_info(struct sk_buff *skb, struct net *net,
                goto nla_put_failure;
 
        for (i = 0; i < flowtable->ops_len; i++) {
-               if (flowtable->dev_name[i][0] &&
-                   nla_put_string(skb, NFTA_DEVICE_NAME,
-                                  flowtable->dev_name[i]))
+               const struct net_device *dev = READ_ONCE(flowtable->ops[i].dev);
+
+               if (dev &&
+                   nla_put_string(skb, NFTA_DEVICE_NAME, dev->name))
                        goto nla_put_failure;
        }
        nla_nest_end(skb, nest_devs);
@@ -5650,37 +5683,39 @@ done:
        return skb->len;
 }
 
-static int nf_tables_dump_flowtable_done(struct netlink_callback *cb)
+static int nf_tables_dump_flowtable_start(struct netlink_callback *cb)
 {
-       struct nft_flowtable_filter *filter = cb->data;
+       const struct nlattr * const *nla = cb->data;
+       struct nft_flowtable_filter *filter = NULL;
 
-       if (!filter)
-               return 0;
+       if (nla[NFTA_FLOWTABLE_TABLE]) {
+               filter = kzalloc(sizeof(*filter), GFP_ATOMIC);
+               if (!filter)
+                       return -ENOMEM;
 
-       kfree(filter->table);
-       kfree(filter);
+               filter->table = nla_strdup(nla[NFTA_FLOWTABLE_TABLE],
+                                          GFP_ATOMIC);
+               if (!filter->table) {
+                       kfree(filter);
+                       return -ENOMEM;
+               }
+       }
 
+       cb->data = filter;
        return 0;
 }
 
-static struct nft_flowtable_filter *
-nft_flowtable_filter_alloc(const struct nlattr * const nla[])
+static int nf_tables_dump_flowtable_done(struct netlink_callback *cb)
 {
-       struct nft_flowtable_filter *filter;
+       struct nft_flowtable_filter *filter = cb->data;
 
-       filter = kzalloc(sizeof(*filter), GFP_ATOMIC);
        if (!filter)
-               return ERR_PTR(-ENOMEM);
+               return 0;
 
-       if (nla[NFTA_FLOWTABLE_TABLE]) {
-               filter->table = nla_strdup(nla[NFTA_FLOWTABLE_TABLE],
-                                          GFP_ATOMIC);
-               if (!filter->table) {
-                       kfree(filter);
-                       return ERR_PTR(-ENOMEM);
-               }
-       }
-       return filter;
+       kfree(filter->table);
+       kfree(filter);
+
+       return 0;
 }
 
 /* called with rcu_read_lock held */
@@ -5700,20 +5735,13 @@ static int nf_tables_getflowtable(struct net *net, struct sock *nlsk,
 
        if (nlh->nlmsg_flags & NLM_F_DUMP) {
                struct netlink_dump_control c = {
+                       .start = nf_tables_dump_flowtable_start,
                        .dump = nf_tables_dump_flowtable,
                        .done = nf_tables_dump_flowtable_done,
                        .module = THIS_MODULE,
+                       .data = (void *)nla,
                };
 
-               if (nla[NFTA_FLOWTABLE_TABLE]) {
-                       struct nft_flowtable_filter *filter;
-
-                       filter = nft_flowtable_filter_alloc(nla);
-                       if (IS_ERR(filter))
-                               return -ENOMEM;
-
-                       c.data = filter;
-               }
                return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
        }
 
@@ -5783,6 +5811,7 @@ static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable)
        kfree(flowtable->name);
        flowtable->data.type->free(&flowtable->data);
        module_put(flowtable->data.type->owner);
+       kfree(flowtable);
 }
 
 static int nf_tables_fill_gen_info(struct sk_buff *skb, struct net *net,
@@ -5825,7 +5854,6 @@ static void nft_flowtable_event(unsigned long event, struct net_device *dev,
                        continue;
 
                nf_unregister_net_hook(dev_net(dev), &flowtable->ops[i]);
-               flowtable->dev_name[i][0] = '\0';
                flowtable->ops[i].dev = NULL;
                break;
        }
@@ -6086,6 +6114,9 @@ static void nft_commit_release(struct nft_trans *trans)
        case NFT_MSG_DELTABLE:
                nf_tables_table_destroy(&trans->ctx);
                break;
+       case NFT_MSG_NEWCHAIN:
+               kfree(nft_trans_chain_name(trans));
+               break;
        case NFT_MSG_DELCHAIN:
                nf_tables_chain_destroy(&trans->ctx);
                break;
@@ -6315,13 +6346,15 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
                        nf_tables_table_notify(&trans->ctx, NFT_MSG_DELTABLE);
                        break;
                case NFT_MSG_NEWCHAIN:
-                       if (nft_trans_chain_update(trans))
+                       if (nft_trans_chain_update(trans)) {
                                nft_chain_commit_update(trans);
-                       else
+                               nf_tables_chain_notify(&trans->ctx, NFT_MSG_NEWCHAIN);
+                               /* trans destroyed after rcu grace period */
+                       } else {
                                nft_clear(net, trans->ctx.chain);
-
-                       nf_tables_chain_notify(&trans->ctx, NFT_MSG_NEWCHAIN);
-                       nft_trans_destroy(trans);
+                               nf_tables_chain_notify(&trans->ctx, NFT_MSG_NEWCHAIN);
+                               nft_trans_destroy(trans);
+                       }
                        break;
                case NFT_MSG_DELCHAIN:
                        nft_chain_del(trans->ctx.chain);
@@ -6471,7 +6504,7 @@ static int __nf_tables_abort(struct net *net)
                case NFT_MSG_NEWCHAIN:
                        if (nft_trans_chain_update(trans)) {
                                free_percpu(nft_trans_chain_stats(trans));
-
+                               kfree(nft_trans_chain_name(trans));
                                nft_trans_destroy(trans);
                        } else {
                                trans->ctx.table->use--;
@@ -6837,13 +6870,6 @@ int nft_validate_register_store(const struct nft_ctx *ctx,
                        err = nf_tables_check_loops(ctx, data->verdict.chain);
                        if (err < 0)
                                return err;
-
-                       if (ctx->chain->level + 1 >
-                           data->verdict.chain->level) {
-                               if (ctx->chain->level + 1 == NFT_JUMP_STACK_SIZE)
-                                       return -EMLINK;
-                               data->verdict.chain->level = ctx->chain->level + 1;
-                       }
                }
 
                return 0;