Merge remote-tracking branch 'net-next/master'
[linux] / net / netfilter / nf_tables_api.c
index 5a92f23..5ca5ec8 100644 (file)
@@ -37,10 +37,16 @@ enum {
        NFT_VALIDATE_DO,
 };
 
+static struct rhltable nft_objname_ht;
+
 static u32 nft_chain_hash(const void *data, u32 len, u32 seed);
 static u32 nft_chain_hash_obj(const void *data, u32 len, u32 seed);
 static int nft_chain_hash_cmp(struct rhashtable_compare_arg *, const void *);
 
+static u32 nft_objname_hash(const void *data, u32 len, u32 seed);
+static u32 nft_objname_hash_obj(const void *data, u32 len, u32 seed);
+static int nft_objname_hash_cmp(struct rhashtable_compare_arg *, const void *);
+
 static const struct rhashtable_params nft_chain_ht_params = {
        .head_offset            = offsetof(struct nft_chain, rhlhead),
        .key_offset             = offsetof(struct nft_chain, name),
@@ -51,6 +57,15 @@ static const struct rhashtable_params nft_chain_ht_params = {
        .automatic_shrinking    = true,
 };
 
+static const struct rhashtable_params nft_objname_ht_params = {
+       .head_offset            = offsetof(struct nft_object, rhlhead),
+       .key_offset             = offsetof(struct nft_object, key),
+       .hashfn                 = nft_objname_hash,
+       .obj_hashfn             = nft_objname_hash_obj,
+       .obj_cmpfn              = nft_objname_hash_cmp,
+       .automatic_shrinking    = true,
+};
+
 static void nft_validate_state_update(struct net *net, u8 new_validate_state)
 {
        switch (net->nft.validate_state) {
@@ -820,6 +835,34 @@ static int nft_chain_hash_cmp(struct rhashtable_compare_arg *arg,
        return strcmp(chain->name, name);
 }
 
+static u32 nft_objname_hash(const void *data, u32 len, u32 seed)
+{
+       const struct nft_object_hash_key *k = data;
+
+       seed ^= hash_ptr(k->table, 32);
+
+       return jhash(k->name, strlen(k->name), seed);
+}
+
+static u32 nft_objname_hash_obj(const void *data, u32 len, u32 seed)
+{
+       const struct nft_object *obj = data;
+
+       return nft_objname_hash(&obj->key, 0, seed);
+}
+
+static int nft_objname_hash_cmp(struct rhashtable_compare_arg *arg,
+                               const void *ptr)
+{
+       const struct nft_object_hash_key *k = arg->key;
+       const struct nft_object *obj = ptr;
+
+       if (obj->key.table != k->table)
+               return -1;
+
+       return strcmp(obj->key.name, k->name);
+}
+
 static int nf_tables_newtable(struct net *net, struct sock *nlsk,
                              struct sk_buff *skb, const struct nlmsghdr *nlh,
                              const struct nlattr * const nla[],
@@ -1076,7 +1119,7 @@ nft_chain_lookup_byhandle(const struct nft_table *table, u64 handle, u8 genmask)
        return ERR_PTR(-ENOENT);
 }
 
-static bool lockdep_commit_lock_is_held(struct net *net)
+static bool lockdep_commit_lock_is_held(const struct net *net)
 {
 #ifdef CONFIG_PROVE_LOCKING
        return lockdep_is_held(&net->nft.commit_mutex);
@@ -2564,6 +2607,9 @@ static int nft_table_validate(struct net *net, const struct nft_table *table)
        return 0;
 }
 
+static struct nft_rule *nft_rule_lookup_byid(const struct net *net,
+                                            const struct nlattr *nla);
+
 #define NFT_RULE_MAXEXPRS      128
 
 static int nf_tables_newrule(struct net *net, struct sock *nlsk,
@@ -2633,6 +2679,12 @@ static int nf_tables_newrule(struct net *net, struct sock *nlsk,
                                NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_POSITION]);
                                return PTR_ERR(old_rule);
                        }
+               } else if (nla[NFTA_RULE_POSITION_ID]) {
+                       old_rule = nft_rule_lookup_byid(net, nla[NFTA_RULE_POSITION_ID]);
+                       if (IS_ERR(old_rule)) {
+                               NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_POSITION_ID]);
+                               return PTR_ERR(old_rule);
+                       }
                }
        }
 
@@ -3841,7 +3893,7 @@ static int nf_tables_fill_setelem(struct sk_buff *skb,
 
        if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF) &&
            nla_put_string(skb, NFTA_SET_ELEM_OBJREF,
-                          (*nft_set_ext_obj(ext))->name) < 0)
+                          (*nft_set_ext_obj(ext))->key.name) < 0)
                goto nla_put_failure;
 
        if (nft_set_ext_exists(ext, NFT_SET_EXT_FLAGS) &&
@@ -4374,7 +4426,8 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
                        err = -EINVAL;
                        goto err2;
                }
-               obj = nft_obj_lookup(ctx->table, nla[NFTA_SET_ELEM_OBJREF],
+               obj = nft_obj_lookup(ctx->net, ctx->table,
+                                    nla[NFTA_SET_ELEM_OBJREF],
                                     set->objtype, genmask);
                if (IS_ERR(obj)) {
                        err = PTR_ERR(obj);
@@ -4809,18 +4862,36 @@ void nft_unregister_obj(struct nft_object_type *obj_type)
 }
 EXPORT_SYMBOL_GPL(nft_unregister_obj);
 
-struct nft_object *nft_obj_lookup(const struct nft_table *table,
+struct nft_object *nft_obj_lookup(const struct net *net,
+                                 const struct nft_table *table,
                                  const struct nlattr *nla, u32 objtype,
                                  u8 genmask)
 {
+       struct nft_object_hash_key k = { .table = table };
+       char search[NFT_OBJ_MAXNAMELEN];
+       struct rhlist_head *tmp, *list;
        struct nft_object *obj;
 
-       list_for_each_entry_rcu(obj, &table->objects, list) {
-               if (!nla_strcmp(nla, obj->name) &&
-                   objtype == obj->ops->type->type &&
-                   nft_active_genmask(obj, genmask))
+       nla_strlcpy(search, nla, sizeof(search));
+       k.name = search;
+
+       WARN_ON_ONCE(!rcu_read_lock_held() &&
+                    !lockdep_commit_lock_is_held(net));
+
+       rcu_read_lock();
+       list = rhltable_lookup(&nft_objname_ht, &k, nft_objname_ht_params);
+       if (!list)
+               goto out;
+
+       rhl_for_each_entry_rcu(obj, tmp, list, rhlhead) {
+               if (objtype == obj->ops->type->type &&
+                   nft_active_genmask(obj, genmask)) {
+                       rcu_read_unlock();
                        return obj;
+               }
        }
+out:
+       rcu_read_unlock();
        return ERR_PTR(-ENOENT);
 }
 EXPORT_SYMBOL_GPL(nft_obj_lookup);
@@ -4978,7 +5049,7 @@ static int nf_tables_newobj(struct net *net, struct sock *nlsk,
        }
 
        objtype = ntohl(nla_get_be32(nla[NFTA_OBJ_TYPE]));
-       obj = nft_obj_lookup(table, nla[NFTA_OBJ_NAME], objtype, genmask);
+       obj = nft_obj_lookup(net, table, nla[NFTA_OBJ_NAME], objtype, genmask);
        if (IS_ERR(obj)) {
                err = PTR_ERR(obj);
                if (err != -ENOENT) {
@@ -5004,11 +5075,11 @@ static int nf_tables_newobj(struct net *net, struct sock *nlsk,
                err = PTR_ERR(obj);
                goto err1;
        }
-       obj->table = table;
+       obj->key.table = table;
        obj->handle = nf_tables_alloc_handle(table);
 
-       obj->name = nla_strdup(nla[NFTA_OBJ_NAME], GFP_KERNEL);
-       if (!obj->name) {
+       obj->key.name = nla_strdup(nla[NFTA_OBJ_NAME], GFP_KERNEL);
+       if (!obj->key.name) {
                err = -ENOMEM;
                goto err2;
        }
@@ -5017,11 +5088,20 @@ static int nf_tables_newobj(struct net *net, struct sock *nlsk,
        if (err < 0)
                goto err3;
 
+       err = rhltable_insert(&nft_objname_ht, &obj->rhlhead,
+                             nft_objname_ht_params);
+       if (err < 0)
+               goto err4;
+
        list_add_tail_rcu(&obj->list, &table->objects);
        table->use++;
        return 0;
+err4:
+       /* queued in transaction log */
+       INIT_LIST_HEAD(&obj->list);
+       return err;
 err3:
-       kfree(obj->name);
+       kfree(obj->key.name);
 err2:
        if (obj->ops->destroy)
                obj->ops->destroy(&ctx, obj);
@@ -5050,7 +5130,7 @@ static int nf_tables_fill_obj_info(struct sk_buff *skb, struct net *net,
        nfmsg->res_id           = htons(net->nft.base_seq & 0xffff);
 
        if (nla_put_string(skb, NFTA_OBJ_TABLE, table->name) ||
-           nla_put_string(skb, NFTA_OBJ_NAME, obj->name) ||
+           nla_put_string(skb, NFTA_OBJ_NAME, obj->key.name) ||
            nla_put_be32(skb, NFTA_OBJ_TYPE, htonl(obj->ops->type->type)) ||
            nla_put_be32(skb, NFTA_OBJ_USE, htonl(obj->use)) ||
            nft_object_dump(skb, NFTA_OBJ_DATA, obj, reset) ||
@@ -5205,7 +5285,7 @@ static int nf_tables_getobj(struct net *net, struct sock *nlsk,
        }
 
        objtype = ntohl(nla_get_be32(nla[NFTA_OBJ_TYPE]));
-       obj = nft_obj_lookup(table, nla[NFTA_OBJ_NAME], objtype, genmask);
+       obj = nft_obj_lookup(net, table, nla[NFTA_OBJ_NAME], objtype, genmask);
        if (IS_ERR(obj)) {
                NL_SET_BAD_ATTR(extack, nla[NFTA_OBJ_NAME]);
                return PTR_ERR(obj);
@@ -5236,7 +5316,7 @@ static void nft_obj_destroy(const struct nft_ctx *ctx, struct nft_object *obj)
                obj->ops->destroy(ctx, obj);
 
        module_put(obj->ops->type->owner);
-       kfree(obj->name);
+       kfree(obj->key.name);
        kfree(obj);
 }
 
@@ -5270,7 +5350,7 @@ static int nf_tables_delobj(struct net *net, struct sock *nlsk,
                obj = nft_obj_lookup_byhandle(table, attr, objtype, genmask);
        } else {
                attr = nla[NFTA_OBJ_NAME];
-               obj = nft_obj_lookup(table, attr, objtype, genmask);
+               obj = nft_obj_lookup(net, table, attr, objtype, genmask);
        }
 
        if (IS_ERR(obj)) {
@@ -5287,7 +5367,7 @@ static int nf_tables_delobj(struct net *net, struct sock *nlsk,
        return nft_delobj(&ctx, obj);
 }
 
-void nft_obj_notify(struct net *net, struct nft_table *table,
+void nft_obj_notify(struct net *net, const struct nft_table *table,
                    struct nft_object *obj, u32 portid, u32 seq, int event,
                    int family, int report, gfp_t gfp)
 {
@@ -6394,6 +6474,12 @@ static void nf_tables_commit_chain(struct net *net, struct nft_chain *chain)
                nf_tables_commit_chain_free_rules_old(g0);
 }
 
+static void nft_obj_del(struct nft_object *obj)
+{
+       rhltable_remove(&nft_objname_ht, &obj->rhlhead, nft_objname_ht_params);
+       list_del_rcu(&obj->list);
+}
+
 static void nft_chain_del(struct nft_chain *chain)
 {
        struct nft_table *table = chain->table;
@@ -6573,7 +6659,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
                        nft_trans_destroy(trans);
                        break;
                case NFT_MSG_DELOBJ:
-                       list_del_rcu(&nft_trans_obj(trans)->list);
+                       nft_obj_del(nft_trans_obj(trans));
                        nf_tables_obj_notify(&trans->ctx, nft_trans_obj(trans),
                                             NFT_MSG_DELOBJ);
                        break;
@@ -6713,7 +6799,7 @@ static int __nf_tables_abort(struct net *net)
                        break;
                case NFT_MSG_NEWOBJ:
                        trans->ctx.table->use--;
-                       list_del_rcu(&nft_trans_obj(trans)->list);
+                       nft_obj_del(nft_trans_obj(trans));
                        break;
                case NFT_MSG_DELOBJ:
                        trans->ctx.table->use++;
@@ -7327,7 +7413,7 @@ static void __nft_release_tables(struct net *net)
                        nft_set_destroy(set);
                }
                list_for_each_entry_safe(obj, ne, &table->objects, list) {
-                       list_del(&obj->list);
+                       nft_obj_del(obj);
                        table->use--;
                        nft_obj_destroy(&ctx, obj);
                }
@@ -7389,12 +7475,18 @@ static int __init nf_tables_module_init(void)
        if (err < 0)
                goto err3;
 
+       err = rhltable_init(&nft_objname_ht, &nft_objname_ht_params);
+       if (err < 0)
+               goto err4;
+
        /* must be last */
        err = nfnetlink_subsys_register(&nf_tables_subsys);
        if (err < 0)
-               goto err4;
+               goto err5;
 
        return err;
+err5:
+       rhltable_destroy(&nft_objname_ht);
 err4:
        unregister_netdevice_notifier(&nf_tables_flowtable_notifier);
 err3:
@@ -7414,6 +7506,7 @@ static void __exit nf_tables_module_exit(void)
        unregister_pernet_subsys(&nf_tables_net_ops);
        cancel_work_sync(&trans_destroy_work);
        rcu_barrier();
+       rhltable_destroy(&nft_objname_ht);
        nf_tables_core_module_exit();
 }