nf_tables_api.c
1068 líneas
// 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))