nf_tables_api.c

Created Diff never expires
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-License-Identifier: GPL-2.0-only
/*
/*
* Copyright (c) 2007-2009 Patrick McHardy <kaber@trash.net>
* Copyright (c) 2007-2009 Patrick McHardy <kaber@trash.net>
*
*
* Development of this code funded by Astaro AG (http://www.astaro.com/)
* Development of this code funded by Astaro AG (http://www.astaro.com/)
*/
*/


#include <linux/module.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/list.h>
#include <linux/skbuff.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <linux/netlink.h>
#include <linux/vmalloc.h>
#include <linux/vmalloc.h>
#include <linux/rhashtable.h>
#include <linux/rhashtable.h>
#include <linux/netfilter.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter/nf_tables.h>
#include <net/netfilter/nf_flow_table.h>
#include <net/netfilter/nf_flow_table.h>
#include <net/netfilter/nf_tables_core.h>
#include <net/netfilter/nf_tables_core.h>
#include <net/netfilter/nf_tables.h>
#include <net/netfilter/nf_tables.h>
#include <net/netfilter/nf_tables_offload.h>
#include <net/netfilter/nf_tables_offload.h>
#include <net/net_namespace.h>
#include <net/net_namespace.h>
#include <net/sock.h>
#include <net/sock.h>


#define NFT_MODULE_AUTOLOAD_LIMIT (MODULE_NAME_LEN - sizeof("nft-expr-255-"))
#define NFT_MODULE_AUTOLOAD_LIMIT (MODULE_NAME_LEN - sizeof("nft-expr-255-"))


static LIST_HEAD(nf_tables_expressions);
static LIST_HEAD(nf_tables_expressions);
static LIST_HEAD(nf_tables_objects);
static LIST_HEAD(nf_tables_objects);
static LIST_HEAD(nf_tables_flowtables);
static LIST_HEAD(nf_tables_flowtables);
static LIST_HEAD(nf_tables_destroy_list);
static LIST_HEAD(nf_tables_destroy_list);
static DEFINE_SPINLOCK(nf_tables_destroy_list_lock);
static DEFINE_SPINLOCK(nf_tables_destroy_list_lock);
static u64 table_handle;
static u64 table_handle;


enum {
enum {
NFT_VALIDATE_SKIP = 0,
NFT_VALIDATE_SKIP = 0,
NFT_VALIDATE_NEED,
NFT_VALIDATE_NEED,
NFT_VALIDATE_DO,
NFT_VALIDATE_DO,
};
};


static struct rhltable nft_objname_ht;
static struct rhltable nft_objname_ht;


static u32 nft_chain_hash(const void *data, u32 len, u32 seed);
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 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 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(const void *data, u32 len, u32 seed);
static u32 nft_objname_hash_obj(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 int nft_objname_hash_cmp(struct rhashtable_compare_arg *, const void *);


static const struct rhashtable_params nft_chain_ht_params = {
static const struct rhashtable_params nft_chain_ht_params = {
.head_offset = offsetof(struct nft_chain, rhlhead),
.head_offset = offsetof(struct nft_chain, rhlhead),
.key_offset = offsetof(struct nft_chain, name),
.key_offset = offsetof(struct nft_chain, name),
.hashfn = nft_chain_hash,
.hashfn = nft_chain_hash,
.obj_hashfn = nft_chain_hash_obj,
.obj_hashfn = nft_chain_hash_obj,
.obj_cmpfn = nft_chain_hash_cmp,
.obj_cmpfn = nft_chain_hash_cmp,
.automatic_shrinking = true,
.automatic_shrinking = true,
};
};


static const struct rhashtable_params nft_objname_ht_params = {
static const struct rhashtable_params nft_objname_ht_params = {
.head_offset = offsetof(struct nft_object, rhlhead),
.head_offset = offsetof(struct nft_object, rhlhead),
.key_offset = offsetof(struct nft_object, key),
.key_offset = offsetof(struct nft_object, key),
.hashfn = nft_objname_hash,
.hashfn = nft_objname_hash,
.obj_hashfn = nft_objname_hash_obj,
.obj_hashfn = nft_objname_hash_obj,
.obj_cmpfn = nft_objname_hash_cmp,
.obj_cmpfn = nft_objname_hash_cmp,
.automatic_shrinking = true,
.automatic_shrinking = true,
};
};


static void nft_validate_state_update(struct net *net, u8 new_validate_state)
static void nft_validate_state_update(struct net *net, u8 new_validate_state)
{
{
switch (net->nft.validate_state) {
switch (net->nft.validate_state) {
case NFT_VALIDATE_SKIP:
case NFT_VALIDATE_SKIP:
WARN_ON_ONCE(new_validate_state == NFT_VALIDATE_DO);
WARN_ON_ONCE(new_validate_state == NFT_VALIDATE_DO);
break;
break;
case NFT_VALIDATE_NEED:
case NFT_VALIDATE_NEED:
break;
break;
case NFT_VALIDATE_DO:
case NFT_VALIDATE_DO:
if (new_validate_state == NFT_VALIDATE_NEED)
if (new_validate_state == NFT_VALIDATE_NEED)
return;
return;
}
}


net->nft.validate_state = new_validate_state;
net->nft.validate_state = new_validate_state;
}
}
static void nf_tables_trans_destroy_work(struct work_struct *w);
static void nf_tables_trans_destroy_work(struct work_struct *w);
static DECLARE_WORK(trans_destroy_work, nf_tables_trans_destroy_work);
static DECLARE_WORK(trans_destroy_work, nf_tables_trans_destroy_work);


static void nft_ctx_init(struct nft_ctx *ctx,
static void nft_ctx_init(struct nft_ctx *ctx,
struct net *net,
struct net *net,
const struct sk_buff *skb,
const struct sk_buff *skb,
const struct nlmsghdr *nlh,
const struct nlmsghdr *nlh,
u8 family,
u8 family,
struct nft_table *table,
struct nft_table *table,
struct nft_chain *chain,
struct nft_chain *chain,
const struct nlattr * const *nla)
const struct nlattr * const *nla)
{
{
ctx->net = net;
ctx->net = net;
ctx->family = family;
ctx->family = family;
ctx->level = 0;
ctx->level = 0;
ctx->table = table;
ctx->table = table;
ctx->chain = chain;
ctx->chain = chain;
ctx->nla = nla;
ctx->nla = nla;
ctx->portid = NETLINK_CB(skb).portid;
ctx->portid = NETLINK_CB(skb).portid;
ctx->report = nlmsg_report(nlh);
ctx->report = nlmsg_report(nlh);
ctx->flags = nlh->nlmsg_flags;
ctx->flags = nlh->nlmsg_flags;
ctx->seq = nlh->nlmsg_seq;
ctx->seq = nlh->nlmsg_seq;
}
}


static struct nft_trans *nft_trans_alloc_gfp(const struct nft_ctx *ctx,
static struct nft_trans *nft_trans_alloc_gfp(const struct nft_ctx *ctx,
int msg_type, u32 size, gfp_t gfp)
int msg_type, u32 size, gfp_t gfp)
{
{
struct nft_trans *trans;
struct nft_trans *trans;


trans = kzalloc(sizeof(struct nft_trans) + size, gfp);
trans = kzalloc(sizeof(struct nft_trans) + size, gfp);
if (trans == NULL)
if (trans == NULL)
return NULL;
return NULL;


INIT_LIST_HEAD(&trans->list);
trans->msg_type = msg_type;
trans->msg_type = msg_type;
trans->ctx = *ctx;
trans->ctx = *ctx;


return trans;
return trans;
}
}


static struct nft_trans *nft_trans_alloc(const struct nft_ctx *ctx,
static struct nft_trans *nft_trans_alloc(const struct nft_ctx *ctx,
int msg_type, u32 size)
int msg_type, u32 size)
{
{
return nft_trans_alloc_gfp(ctx, msg_type, size, GFP_KERNEL);
return nft_trans_alloc_gfp(ctx, msg_type, size, GFP_KERNEL);
}
}


static void nft_trans_destroy(struct nft_trans *trans)
static void nft_trans_destroy(struct nft_trans *trans)
{
{
list_del(&trans->list);
list_del(&trans->list);
kfree(trans);
kfree(trans);
}
}


static void nft_set_trans_bind(const struct nft_ctx *ctx, struct nft_set *set)
static void nft_set_trans_bind(const struct nft_ctx *ctx, struct nft_set *set)
{
{
struct net *net = ctx->net;
struct net *net = ctx->net;
struct nft_trans *trans;
struct nft_trans *trans;


if (!nft_set_is_anonymous(set))
if (!nft_set_is_anonymous(set))
return;
return;


list_for_each_entry_reverse(trans, &net->nft.commit_list, list) {
list_for_each_entry_reverse(trans, &net->nft.commit_list, list) {
switch (trans->msg_type) {
switch (trans->msg_type) {
case NFT_MSG_NEWSET:
case NFT_MSG_NEWSET:
if (nft_trans_set(trans) == set)
if (nft_trans_set(trans) == set)
nft_trans_set_bound(trans) = true;
nft_trans_set_bound(trans) = true;
break;
break;
case NFT_MSG_NEWSETELEM:
case NFT_MSG_NEWSETELEM:
if (nft_trans_elem_set(trans) == set)
if (nft_trans_elem_set(trans) == set)
nft_trans_elem_set_bound(trans) = true;
nft_trans_elem_set_bound(trans) = true;
break;
break;
}
}
}
}
}
}


static int nf_tables_register_hook(struct net *net,
static int nf_tables_register_hook(struct net *net,
const struct nft_table *table,
const struct nft_table *table,
struct nft_chain *chain)
struct nft_chain *chain)
{
{
const struct nft_base_chain *basechain;
const struct nft_base_chain *basechain;
const struct nf_hook_ops *ops;
const struct nf_hook_ops *ops;


if (table->flags & NFT_TABLE_F_DORMANT ||
if (table->flags & NFT_TABLE_F_DORMANT ||
!nft_is_base_chain(chain))
!nft_is_base_chain(chain))
return 0;
return 0;


basechain = nft_base_chain(chain);
basechain = nft_base_chain(chain);
ops = &basechain->ops;
ops = &basechain->ops;


if (basechain->type->ops_register)
if (basechain->type->ops_register)
return basechain->type->ops_register(net, ops);
return basechain->type->ops_register(net, ops);


return nf_register_net_hook(net, ops);
return nf_register_net_hook(net, ops);
}
}


static void nf_tables_unregister_hook(struct net *net,
static void nf_tables_unregister_hook(struct net *net,
const struct nft_table *table,
const struct nft_table *table,
struct nft_chain *chain)
struct nft_chain *chain)
{
{
const struct nft_base_chain *basechain;
const struct nft_base_chain *basechain;
const struct nf_hook_ops *ops;
const struct nf_hook_ops *ops;


if (table->flags & NFT_TABLE_F_DORMANT ||
if (table->flags & NFT_TABLE_F_DORMANT ||
!nft_is_base_chain(chain))
!nft_is_base_chain(chain))
return;
return;
basechain = nft_base_chain(chain);
basechain = nft_base_chain(chain);
ops = &basechain->ops;
ops = &basechain->ops;


if (basechain->type->ops_unregister)
if (basechain->type->ops_unregister)
return basechain->type->ops_unregister(net, ops);
return basechain->type->ops_unregister(net, ops);


nf_unregister_net_hook(net, ops);
nf_unregister_net_hook(net, ops);
}
}


static int nft_trans_table_add(struct nft_ctx *ctx, int msg_type)
static int nft_trans_table_add(struct nft_ctx *ctx, int msg_type)
{
{
struct nft_trans *trans;
struct nft_trans *trans;


trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_table));
trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_table));
if (trans == NULL)
if (trans == NULL)
return -ENOMEM;
return -ENOMEM;


if (msg_type == NFT_MSG_NEWTABLE)
if (msg_type == NFT_MSG_NEWTABLE)
nft_activate_next(ctx->net, ctx->table);
nft_activate_next(ctx->net, ctx->table);


list_add_tail(&trans->list, &ctx->net->nft.commit_list);
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
return 0;
return 0;
}
}


static int nft_deltable(struct nft_ctx *ctx)
static int nft_deltable(struct nft_ctx *ctx)
{
{
int err;
int err;


err = nft_trans_table_add(ctx, NFT_MSG_DELTABLE);
err = nft_trans_table_add(ctx, NFT_MSG_DELTABLE);
if (err < 0)
if (err < 0)
return err;
return err;


nft_deactivate_next(ctx->net, ctx->table);
nft_deactivate_next(ctx->net, ctx->table);
return err;
return err;
}
}


static struct nft_trans *nft_trans_chain_add(struct nft_ctx *ctx, int msg_type)
static struct nft_trans *nft_trans_chain_add(struct nft_ctx *ctx, int msg_type)
{
{
struct nft_trans *trans;
struct nft_trans *trans;


trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_chain));
trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_chain));
if (trans == NULL)
if (trans == NULL)
return ERR_PTR(-ENOMEM);
return ERR_PTR(-ENOMEM);


if (msg_type == NFT_MSG_NEWCHAIN)
if (msg_type == NFT_MSG_NEWCHAIN)
nft_activate_next(ctx->net, ctx->chain);
nft_activate_next(ctx->net, ctx->chain);


list_add_tail(&trans->list, &ctx->net->nft.commit_list);
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
return trans;
return trans;
}
}


static int nft_delchain(struct nft_ctx *ctx)
static int nft_delchain(struct nft_ctx *ctx)
{
{
struct nft_trans *trans;
struct nft_trans *trans;


trans = nft_trans_chain_add(ctx, NFT_MSG_DELCHAIN);
trans = nft_trans_chain_add(ctx, NFT_MSG_DELCHAIN);
if (IS_ERR(trans))
if (IS_ERR(trans))
return PTR_ERR(trans);
return PTR_ERR(trans);


ctx->table->use--;
ctx->table->use--;
nft_deactivate_next(ctx->net, ctx->chain);
nft_deactivate_next(ctx->net, ctx->chain);


return 0;
return 0;
}
}


static void nft_rule_expr_activate(const struct nft_ctx *ctx,
static void nft_rule_expr_activate(const struct nft_ctx *ctx,
struct nft_rule *rule)
struct nft_rule *rule)
{
{
struct nft_expr *expr;
struct nft_expr *expr;


expr = nft_expr_first(rule);
expr = nft_expr_first(rule);
while (nft_expr_more(rule, expr)) {
while (nft_expr_more(rule, expr)) {
if (expr->ops->activate)
if (expr->ops->activate)
expr->ops->activate(ctx, expr);
expr->ops->activate(ctx, expr);


expr = nft_expr_next(expr);
expr = nft_expr_next(expr);
}
}
}
}


static void nft_rule_expr_deactivate(const struct nft_ctx *ctx,
static void nft_rule_expr_deactivate(const struct nft_ctx *ctx,
struct nft_rule *rule,
struct nft_rule *rule,
enum nft_trans_phase phase)
enum nft_trans_phase phase)
{
{
struct nft_expr *expr;
struct nft_expr *expr;


expr = nft_expr_first(rule);
expr = nft_expr_first(rule);
while (nft_expr_more(rule, expr)) {
while (nft_expr_more(rule, expr)) {
if (expr->ops->deactivate)
if (expr->ops->deactivate)
expr->ops->deactivate(ctx, expr, phase);
expr->ops->deactivate(ctx, expr, phase);


expr = nft_expr_next(expr);
expr = nft_expr_next(expr);
}
}
}
}


static int
static int
nf_tables_delrule_deactivate(struct nft_ctx *ctx, struct nft_rule *rule)
nf_tables_delrule_deactivate(struct nft_ctx *ctx, struct nft_rule *rule)
{
{
/* You cannot delete the same rule twice */
/* You cannot delete the same rule twice */
if (nft_is_active_next(ctx->net, rule)) {
if (nft_is_active_next(ctx->net, rule)) {
nft_deactivate_next(ctx->net, rule);
nft_deactivate_next(ctx->net, rule);
ctx->chain->use--;
ctx->chain->use--;
return 0;
return 0;
}
}
return -ENOENT;
return -ENOENT;
}
}


static struct nft_trans *nft_trans_rule_add(struct nft_ctx *ctx, int msg_type,
static struct nft_trans *nft_trans_rule_add(struct nft_ctx *ctx, int msg_type,
struct nft_rule *rule)
struct nft_rule *rule)
{
{
struct nft_trans *trans;
struct nft_trans *trans;


trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_rule));
trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_rule));
if (trans == NULL)
if (trans == NULL)
return NULL;
return NULL;


if (msg_type == NFT_MSG_NEWRULE && ctx->nla[NFTA_RULE_ID] != NULL) {
if (msg_type == NFT_MSG_NEWRULE && ctx->nla[NFTA_RULE_ID] != NULL) {
nft_trans_rule_id(trans) =
nft_trans_rule_id(trans) =
ntohl(nla_get_be32(ctx->nla[NFTA_RULE_ID]));
ntohl(nla_get_be32(ctx->nla[NFTA_RULE_ID]));
}
}
nft_trans_rule(trans) = rule;
nft_trans_rule(trans) = rule;
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
list_add_tail(&trans->list, &ctx->net->nft.commit_list);


return trans;
return trans;
}
}


static int nft_delrule(struct nft_ctx *ctx, struct nft_rule *rule)
static int nft_delrule(struct nft_ctx *ctx, struct nft_rule *rule)
{
{
struct nft_trans *trans;
struct nft_trans *trans;
int err;
int err;


trans = nft_trans_rule_add(ctx, NFT_MSG_DELRULE, rule);
trans = nft_trans_rule_add(ctx, NFT_MSG_DELRULE, rule);
if (trans == NULL)
if (trans == NULL)
return -ENOMEM;
return -ENOMEM;


err = nf_tables_delrule_deactivate(ctx, rule);
err = nf_tables_delrule_deactivate(ctx, rule);
if (err < 0) {
if (err < 0) {
nft_trans_destroy(trans);
nft_trans_destroy(trans);
return err;
return err;
}
}
nft_rule_expr_deactivate(ctx, rule, NFT_TRANS_PREPARE);
nft_rule_expr_deactivate(ctx, rule, NFT_TRANS_PREPARE);


return 0;
return 0;
}
}


static int nft_delrule_by_chain(struct nft_ctx *ctx)
static int nft_delrule_by_chain(struct nft_ctx *ctx)
{
{
struct nft_rule *rule;
struct nft_rule *rule;
int err;
int err;


list_for_each_entry(rule, &ctx->chain->rules, list) {
list_for_each_entry(rule, &ctx->chain->rules, list) {
if (!nft_is_active_next(ctx->net, rule))
if (!nft_is_active_next(ctx->net, rule))
continue;
continue;


err = nft_delrule(ctx, rule);
err = nft_delrule(ctx, rule);
if (err < 0)
if (err < 0)
return err;
return err;
}
}
return 0;
return 0;
}
}


static int nft_trans_set_add(const struct nft_ctx *ctx, int msg_type,
static int nft_trans_set_add(const struct nft_ctx *ctx, int msg_type,
struct nft_set *set)
struct nft_set *set)
{
{
struct nft_trans *trans;
struct nft_trans *trans;


trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_set));
trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_set));
if (trans == NULL)
if (trans == NULL)
return -ENOMEM;
return -ENOMEM;


if (msg_type == NFT_MSG_NEWSET && ctx->nla[NFTA_SET_ID] != NULL) {
if (msg_type == NFT_MSG_NEWSET && ctx->nla[NFTA_SET_ID] != NULL) {
nft_trans_set_id(trans) =
nft_trans_set_id(trans) =
ntohl(nla_get_be32(ctx->nla[NFTA_SET_ID]));
ntohl(nla_get_be32(ctx->nla[NFTA_SET_ID]));
nft_activate_next(ctx->net, set);
nft_activate_next(ctx->net, set);
}
}
nft_trans_set(trans) = set;
nft_trans_set(trans) = set;
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
list_add_tail(&trans->list, &ctx->net->nft.commit_list);


return 0;
return 0;
}
}


static int nft_delset(const struct nft_ctx *ctx, struct nft_set *set)
static int nft_delset(const struct nft_ctx *ctx, struct nft_set *set)
{
{
int err;
int err;


err = nft_trans_set_add(ctx, NFT_MSG_DELSET, set);
err = nft_trans_set_add(ctx, NFT_MSG_DELSET, set);
if (err < 0)
if (err < 0)
return err;
return err;


nft_deactivate_next(ctx->net, set);
nft_deactivate_next(ctx->net, set);
ctx->table->use--;
ctx->table->use--;


return err;
return err;
}
}


static int nft_trans_obj_add(struct nft_ctx *ctx, int msg_type,
static int nft_trans_obj_add(struct nft_ctx *ctx, int msg_type,
struct nft_object *obj)
struct nft_object *obj)
{
{
struct nft_trans *trans;
struct nft_trans *trans;


trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_obj));
trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_obj));
if (trans == NULL)
if (trans == NULL)
return -ENOMEM;
return -ENOMEM;


if (msg_type == NFT_MSG_NEWOBJ)
if (msg_type == NFT_MSG_NEWOBJ)
nft_activate_next(ctx->net, obj);
nft_activate_next(ctx->net, obj);


nft_trans_obj(trans) = obj;
nft_trans_obj(trans) = obj;
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
list_add_tail(&trans->list, &ctx->net->nft.commit_list);


return 0;
return 0;
}
}


static int nft_delobj(struct nft_ctx *ctx, struct nft_object *obj)
static int nft_delobj(struct nft_ctx *ctx, struct nft_object *obj)
{
{
int err;
int err;


err = nft_trans_obj_add(ctx, NFT_MSG_DELOBJ, obj);
err = nft_trans_obj_add(ctx, NFT_MSG_DELOBJ, obj);
if (err < 0)
if (err < 0)
return err;
return err;


nft_deactivate_next(ctx->net, obj);
nft_deactivate_next(ctx->net, obj);
ctx->table->use--;
ctx->table->use--;


return err;
return err;
}
}


static int nft_trans_flowtable_add(struct nft_ctx *ctx, int msg_type,
static int nft_trans_flowtable_add(struct nft_ctx *ctx, int msg_type,
struct nft_flowtable *flowtable)
struct nft_flowtable *flowtable)
{
{
struct nft_trans *trans;
struct nft_trans *trans;


trans = nft_trans_alloc(ctx, msg_type,
trans = nft_trans_alloc(ctx, msg_type,
sizeof(struct nft_trans_flowtable));
sizeof(struct nft_trans_flowtable));
if (trans == NULL)
if (trans == NULL)
return -ENOMEM;
return -ENOMEM;


if (msg_type == NFT_MSG_NEWFLOWTABLE)
if (msg_type == NFT_MSG_NEWFLOWTABLE)
nft_activate_next(ctx->net, flowtable);
nft_activate_next(ctx->net, flowtable);


nft_trans_flowtable(trans) = flowtable;
nft_trans_flowtable(trans) = flowtable;
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
list_add_tail(&trans->list, &ctx->net->nft.commit_list);


return 0;
return 0;
}
}


static int nft_delflowtable(struct nft_ctx *ctx,
static int nft_delflowtable(struct nft_ctx *ctx,
struct nft_flowtable *flowtable)
struct nft_flowtable *flowtable)
{
{
int err;
int err;


err = nft_trans_flowtable_add(ctx, NFT_MSG_DELFLOWTABLE, flowtable);
err = nft_trans_flowtable_add(ctx, NFT_MSG_DELFLOWTABLE, flowtable);
if (err < 0)
if (err < 0)
return err;
return err;


nft_deactivate_next(ctx->net, flowtable);
nft_deactivate_next(ctx->net, flowtable);
ctx->table->use--;
ctx->table->use--;


return err;
return err;
}
}


/*
/*
* Tables
* Tables
*/
*/


static struct nft_table *nft_table_lookup(const struct net *net,
static struct nft_table *nft_table_lookup(const struct net *net,
const struct nlattr *nla,
const struct nlattr *nla,
u8 family, u8 genmask)
u8 family, u8 genmask)
{
{
struct nft_table *table;
struct nft_table *table;


if (nla == NULL)
if (nla == NULL)
return ERR_PTR(-EINVAL);
return ERR_PTR(-EINVAL);


list_for_each_entry_rcu(table, &net->nft.tables, list,
list_for_each_entry_rcu(table, &net->nft.tables, list,
lockdep_is_held(&net->nft.commit_mutex)) {
lockdep_is_held(&net->nft.commit_mutex)) {
if (!nla_strcmp(nla, table->name) &&
if (!nla_strcmp(nla, table->name) &&
table->family == family &&
table->family == family &&
nft_active_genmask(table, genmask))
nft_active_genmask(table, genmask))
return table;
return table;
}
}


return ERR_PTR(-ENOENT);
return ERR_PTR(-ENOENT);
}
}


static struct nft_table *nft_table_lookup_byhandle(const struct net *net,
static struct nft_table *nft_table_lookup_byhandle(const struct net *net,
const struct nlattr *nla,
const struct nlattr *nla,
u8 genmask)
u8 genmask)
{
{
struct nft_table *table;
struct nft_table *table;


list_for_each_entry(table, &net->nft.tables, list) {
list_for_each_entry(table, &net->nft.tables, list) {
if (be64_to_cpu(nla_get_be64(nla)) == table->handle &&
if (be64_to_cpu(nla_get_be64(nla)) == table->handle &&
nft_active_genmask(table, genmask))
nft_active_genmask(table, genmask))
return table;
return table;
}
}


return ERR_PTR(-ENOENT);
return ERR_PTR(-ENOENT);
}
}


static inline u64 nf_tables_alloc_handle(struct nft_table *table)
static inline u64 nf_tables_alloc_handle(struct nft_table *table)
{
{
return ++table->hgenerator;
return ++table->hgenerator;
}
}


static const struct nft_chain_type *chain_type[NFPROTO_NUMPROTO][NFT_CHAIN_T_MAX];
static const struct nft_chain_type *chain_type[NFPROTO_NUMPROTO][NFT_CHAIN_T_MAX];


static const struct nft_chain_type *
static const struct nft_chain_type *
__nft_chain_type_get(u8 family, enum nft_chain_types type)
__nft_chain_type_get(u8 family, enum nft_chain_types type)
{
{
if (family >= NFPROTO_NUMPROTO ||
if (family >= NFPROTO_NUMPROTO ||
type >= NFT_CHAIN_T_MAX)
type >= NFT_CHAIN_T_MAX)
return NULL;
return NULL;


return chain_type[family][type];
return chain_type[family][type];
}
}


static const struct nft_chain_type *
static const struct nft_chain_type *
__nf_tables_chain_type_lookup(const struct nlattr *nla, u8 family)
__nf_tables_chain_type_lookup(const struct nlattr *nla, u8 family)
{
{
const struct nft_chain_type *type;
const struct nft_chain_type *type;
int i;
int i;


for (i = 0; i < NFT_CHAIN_T_MAX; i++) {
for (i = 0; i < NFT_CHAIN_T_MAX; i++) {
type = __nft_chain_type_get(family, i);
type = __nft_chain_type_get(family, i);
if (!type)
if (!type)
continue;
continue;
if (!nla_strcmp(nla, type->name))
if (!nla_strcmp(nla, type->name))
return type;
return type;
}
}
return NULL;
return NULL;
}
}


struct nft_module_request {
struct nft_module_request {
struct list_head list;
struct list_head list;
char module[MODULE_NAME_LEN];
char module[MODULE_NAME_LEN];
bool done;
bool done;
};
};


#ifdef CONFIG_MODULES
#ifdef CONFIG_MODULES
static int nft_request_module(struct net *net, const char *fmt, ...)
static int nft_request_module(struct net *net, const char *fmt, ...)
{
{
char module_name[MODULE_NAME_LEN];
char module_name[MODULE_NAME_LEN];
struct nft_module_request *req;
struct nft_module_request *req;
va_list args;
va_list args;
int ret;
int ret;


va_start(args, fmt);
va_start(args, fmt);
ret = vsnprintf(module_name, MODULE_NAME_LEN, fmt, args);
ret = vsnprintf(module_name, MODULE_NAME_LEN, fmt, args);
va_end(args);
va_end(args);
if (ret >= MODULE_NAME_LEN)
if (ret >= MODULE_NAME_LEN)
return 0;
return 0;


list_for_each_entry(req, &net->nft.module_list, list) {
list_for_each_entry(req, &net->nft.module_list, list) {
if (!strcmp(req->module, module_name)) {
if (!strcmp(req->module, module_name)) {
if (req->done)
if (req->done)
return 0;
return 0;


/* A request to load this module already exists. */
/* A request to load this module already exists. */
return -EAGAIN;
return -EAGAIN;
}
}
}
}


req = kmalloc(sizeof(*req), GFP_KERNEL);
req = kmalloc(sizeof(*req), GFP_KERNEL);
if (!req)
if (!req)
return -ENOMEM;
return -ENOMEM;


req->done = false;
req->done = false;
strlcpy(req->module, module_name, MODULE_NAME_LEN);
strlcpy(req->module, module_name, MODULE_NAME_LEN);
list_add_tail(&req->list, &net->nft.module_list);
list_add_tail(&req->list, &net->nft.module_list);


return -EAGAIN;
return -EAGAIN;
}
}
#endif
#endif


static void lockdep_nfnl_nft_mutex_not_held(void)
static void lockdep_nfnl_nft_mutex_not_held(void)
{
{
#ifdef CONFIG_PROVE_LOCKING
#ifdef CONFIG_PROVE_LOCKING
if (debug_locks)
if (debug_locks)
WARN_ON_ONCE(lockdep_nfnl_is_held(NFNL_SUBSYS_NFTABLES));
WARN_ON_ONCE(lockdep_nfnl_is_held(NFNL_SUBSYS_NFTABLES));
#endif
#endif
}
}


static const struct nft_chain_type *
static const struct nft_chain_type *
nf_tables_chain_type_lookup(struct net *net, const struct nlattr *nla,
nf_tables_chain_type_lookup(struct net *net, const struct nlattr *nla,
u8 family, bool autoload)
u8 family, bool autoload)
{
{
const struct nft_chain_type *type;
const struct nft_chain_type *type;


type = __nf_tables_chain_type_lookup(nla, family);
type = __nf_tables_chain_type_lookup(nla, family);
if (type != NULL)
if (type != NULL)
return type;
return type;


lockdep_nfnl_nft_mutex_not_held();
lockdep_nfnl_nft_mutex_not_held();
#ifdef CONFIG_MODULES
#ifdef CONFIG_MODULES
if (autoload) {
if (autoload) {
if (nft_request_module(net, "nft-chain-%u-%.*s", family,
if (nft_request_module(net, "nft-chain-%u-%.*s", family,
nla_len(nla),
nla_len(nla),
(const char *)nla_data(nla)) == -EAGAIN)
(const char *)nla_data(nla)) == -EAGAIN)
return ERR_PTR(-EAGAIN);
return ERR_PTR(-EAGAIN);
}
}
#endif
#endif
return ERR_PTR(-ENOENT);
return ERR_PTR(-ENOENT);
}
}


static const struct nla_policy nft_table_policy[NFTA_TABLE_MAX + 1] = {
static const struct nla_policy nft_table_policy[NFTA_TABLE_MAX + 1] = {
[NFTA_TABLE_NAME] = { .type = NLA_STRING,
[NFTA_TABLE_NAME] = { .type = NLA_STRING,
.len = NFT_TABLE_MAXNAMELEN - 1 },
.len = NFT_TABLE_MAXNAMELEN - 1 },
[NFTA_TABLE_FLAGS] = { .type = NLA_U32 },
[NFTA_TABLE_FLAGS] = { .type = NLA_U32 },
[NFTA_TABLE_HANDLE] = { .type = NLA_U64 },
[NFTA_TABLE_HANDLE] = { .type = NLA_U64 },
};
};


static int nf_tables_fill_table_info(struct sk_buff *skb, struct net *net,
static int nf_tables_fill_table_info(struct sk_buff *skb, struct net *net,
u32 portid, u32 seq, int event, u32 flags,
u32 portid, u32 seq, int event, u32 flags,
int family, const struct nft_table *table)
int family, const struct nft_table *table)
{
{
struct nlmsghdr *nlh;
struct nlmsghdr *nlh;
struct nfgenmsg *nfmsg;
struct nfgenmsg *nfmsg;


event = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, event);
event = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, event);
nlh = nlmsg_put(skb, portid, seq, event, sizeof(struct nfgenmsg), flags);
nlh = nlmsg_put(skb, portid, seq, event, sizeof(struct nfgenmsg), flags);
if (nlh == NULL)
if (nlh == NULL)
goto nla_put_failure;
goto nla_put_failure;


nfmsg = nlmsg_data(nlh);
nfmsg = nlmsg_data(nlh);
nfmsg->nfgen_family = family;
nfmsg->nfgen_family = family;
nfmsg->version = NFNETLINK_V0;
nfmsg->version = NFNETLINK_V0;
nfmsg->res_id = htons(net->nft.base_seq & 0xffff);
nfmsg->res_id = htons(net->nft.base_seq & 0xffff);


if (nla_put_string(skb, NFTA_TABLE_NAME, table->name) ||
if (nla_put_string(skb, NFTA_TABLE_NAME, table->name) ||
nla_put_be32(skb, NFTA_TABLE_FLAGS, htonl(table->flags)) ||
nla_put_be32(skb, NFTA_TABLE_FLAGS, htonl(table->flags)) ||
nla_put_be32(skb, NFTA_TABLE_USE, htonl(table->use)) ||
nla_put_be32(skb, NFTA_TABLE_USE, htonl(table->use)) ||
nla_put_be64(skb, NFTA_TABLE_HANDLE, cpu_to_be64(table->handle),
nla_put_be64(skb, NFTA_TABLE_HANDLE, cpu_to_be64(table->handle),
NFTA_TABLE_PAD))
NFTA_TABLE_PAD))
goto nla_put_failure;
goto nla_put_failure;


nlmsg_end(skb, nlh);
nlmsg_end(skb, nlh);
return 0;
return 0;


nla_put_failure:
nla_put_failure:
nlmsg_trim(skb, nlh);
nlmsg_trim(skb, nlh);
return -1;
return -1;
}
}


static void nf_tables_table_notify(const struct nft_ctx *ctx, int event)
static void nf_tables_table_notify(const struct nft_ctx *ctx, int event)
{
{
struct sk_buff *skb;
struct sk_buff *skb;
int err;
int err;


if (!ctx->report &&
if (!ctx->report &&
!nfnetlink_has_listeners(ctx->net, NFNLGRP_NFTABLES))
!nfnetlink_has_listeners(ctx->net, NFNLGRP_NFTABLES))
return;
return;


skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (skb == NULL)
if (skb == NULL)
goto err;
goto err;


err = nf_tables_fill_table_info(skb, ctx->net, ctx->portid, ctx->seq,
err = nf_tables_fill_table_info(skb, ctx->net, ctx->portid, ctx->seq,
event, 0, ctx->family, ctx->table);
event, 0, ctx->family, ctx->table);
if (err < 0) {
if (err < 0) {
kfree_skb(skb);
kfree_skb(skb);
goto err;
goto err;
}
}


nfnetlink_send(skb, ctx->net, ctx->portid, NFNLGRP_NFTABLES,
nfnetlink_send(skb, ctx->net, ctx->portid, NFNLGRP_NFTABLES,
ctx->report, GFP_KERNEL);
ctx->report, GFP_KERNEL);
return;
return;
err:
err:
nfnetlink_set_err(ctx->net, ctx->portid, NFNLGRP_NFTABLES, -ENOBUFS);
nfnetlink_set_err(ctx->net, ctx->portid, NFNLGRP_NFTABLES, -ENOBUFS);
}
}


static int nf_tables_dump_tables(struct sk_buff *skb,
static int nf_tables_dump_tables(struct sk_buff *skb,
struct netlink_callback *cb)
struct netlink_callback *cb)
{
{
const struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
const struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
const struct nft_table *table;
const struct nft_table *table;
unsigned int idx = 0, s_idx = cb->args[0];
unsigned int idx = 0, s_idx = cb->args[0];
struct net *net = sock_net(skb->sk);
struct net *net = sock_net(skb->sk);
int family = nfmsg->nfgen_family;
int family = nfmsg->nfgen_family;


rcu_read_lock();
rcu_read_lock();
cb->seq = net->nft.base_seq;
cb->seq = net->nft.base_seq;


list_for_each_entry_rcu(table, &net->nft.tables, list) {
list_for_each_entry_rcu(table, &net->nft.tables, list) {
if (family != NFPROTO_UNSPEC && family != table->family)
if (family != NFPROTO_UNSPEC && family != table->family)
continue;
continue;


if (idx < s_idx)
if (idx < s_idx)
goto cont;
goto cont;
if (idx > s_idx)
if (idx > s_idx)
memset(&cb->args[1], 0,
memset(&cb->args[1], 0,
sizeof(cb->args) - sizeof(cb->args[0]));
sizeof(cb->args) - sizeof(cb->args[0]));
if (!nft_is_active(net, table))
if (!nft_is_active(net, table))
continue;
continue;
if (nf_tables_fill_table_info(skb, net,
if (nf_tables_fill_table_info(skb, net,
NETLINK_CB(cb->skb).portid,
NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq,
cb->nlh->nlmsg_seq,
NFT_MSG_NEWTABLE, NLM_F_MULTI,
NFT_MSG_NEWTABLE, NLM_F_MULTI,
table->family, table) < 0)
table->family, table) < 0)
goto done;
goto done;


nl_dump_check_consistent(cb, nlmsg_hdr(skb));
nl_dump_check_consistent(cb, nlmsg_hdr(skb));
cont:
cont:
idx++;
idx++;
}
}
done:
done:
rcu_read_unlock();
rcu_read_unlock();
cb->args[0] = idx;
cb->args[0] = idx;
return skb->len;
return skb->len;
}
}


static int nft_netlink_dump_start_rcu(struct sock *nlsk, struct sk_buff *skb,
static int nft_netlink_dump_start_rcu(struct sock *nlsk, struct sk_buff *skb,
const struct nlmsghdr *nlh,
const struct nlmsghdr *nlh,
struct netlink_dump_control *c)
struct netlink_dump_control *c)
{
{
int err;
int err;


if (!try_module_get(THIS_MODULE))
if (!try_module_get(THIS_MODULE))
return -EINVAL;
return -EINVAL;


rcu_read_unlock();
rcu_read_unlock();
err = netlink_dump_start(nlsk, skb, nlh, c);
err = netlink_dump_start(nlsk, skb, nlh, c);
rcu_read_lock();
rcu_read_lock();
module_put(THIS_MODULE);
module_put(THIS_MODULE);


return err;
return err;
}
}


/* called with rcu_read_lock held */
/* called with rcu_read_lock held */
static int nf_tables_gettable(struct net *net, struct sock *nlsk,
static int nf_tables_gettable(struct net *net, struct sock *nlsk,
struct sk_buff *skb, const struct nlmsghdr *nlh,
struct sk_buff *skb, const struct nlmsghdr *nlh,
const struct nlattr * const nla[],
const struct nlattr * const nla[],
struct netlink_ext_ack *extack)
struct netlink_ext_ack *extack)
{
{
const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
u8 genmask = nft_genmask_cur(net);
u8 genmask = nft_genmask_cur(net);
const struct nft_table *table;
const struct nft_table *table;
struct sk_buff *skb2;
struct sk_buff *skb2;
int family = nfmsg->nfgen_family;
int family = nfmsg->nfgen_family;
int err;
int err;


if (nlh->nlmsg_flags & NLM_F_DUMP) {
if (nlh->nlmsg_flags & NLM_F_DUMP) {
struct netlink_dump_control c = {
struct netlink_dump_control c = {
.dump = nf_tables_dump_tables,
.dump = nf_tables_dump_tables,
.module = THIS_MODULE,
.module = THIS_MODULE,
};
};


return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
}
}


table = nft_table_lookup(net, nla[NFTA_TABLE_NAME], family, genmask);
table = nft_table_lookup(net, nla[NFTA_TABLE_NAME], family, genmask);
if (IS_ERR(table)) {
if (IS_ERR(table)) {
NL_SET_BAD_ATTR(extack, nla[NFTA_TABLE_NAME]);
NL_SET_BAD_ATTR(extack, nla[NFTA_TABLE_NAME]);
return PTR_ERR(table);
return PTR_ERR(table);
}
}


skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
if (!skb2)
if (!skb2)
return -ENOMEM;
return -ENOMEM;


err = nf_tables_fill_table_info(skb2, net, NETLINK_CB(skb).portid,
err = nf_tables_fill_table_info(skb2, net, NETLINK_CB(skb).portid,
nlh->nlmsg_seq, NFT_MSG_NEWTABLE, 0,
nlh->nlmsg_seq, NFT_MSG_NEWTABLE, 0,
family, table);
family, table);
if (err < 0)
if (err < 0)
goto err_fill_table_info;
goto err_fill_table_info;


return nfnetlink_unicast(skb2, net, NETLINK_CB(skb).portid);
return nfnetlink_unicast(skb2, net, NETLINK_CB(skb).portid);


err_fill_table_info:
err_fill_table_info:
kfree_skb(skb2);
kfree_skb(skb2);
return err;
return err;
}
}


static void nft_table_disable(struct net *net, struct nft_table *table, u32 cnt)
static void nft_table_disable(struct net *net, struct nft_table *table, u32 cnt)
{
{
struct nft_chain *chain;
struct nft_chain *chain;
u32 i = 0;
u32 i = 0;


list_for_each_entry(chain, &table->chains, list) {
list_for_each_entry(chain, &table->chains, list) {
if (!nft_is_active_next(net, chain))
if (!nft_is_active_next(net, chain))
continue;
continue;
if (!nft_is_base_chain(chain))
if (!nft_is_base_chain(chain))
continue;
continue;


if (cnt && i++ == cnt)
if (cnt && i++ == cnt)
break;
break;


nf_unregister_net_hook(net, &nft_base_chain(chain)->ops);
nf_unregister_net_hook(net, &nft_base_chain(chain)->ops);
}
}
}
}


static int nf_tables_table_enable(struct net *net, struct nft_table *table)
static int nf_tables_table_enable(struct net *net, struct nft_table *table)
{
{
struct nft_chain *chain;
struct nft_chain *chain;
int err, i = 0;
int err, i = 0;


list_for_each_entry(chain, &table->chains, list) {
list_for_each_entry(chain, &table->chains, list) {
if (!nft_is_active_next(net, chain))
if (!nft_is_active_next(net, chain))
continue;
continue;
if (!nft_is_base_chain(chain))
if (!nft_is_base_chain(chain))
continue;
continue;


err = nf_register_net_hook(net, &nft_base_chain(chain)->ops);
err = nf_register_net_hook(net, &nft_base_chain(chain)->ops);
if (err < 0)
if (err < 0)
goto err;
goto err;


i++;
i++;
}
}
return 0;
return 0;
err:
err:
if (i)
if (i)
nft_table_disable(net, table, i);
nft_table_disable(net, table, i);
return err;
return err;
}
}


static void nf_tables_table_disable(struct net *net, struct nft_table *table)
static void nf_tables_table_disable(struct net *net, struct nft_table *table)
{
{
nft_table_disable(net, table, 0);
nft_table_disable(net, table, 0);
}
}


static int nf_tables_updtable(struct nft_ctx *ctx)
static int nf_tables_updtable(struct nft_ctx *ctx)
{
{
struct nft_trans *trans;
struct nft_trans *trans;
u32 flags;
u32 flags;
int ret = 0;
int ret = 0;


if (!ctx->nla[NFTA_TABLE_FLAGS])
if (!ctx->nla[NFTA_TABLE_FLAGS])
return 0;
return 0;


flags = ntohl(nla_get_be32(ctx->nla[NFTA_TABLE_FLAGS]));
flags = ntohl(nla_get_be32(ctx->nla[NFTA_TABLE_FLAGS]));
if (flags & ~NFT_TABLE_F_DORMANT)
if (flags & ~NFT_TABLE_F_DORMANT)
return -EINVAL;
return -EINVAL;


if (flags == ctx->table->flags)
if (flags == ctx->table->flags)
return 0;
return 0;


trans = nft_trans_alloc(ctx, NFT_MSG_NEWTABLE,
trans = nft_trans_alloc(ctx, NFT_MSG_NEWTABLE,
sizeof(struct nft_trans_table));
sizeof(struct nft_trans_table));
if (trans == NULL)
if (trans == NULL)
return -ENOMEM;
return -ENOMEM;


if ((flags & NFT_TABLE_F_DORMANT) &&
if ((flags & NFT_TABLE_F_DORMANT) &&
!(ctx->table->flags & NFT_TABLE_F_DORMANT)) {
!(ctx->table->flags & NFT_TABLE_F_DORMANT)) {
nft_trans_table_enable(trans) = false;
nft_trans_table_enable(trans) = false;
} else if (!(flags & NFT_TABLE_F_DORMANT) &&
} else if (!(flags & NFT_TABLE_F_DORMANT) &&
ctx->table->flags & NFT_TABLE_F_DORMANT) {
ctx->table->flags & NFT_TABLE_F_DORMANT) {
ret = nf_tables_table_enable(ctx->net, ctx->table);
ret = nf_tables_table_enable(ctx->net, ctx->table);
if (ret >= 0) {
if (ret >= 0) {
ctx->table->flags &= ~NFT_TABLE_F_DORMANT;
ctx->table->flags &= ~NFT_TABLE_F_DORMANT;
nft_trans_table_enable(trans) = true;
nft_trans_table_enable(trans) = true;
}
}
}
}
if (ret < 0)
if (ret < 0)
goto err;
goto err;


nft_trans_table_update(trans) = true;
nft_trans_table_update(trans) = true;
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
return 0;
return 0;
err:
err:
nft_trans_destroy(trans);
nft_trans_destroy(trans);
return ret;
return ret;
}
}


static u32 nft_chain_hash(const void *data, u32 len, u32 seed)
static u32 nft_chain_hash(const void *data, u32 len, u32 seed)
{
{
const char *name = data;
const char *name = data;


return jhash(name, strlen(name), seed);
return jhash(name, strlen(name), seed);
}
}


static u32 nft_chain_hash_obj(const void *data, u32 len, u32 seed)
static u32 nft_chain_hash_obj(const void *data, u32 len, u32 seed)
{
{
const struct nft_chain *chain = data;
const struct nft_chain *chain = data;


return nft_chain_hash(chain->name, 0, seed);
return nft_chain_hash(chain->name, 0, seed);
}
}


static int nft_chain_hash_cmp(struct rhashtable_compare_arg *arg,
static int nft_chain_hash_cmp(struct rhashtable_compare_arg *arg,
const void *ptr)
const void *ptr)
{
{
const struct nft_chain *chain = ptr;
const struct nft_chain *chain = ptr;
const char *name = arg->key;
const char *name = arg->key;


return strcmp(chain->name, name);
return strcmp(chain->name, name);
}
}


static u32 nft_objname_hash(const void *data, u32 len, u32 seed)
static u32 nft_objname_hash(const void *data, u32 len, u32 seed)
{
{
const struct nft_object_hash_key *k = data;
const struct nft_object_hash_key *k = data;


seed ^= hash_ptr(k->table, 32);
seed ^= hash_ptr(k->table, 32);


return jhash(k->name, strlen(k->name), seed);
return jhash(k->name, strlen(k->name), seed);
}
}


static u32 nft_objname_hash_obj(const void *data, u32 len, u32 seed)
static u32 nft_objname_hash_obj(const void *data, u32 len, u32 seed)
{
{
const struct nft_object *obj = data;
const struct nft_object *obj = data;


return nft_objname_hash(&obj->key, 0, seed);
return nft_objname_hash(&obj->key, 0, seed);
}
}


static int nft_objname_hash_cmp(struct rhashtable_compare_arg *arg,
static int nft_objname_hash_cmp(struct rhashtable_compare_arg *arg,
const void *ptr)
const void *ptr)
{
{
const struct nft_object_hash_key *k = arg->key;
const struct nft_object_hash_key *k = arg->key;
const struct nft_object *obj = ptr;
const struct nft_object *obj = ptr;


if (obj->key.table != k->table)
if (obj->key.table != k->table)
return -1;
return -1;


return strcmp(obj->key.name, k->name);
return strcmp(obj->key.name, k->name);
}
}


static int nf_tables_newtable(struct net *net, struct sock *nlsk,
static int nf_tables_newtable(struct net *net, struct sock *nlsk,
struct sk_buff *skb, const struct nlmsghdr *nlh,
struct sk_buff *skb, const struct nlmsghdr *nlh,
const struct nlattr * const nla[],
const struct nlattr * const nla[],
struct netlink_ext_ack *extack)
struct netlink_ext_ack *extack)
{
{
const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
u8 genmask = nft_genmask_next(net);
u8 genmask = nft_genmask_next(net);
int family = nfmsg->nfgen_family;
int family = nfmsg->nfgen_family;
const struct nlattr *attr;
const struct nlattr *attr;
struct nft_table *table;
struct nft_table *table;
u32 flags = 0;
u32 flags = 0;
struct nft_ctx ctx;
struct nft_ctx ctx;
int err;
int err;


lockdep_assert_held(&net->nft.commit_mutex);
lockdep_assert_held(&net->nft.commit_mutex);
attr = nla[NFTA_TABLE_NAME];
attr = nla[NFTA_TABLE_NAME];
table = nft_table_lookup(net, attr, family, genmask);
table = nft_table_lookup(net, attr, family, genmask);
if (IS_ERR(table)) {
if (IS_ERR(table)) {
if (PTR_ERR(table) != -ENOENT)
if (PTR_ERR(table) != -ENOENT)
return PTR_ERR(table);
return PTR_ERR(table);
} else {
} else {
if (nlh->nlmsg_flags & NLM_F_EXCL) {
if (nlh->nlmsg_flags & NLM_F_EXCL) {
NL_SET_BAD_ATTR(extack, attr);
NL_SET_BAD_ATTR(extack, attr);
return -EEXIST;
return -EEXIST;
}
}
if (nlh->nlmsg_flags & NLM_F_REPLACE)
if (nlh->nlmsg_flags & NLM_F_REPLACE)
return -EOPNOTSUPP;
return -EOPNOTSUPP;


nft_ctx_init(&ctx, net, skb, nlh, family, table, NULL, nla);
nft_ctx_init(&ctx, net, skb, nlh, family, table, NULL, nla);
return nf_tables_updtable(&ctx);
return nf_tables_updtable(&ctx);
}
}


if (nla[NFTA_TABLE_FLAGS]) {
if (nla[NFTA_TABLE_FLAGS]) {
flags = ntohl(nla_get_be32(nla[NFTA_TABLE_FLAGS]));
flags = ntohl(nla_get_be32(nla[NFTA_TABLE_FLAGS]));
if (flags & ~NFT_TABLE_F_DORMANT)
if (flags & ~NFT_TABLE_F_DORMANT)
return -EINVAL;
return -EINVAL;
}
}


err = -ENOMEM;
err = -ENOMEM;
table = kzalloc(sizeof(*table), GFP_KERNEL);
table = kzalloc(sizeof(*table), GFP_KERNEL);
if (table == NULL)
if (table == NULL)
goto err_kzalloc;
goto err_kzalloc;


table->name = nla_strdup(attr, GFP_KERNEL);
table->name = nla_strdup(attr, GFP_KERNEL);
if (table->name == NULL)
if (table->name == NULL)
goto err_strdup;
goto err_strdup;


err = rhltable_init(&table->chains_ht, &nft_chain_ht_params);
err = rhltable_init(&table->chains_ht, &nft_chain_ht_params);
if (err)
if (err)
goto err_chain_ht;
goto err_chain_ht;


INIT_LIST_HEAD(&table->chains);
INIT_LIST_HEAD(&table->chains);
INIT_LIST_HEAD(&table->sets);
INIT_LIST_HEAD(&table->sets);
INIT_LIST_HEAD(&table->objects);
INIT_LIST_HEAD(&table->objects);
INIT_LIST_HEAD(&table->flowtables);
INIT_LIST_HEAD(&table->flowtables);
table->family = family;
table->family = family;
table->flags = flags;
table->flags = flags;
table->handle = ++table_handle;
table->handle = ++table_handle;


nft_ctx_init(&ctx, net, skb, nlh, family, table, NULL, nla);
nft_ctx_init(&ctx, net, skb, nlh, family, table, NULL, nla);
err = nft_trans_table_add(&ctx, NFT_MSG_NEWTABLE);
err = nft_trans_table_add(&ctx, NFT_MSG_NEWTABLE);
if (err < 0)
if (err < 0)
goto err_trans;
goto err_trans;


list_add_tail_rcu(&table->list, &net->nft.tables);
list_add_tail_rcu(&table->list, &net->nft.tables);
return 0;
return 0;
err_trans:
err_trans:
rhltable_destroy(&table->chains_ht);
rhltable_destroy(&table->chains_ht);
err_chain_ht:
err_chain_ht:
kfree(table->name);
kfree(table->name);
err_strdup:
err_strdup:
kfree(table);
kfree(table);
err_kzalloc:
err_kzalloc:
return err;
return err;
}
}


static int nft_flush_table(struct nft_ctx *ctx)
static int nft_flush_table(struct nft_ctx *ctx)
{
{
struct nft_flowtable *flowtable, *nft;
struct nft_flowtable *flowtable, *nft;
struct nft_chain *chain, *nc;
struct nft_chain *chain, *nc;
struct nft_object *obj, *ne;
struct nft_object *obj, *ne;
struct nft_set *set, *ns;
struct nft_set *set, *ns;
int err;
int err;


list_for_each_entry(chain, &ctx->table->chains, list) {
list_for_each_entry(chain, &ctx->table->chains, list) {
if (!nft_is_active_next(ctx->net, chain))
if (!nft_is_active_next(ctx->net, chain))
continue;
continue;


ctx->chain = chain;
ctx->chain = chain;


err = nft_delrule_by_chain(ctx);
err = nft_delrule_by_chain(ctx);
if (err < 0)
if (err < 0)
goto out;
goto out;
}
}


list_for_each_entry_safe(set, ns, &ctx->table->sets, list) {
list_for_each_entry_safe(set, ns, &ctx->table->sets, list) {
if (!nft_is_active_next(ctx->net, set))
if (!nft_is_active_next(ctx->net, set))
continue;
continue;


if (nft_set_is_anonymous(set) &&
if (nft_set_is_anonymous(set) &&
!list_empty(&set->bindings))
!list_empty(&set->bindings))