Commit d0e2c7de authored by Pablo Neira Ayuso's avatar Pablo Neira Ayuso
Browse files

netfilter: nf_tables: add NFT_CHAIN_BINDING



This new chain flag specifies that:

* the kernel dynamically allocates the chain name, if no chain name
  is specified.

* If the immediate expression that refers to this chain is removed,
  then this bound chain (and its content) is destroyed.

Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent 04b7db41
Loading
Loading
Loading
Loading
+12 −1
Original line number Diff line number Diff line
@@ -899,6 +899,8 @@ static inline struct nft_userdata *nft_userdata(const struct nft_rule *rule)
	return (void *)&rule->data[rule->dlen];
}

void nf_tables_rule_release(const struct nft_ctx *ctx, struct nft_rule *rule);

static inline void nft_set_elem_update_expr(const struct nft_set_ext *ext,
					    struct nft_regs *regs,
					    const struct nft_pktinfo *pkt)
@@ -944,7 +946,8 @@ struct nft_chain {
	struct nft_table		*table;
	u64				handle;
	u32				use;
	u8				flags:6,
	u8				flags:5,
					bound:1,
					genmask:2;
	char				*name;

@@ -989,6 +992,14 @@ int nft_chain_validate_dependency(const struct nft_chain *chain,
int nft_chain_validate_hooks(const struct nft_chain *chain,
                             unsigned int hook_flags);

static inline bool nft_chain_is_bound(struct nft_chain *chain)
{
	return (chain->flags & NFT_CHAIN_BINDING) && chain->bound;
}

void nft_chain_del(struct nft_chain *chain);
void nf_tables_chain_destroy(struct nft_ctx *ctx);

struct nft_stats {
	u64			bytes;
	u64			pkts;
+1 −0
Original line number Diff line number Diff line
@@ -187,6 +187,7 @@ enum nft_table_attributes {
enum nft_chain_flags {
	NFT_CHAIN_BASE		= (1 << 0),
	NFT_CHAIN_HW_OFFLOAD	= (1 << 1),
	NFT_CHAIN_BINDING	= (1 << 2),
};

/**
+74 −12
Original line number Diff line number Diff line
@@ -1056,6 +1056,9 @@ static int nft_flush_table(struct nft_ctx *ctx)
		if (!nft_is_active_next(ctx->net, chain))
			continue;

		if (nft_chain_is_bound(chain))
			continue;

		ctx->chain = chain;

		err = nft_delrule_by_chain(ctx);
@@ -1098,6 +1101,9 @@ static int nft_flush_table(struct nft_ctx *ctx)
		if (!nft_is_active_next(ctx->net, chain))
			continue;

		if (nft_chain_is_bound(chain))
			continue;

		ctx->chain = chain;

		err = nft_delchain(ctx);
@@ -1413,12 +1419,11 @@ static int nf_tables_fill_chain_info(struct sk_buff *skb, struct net *net,
					      lockdep_commit_lock_is_held(net));
		if (nft_dump_stats(skb, stats))
			goto nla_put_failure;
	}

		if ((chain->flags & NFT_CHAIN_HW_OFFLOAD) &&
		    nla_put_be32(skb, NFTA_CHAIN_FLAGS,
				 htonl(NFT_CHAIN_HW_OFFLOAD)))
	if (chain->flags &&
	    nla_put_be32(skb, NFTA_CHAIN_FLAGS, htonl(chain->flags)))
		goto nla_put_failure;
	}

	if (nla_put_be32(skb, NFTA_CHAIN_USE, htonl(chain->use)))
		goto nla_put_failure;
@@ -1621,7 +1626,7 @@ static void nf_tables_chain_free_chain_rules(struct nft_chain *chain)
	kvfree(chain->rules_next);
}

static void nf_tables_chain_destroy(struct nft_ctx *ctx)
void nf_tables_chain_destroy(struct nft_ctx *ctx)
{
	struct nft_chain *chain = ctx->chain;
	struct nft_hook *hook, *next;
@@ -1928,6 +1933,8 @@ static int nft_chain_add(struct nft_table *table, struct nft_chain *chain)
	return 0;
}

static u64 chain_id;

static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
			      u8 policy, u32 flags)
{
@@ -1936,6 +1943,7 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
	struct nft_base_chain *basechain;
	struct nft_stats __percpu *stats;
	struct net *net = ctx->net;
	char name[NFT_NAME_MAXLEN];
	struct nft_trans *trans;
	struct nft_chain *chain;
	struct nft_rule **rules;
@@ -1947,6 +1955,9 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
	if (nla[NFTA_CHAIN_HOOK]) {
		struct nft_chain_hook hook;

		if (flags & NFT_CHAIN_BINDING)
			return -EOPNOTSUPP;

		err = nft_chain_parse_hook(net, nla, &hook, family, true);
		if (err < 0)
			return err;
@@ -1976,16 +1987,33 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
			return err;
		}
	} else {
		if (flags & NFT_CHAIN_BASE)
			return -EINVAL;
		if (flags & NFT_CHAIN_HW_OFFLOAD)
			return -EOPNOTSUPP;

		chain = kzalloc(sizeof(*chain), GFP_KERNEL);
		if (chain == NULL)
			return -ENOMEM;

		chain->flags = flags;
	}
	ctx->chain = chain;

	INIT_LIST_HEAD(&chain->rules);
	chain->handle = nf_tables_alloc_handle(table);
	chain->table = table;

	if (nla[NFTA_CHAIN_NAME]) {
		chain->name = nla_strdup(nla[NFTA_CHAIN_NAME], GFP_KERNEL);
	} else {
		if (!(flags & NFT_CHAIN_BINDING))
			return -EINVAL;

		snprintf(name, sizeof(name), "__chain%llu", ++chain_id);
		chain->name = kstrdup(name, GFP_KERNEL);
	}

	if (!chain->name) {
		err = -ENOMEM;
		goto err1;
@@ -2976,8 +3004,7 @@ static void nf_tables_rule_destroy(const struct nft_ctx *ctx,
	kfree(rule);
}

static void nf_tables_rule_release(const struct nft_ctx *ctx,
				   struct nft_rule *rule)
void nf_tables_rule_release(const struct nft_ctx *ctx, struct nft_rule *rule)
{
	nft_rule_expr_deactivate(ctx, rule, NFT_TRANS_RELEASE);
	nf_tables_rule_destroy(ctx, rule);
@@ -3075,6 +3102,9 @@ static int nf_tables_newrule(struct net *net, struct sock *nlsk,
			NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_CHAIN]);
			return PTR_ERR(chain);
		}
		if (nft_chain_is_bound(chain))
			return -EOPNOTSUPP;

	} else if (nla[NFTA_RULE_CHAIN_ID]) {
		chain = nft_chain_lookup_byid(net, nla[NFTA_RULE_CHAIN_ID]);
		if (IS_ERR(chain)) {
@@ -3294,6 +3324,8 @@ static int nf_tables_delrule(struct net *net, struct sock *nlsk,
			NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_CHAIN]);
			return PTR_ERR(chain);
		}
		if (nft_chain_is_bound(chain))
			return -EOPNOTSUPP;
	}

	nft_ctx_init(&ctx, net, skb, nlh, family, table, chain, nla);
@@ -5330,11 +5362,24 @@ static int nf_tables_newsetelem(struct net *net, struct sock *nlsk,
 */
void nft_data_hold(const struct nft_data *data, enum nft_data_types type)
{
	struct nft_chain *chain;
	struct nft_rule *rule;

	if (type == NFT_DATA_VERDICT) {
		switch (data->verdict.code) {
		case NFT_JUMP:
		case NFT_GOTO:
			data->verdict.chain->use++;
			chain = data->verdict.chain;
			chain->use++;

			if (!nft_chain_is_bound(chain))
				break;

			chain->table->use++;
			list_for_each_entry(rule, &chain->rules, list)
				chain->use++;

			nft_chain_add(chain->table, chain);
			break;
		}
	}
@@ -7474,7 +7519,7 @@ static void nft_obj_del(struct nft_object *obj)
	list_del_rcu(&obj->list);
}

static void nft_chain_del(struct nft_chain *chain)
void nft_chain_del(struct nft_chain *chain)
{
	struct nft_table *table = chain->table;

@@ -7825,6 +7870,10 @@ static int __nf_tables_abort(struct net *net, bool autoload)
				kfree(nft_trans_chain_name(trans));
				nft_trans_destroy(trans);
			} else {
				if (nft_chain_is_bound(trans->ctx.chain)) {
					nft_trans_destroy(trans);
					break;
				}
				trans->ctx.table->use--;
				nft_chain_del(trans->ctx.chain);
				nf_tables_unregister_hook(trans->ctx.net,
@@ -8321,10 +8370,23 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data,

static void nft_verdict_uninit(const struct nft_data *data)
{
	struct nft_chain *chain;
	struct nft_rule *rule;

	switch (data->verdict.code) {
	case NFT_JUMP:
	case NFT_GOTO:
		data->verdict.chain->use--;
		chain = data->verdict.chain;
		chain->use--;

		if (!nft_chain_is_bound(chain))
			break;

		chain->table->use--;
		list_for_each_entry(rule, &chain->rules, list)
			chain->use--;

		nft_chain_del(chain);
		break;
	}
}
+51 −0
Original line number Diff line number Diff line
@@ -54,6 +54,23 @@ static int nft_immediate_init(const struct nft_ctx *ctx,
	if (err < 0)
		goto err1;

	if (priv->dreg == NFT_REG_VERDICT) {
		struct nft_chain *chain = priv->data.verdict.chain;

		switch (priv->data.verdict.code) {
		case NFT_JUMP:
		case NFT_GOTO:
			if (nft_chain_is_bound(chain)) {
				err = -EBUSY;
				goto err1;
			}
			chain->bound = true;
			break;
		default:
			break;
		}
	}

	return 0;

err1:
@@ -81,6 +98,39 @@ static void nft_immediate_deactivate(const struct nft_ctx *ctx,
	return nft_data_release(&priv->data, nft_dreg_to_type(priv->dreg));
}

static void nft_immediate_destroy(const struct nft_ctx *ctx,
				  const struct nft_expr *expr)
{
	const struct nft_immediate_expr *priv = nft_expr_priv(expr);
	const struct nft_data *data = &priv->data;
	struct nft_ctx chain_ctx;
	struct nft_chain *chain;
	struct nft_rule *rule;

	if (priv->dreg != NFT_REG_VERDICT)
		return;

	switch (data->verdict.code) {
	case NFT_JUMP:
	case NFT_GOTO:
		chain = data->verdict.chain;

		if (!nft_chain_is_bound(chain))
			break;

		chain_ctx = *ctx;
		chain_ctx.chain = chain;

		list_for_each_entry(rule, &chain->rules, list)
			nf_tables_rule_release(&chain_ctx, rule);

		nf_tables_chain_destroy(&chain_ctx);
		break;
	default:
		break;
	}
}

static int nft_immediate_dump(struct sk_buff *skb, const struct nft_expr *expr)
{
	const struct nft_immediate_expr *priv = nft_expr_priv(expr);
@@ -170,6 +220,7 @@ static const struct nft_expr_ops nft_imm_ops = {
	.init		= nft_immediate_init,
	.activate	= nft_immediate_activate,
	.deactivate	= nft_immediate_deactivate,
	.destroy	= nft_immediate_destroy,
	.dump		= nft_immediate_dump,
	.validate	= nft_immediate_validate,
	.offload	= nft_immediate_offload,