Commit 563125a7 authored by Pablo Neira Ayuso's avatar Pablo Neira Ayuso
Browse files

netfilter: nftables: generalize set extension to support for several expressions



This patch replaces NFT_SET_EXPR by NFT_SET_EXT_EXPRESSIONS. This new
extension allows to attach several expressions to one set element (not
only one single expression as NFT_SET_EXPR provides). This patch
prepares for support for several expressions per set element in the
netlink userspace API.

Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent 92b211a2
Loading
Loading
Loading
Loading
+28 −8
Original line number Diff line number Diff line
@@ -421,6 +421,20 @@ struct nft_set_type {
};
#define to_set_type(o) container_of(o, struct nft_set_type, ops)

struct nft_set_elem_expr {
	u8				size;
	unsigned char			data[]
		__attribute__((aligned(__alignof__(struct nft_expr))));
};

#define nft_setelem_expr_at(__elem_expr, __offset)			\
	((struct nft_expr *)&__elem_expr->data[__offset])

#define nft_setelem_expr_foreach(__expr, __elem_expr, __size)		\
	for (__expr = nft_setelem_expr_at(__elem_expr, 0), __size = 0;	\
	     __size < (__elem_expr)->size;				\
	     __size += (__expr)->ops->size, __expr = ((void *)(__expr)) + (__expr)->ops->size)

#define NFT_SET_EXPR_MAX	2

/**
@@ -547,7 +561,7 @@ void nf_tables_destroy_set(const struct nft_ctx *ctx, struct nft_set *set);
 *	@NFT_SET_EXT_TIMEOUT: element timeout
 *	@NFT_SET_EXT_EXPIRATION: element expiration time
 *	@NFT_SET_EXT_USERDATA: user data associated with the element
 *	@NFT_SET_EXT_EXPR: expression assiociated with the element
 *	@NFT_SET_EXT_EXPRESSIONS: expressions assiciated with the element
 *	@NFT_SET_EXT_OBJREF: stateful object reference associated with element
 *	@NFT_SET_EXT_NUM: number of extension types
 */
@@ -559,7 +573,7 @@ enum nft_set_extensions {
	NFT_SET_EXT_TIMEOUT,
	NFT_SET_EXT_EXPIRATION,
	NFT_SET_EXT_USERDATA,
	NFT_SET_EXT_EXPR,
	NFT_SET_EXT_EXPRESSIONS,
	NFT_SET_EXT_OBJREF,
	NFT_SET_EXT_NUM
};
@@ -677,9 +691,9 @@ static inline struct nft_userdata *nft_set_ext_userdata(const struct nft_set_ext
	return nft_set_ext(ext, NFT_SET_EXT_USERDATA);
}

static inline struct nft_expr *nft_set_ext_expr(const struct nft_set_ext *ext)
static inline struct nft_set_elem_expr *nft_set_ext_expr(const struct nft_set_ext *ext)
{
	return nft_set_ext(ext, NFT_SET_EXT_EXPR);
	return nft_set_ext(ext, NFT_SET_EXT_EXPRESSIONS);
}

static inline bool nft_set_elem_expired(const struct nft_set_ext *ext)
@@ -909,11 +923,17 @@ static inline void nft_set_elem_update_expr(const struct nft_set_ext *ext,
					    struct nft_regs *regs,
					    const struct nft_pktinfo *pkt)
{
	struct nft_set_elem_expr *elem_expr;
	struct nft_expr *expr;
	u32 size;

	if (__nft_set_ext_exists(ext, NFT_SET_EXT_EXPR)) {
		expr = nft_set_ext_expr(ext);
	if (__nft_set_ext_exists(ext, NFT_SET_EXT_EXPRESSIONS)) {
		elem_expr = nft_set_ext_expr(ext);
		nft_setelem_expr_foreach(expr, elem_expr, size) {
			expr->ops->eval(expr, regs, pkt);
			if (regs->verdict.code == NFT_BREAK)
				return;
		}
	}
}

+65 −20
Original line number Diff line number Diff line
@@ -4496,8 +4496,8 @@ const struct nft_set_ext_type nft_set_ext_types[] = {
	[NFT_SET_EXT_DATA]		= {
		.align	= __alignof__(u32),
	},
	[NFT_SET_EXT_EXPR]		= {
		.align	= __alignof__(struct nft_expr),
	[NFT_SET_EXT_EXPRESSIONS]	= {
		.align	= __alignof__(struct nft_set_elem_expr),
	},
	[NFT_SET_EXT_OBJREF]		= {
		.len	= sizeof(struct nft_object *),
@@ -4573,6 +4573,29 @@ static int nft_ctx_init_from_elemattr(struct nft_ctx *ctx, struct net *net,
	return 0;
}

static int nft_set_elem_expr_dump(struct sk_buff *skb,
				  const struct nft_set *set,
				  const struct nft_set_ext *ext)
{
	struct nft_set_elem_expr *elem_expr;
	u32 size, num_exprs = 0;
	struct nft_expr *expr;

	elem_expr = nft_set_ext_expr(ext);
	nft_setelem_expr_foreach(expr, elem_expr, size)
		num_exprs++;

	if (num_exprs == 1) {
		expr = nft_setelem_expr_at(elem_expr, 0);
		if (nft_expr_dump(skb, NFTA_SET_ELEM_EXPR, expr) < 0)
			return -1;

		return 0;
	}

	return 0;
}

static int nf_tables_fill_setelem(struct sk_buff *skb,
				  const struct nft_set *set,
				  const struct nft_set_elem *elem)
@@ -4600,8 +4623,8 @@ static int nf_tables_fill_setelem(struct sk_buff *skb,
			  set->dlen) < 0)
		goto nla_put_failure;

	if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPR) &&
	    nft_expr_dump(skb, NFTA_SET_ELEM_EXPR, nft_set_ext_expr(ext)) < 0)
	if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPRESSIONS) &&
	    nft_set_elem_expr_dump(skb, set, ext))
		goto nla_put_failure;

	if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF) &&
@@ -5096,7 +5119,7 @@ void *nft_set_elem_init(const struct nft_set *set,
	return elem;
}

static void nft_set_elem_expr_destroy(const struct nft_ctx *ctx,
static void __nft_set_elem_expr_destroy(const struct nft_ctx *ctx,
					struct nft_expr *expr)
{
	if (expr->ops->destroy_clone) {
@@ -5107,6 +5130,16 @@ static void nft_set_elem_expr_destroy(const struct nft_ctx *ctx,
	}
}

static void nft_set_elem_expr_destroy(const struct nft_ctx *ctx,
				      struct nft_set_elem_expr *elem_expr)
{
	struct nft_expr *expr;
	u32 size;

	nft_setelem_expr_foreach(expr, elem_expr, size)
		__nft_set_elem_expr_destroy(ctx, expr);
}

void nft_set_elem_destroy(const struct nft_set *set, void *elem,
			  bool destroy_expr)
{
@@ -5119,7 +5152,7 @@ void nft_set_elem_destroy(const struct nft_set *set, void *elem,
	nft_data_release(nft_set_ext_key(ext), NFT_DATA_VALUE);
	if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA))
		nft_data_release(nft_set_ext_data(ext), set->dtype);
	if (destroy_expr && nft_set_ext_exists(ext, NFT_SET_EXT_EXPR))
	if (destroy_expr && nft_set_ext_exists(ext, NFT_SET_EXT_EXPRESSIONS))
		nft_set_elem_expr_destroy(&ctx, nft_set_ext_expr(ext));

	if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF))
@@ -5136,7 +5169,7 @@ static void nf_tables_set_elem_destroy(const struct nft_ctx *ctx,
{
	struct nft_set_ext *ext = nft_set_elem_ext(set, elem);

	if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPR))
	if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPRESSIONS))
		nft_set_elem_expr_destroy(ctx, nft_set_ext_expr(ext));

	kfree(elem);
@@ -5171,6 +5204,18 @@ err_expr:
	return -ENOMEM;
}

static void nft_set_elem_expr_setup(const struct nft_set_ext *ext, int i,
				    struct nft_expr *expr_array[])
{
	struct nft_set_elem_expr *elem_expr = nft_set_ext_expr(ext);
	struct nft_expr *expr = nft_setelem_expr_at(elem_expr, elem_expr->size);

	memcpy(expr, expr_array[i], expr_array[i]->ops->size);
	elem_expr->size += expr_array[i]->ops->size;
	kfree(expr_array[i]);
	expr_array[i] = NULL;
}

static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
			    const struct nlattr *attr, u32 nlmsg_flags)
{
@@ -5186,11 +5231,11 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
	struct nft_data_desc desc;
	enum nft_registers dreg;
	struct nft_trans *trans;
	u32 flags = 0;
	u32 flags = 0, size = 0;
	u64 timeout;
	u64 expiration;
	u8 ulen;
	int err, i;
	u8 ulen;

	err = nla_parse_nested_deprecated(nla, NFTA_SET_ELEM_MAX, attr,
					  nft_set_elem_policy, NULL);
@@ -5293,9 +5338,14 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
			nft_set_ext_add(&tmpl, NFT_SET_EXT_TIMEOUT);
	}

	if (set->num_exprs == 1)
		nft_set_ext_add_length(&tmpl, NFT_SET_EXT_EXPR,
				       expr_array[0]->ops->size);
	if (set->num_exprs) {
		for (i = 0; i < set->num_exprs; i++)
			size += expr_array[i]->ops->size;

		nft_set_ext_add_length(&tmpl, NFT_SET_EXT_EXPRESSIONS,
				       sizeof(struct nft_set_elem_expr) +
				       size);
	}

	if (nla[NFTA_SET_ELEM_OBJREF] != NULL) {
		if (!(set->flags & NFT_SET_OBJECT)) {
@@ -5377,13 +5427,8 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
		*nft_set_ext_obj(ext) = obj;
		obj->use++;
	}
	if (set->num_exprs == 1) {
		struct nft_expr *expr = expr_array[0];

		memcpy(nft_set_ext_expr(ext), expr, expr->ops->size);
		kfree(expr);
		expr_array[0] = NULL;
	}
	for (i = 0; i < set->num_exprs; i++)
		nft_set_elem_expr_setup(ext, i, expr_array);

	trans = nft_trans_elem_alloc(ctx, NFT_MSG_NEWSETELEM, set);
	if (trans == NULL)
+83 −20
Original line number Diff line number Diff line
@@ -19,11 +19,30 @@ struct nft_dynset {
	enum nft_registers		sreg_key:8;
	enum nft_registers		sreg_data:8;
	bool				invert;
	u8				num_exprs;
	u64				timeout;
	struct nft_expr			*expr;
	struct nft_expr			*expr_array[NFT_SET_EXPR_MAX];
	struct nft_set_binding		binding;
};

static int nft_dynset_expr_setup(const struct nft_dynset *priv,
				 const struct nft_set_ext *ext)
{
	struct nft_set_elem_expr *elem_expr = nft_set_ext_expr(ext);
	struct nft_expr *expr;
	int i;

	for (i = 0; i < priv->num_exprs; i++) {
		expr = nft_setelem_expr_at(elem_expr, elem_expr->size);
		if (nft_expr_clone(expr, priv->expr_array[i]) < 0)
			return -1;

		elem_expr->size += priv->expr_array[i]->ops->size;
	}

	return 0;
}

static void *nft_dynset_new(struct nft_set *set, const struct nft_expr *expr,
			    struct nft_regs *regs)
{
@@ -44,8 +63,7 @@ static void *nft_dynset_new(struct nft_set *set, const struct nft_expr *expr,
		goto err1;

	ext = nft_set_elem_ext(set, elem);
	if (priv->expr != NULL &&
	    nft_expr_clone(nft_set_ext_expr(ext), priv->expr) < 0)
	if (priv->num_exprs && nft_dynset_expr_setup(priv, ext) < 0)
		goto err2;

	return elem;
@@ -90,6 +108,41 @@ void nft_dynset_eval(const struct nft_expr *expr,
		regs->verdict.code = NFT_BREAK;
}

static void nft_dynset_ext_add_expr(struct nft_dynset *priv)
{
	u8 size = 0;
	int i;

	for (i = 0; i < priv->num_exprs; i++)
		size += priv->expr_array[i]->ops->size;

	nft_set_ext_add_length(&priv->tmpl, NFT_SET_EXT_EXPRESSIONS,
			       sizeof(struct nft_set_elem_expr) + size);
}

static struct nft_expr *
nft_dynset_expr_alloc(const struct nft_ctx *ctx, const struct nft_set *set,
		      const struct nlattr *attr, int pos)
{
	struct nft_expr *expr;
	int err;

	expr = nft_set_elem_expr_alloc(ctx, set, attr);
	if (IS_ERR(expr))
		return expr;

	if (set->exprs[pos] && set->exprs[pos]->ops != expr->ops) {
		err = -EOPNOTSUPP;
		goto err_dynset_expr;
	}

	return expr;

err_dynset_expr:
	nft_expr_destroy(ctx, expr);
	return ERR_PTR(err);
}

static const struct nla_policy nft_dynset_policy[NFTA_DYNSET_MAX + 1] = {
	[NFTA_DYNSET_SET_NAME]	= { .type = NLA_STRING,
				    .len = NFT_SET_MAXNAMELEN - 1 },
@@ -110,7 +163,7 @@ static int nft_dynset_init(const struct nft_ctx *ctx,
	u8 genmask = nft_genmask_next(ctx->net);
	struct nft_set *set;
	u64 timeout;
	int err;
	int err, i;

	lockdep_assert_held(&ctx->net->nft.commit_mutex);

@@ -179,17 +232,23 @@ static int nft_dynset_init(const struct nft_ctx *ctx,
	} else if (set->flags & NFT_SET_MAP)
		return -EINVAL;

	if (tb[NFTA_DYNSET_EXPR] != NULL) {
	if (tb[NFTA_DYNSET_EXPR]) {
		struct nft_expr *dynset_expr;

		if (!(set->flags & NFT_SET_EVAL))
			return -EINVAL;

		priv->expr = nft_set_elem_expr_alloc(ctx, set,
						     tb[NFTA_DYNSET_EXPR]);
		if (IS_ERR(priv->expr))
			return PTR_ERR(priv->expr);
		dynset_expr = nft_dynset_expr_alloc(ctx, set,
						    tb[NFTA_DYNSET_EXPR], 0);
		if (IS_ERR(dynset_expr))
			return PTR_ERR(dynset_expr);

		if (set->num_exprs == 1 &&
		    set->exprs[0]->ops != priv->expr->ops) {
		priv->num_exprs++;
		priv->expr_array[0] = dynset_expr;

		if (set->num_exprs > 1 ||
		    (set->num_exprs == 1 &&
		     dynset_expr->ops != set->exprs[0]->ops)) {
			err = -EOPNOTSUPP;
			goto err_expr_free;
		}
@@ -199,9 +258,10 @@ static int nft_dynset_init(const struct nft_ctx *ctx,
	nft_set_ext_add_length(&priv->tmpl, NFT_SET_EXT_KEY, set->klen);
	if (set->flags & NFT_SET_MAP)
		nft_set_ext_add_length(&priv->tmpl, NFT_SET_EXT_DATA, set->dlen);
	if (priv->expr != NULL)
		nft_set_ext_add_length(&priv->tmpl, NFT_SET_EXT_EXPR,
				       priv->expr->ops->size);

	if (priv->num_exprs)
		nft_dynset_ext_add_expr(priv);

	if (set->flags & NFT_SET_TIMEOUT) {
		if (timeout || set->timeout)
			nft_set_ext_add(&priv->tmpl, NFT_SET_EXT_EXPIRATION);
@@ -220,8 +280,8 @@ static int nft_dynset_init(const struct nft_ctx *ctx,
	return 0;

err_expr_free:
	if (priv->expr != NULL)
		nft_expr_destroy(ctx, priv->expr);
	for (i = 0; i < priv->num_exprs; i++)
		nft_expr_destroy(ctx, priv->expr_array[i]);
	return err;
}

@@ -246,9 +306,10 @@ static void nft_dynset_destroy(const struct nft_ctx *ctx,
			       const struct nft_expr *expr)
{
	struct nft_dynset *priv = nft_expr_priv(expr);
	int i;

	if (priv->expr != NULL)
		nft_expr_destroy(ctx, priv->expr);
	for (i = 0; i < priv->num_exprs; i++)
		nft_expr_destroy(ctx, priv->expr_array[i]);

	nf_tables_destroy_set(ctx, priv->set);
}
@@ -271,8 +332,10 @@ static int nft_dynset_dump(struct sk_buff *skb, const struct nft_expr *expr)
			 cpu_to_be64(jiffies_to_msecs(priv->timeout)),
			 NFTA_DYNSET_PAD))
		goto nla_put_failure;
	if (priv->expr && nft_expr_dump(skb, NFTA_DYNSET_EXPR, priv->expr))
	if (priv->num_exprs == 1) {
		if (nft_expr_dump(skb, NFTA_DYNSET_EXPR, priv->expr_array[0]))
			goto nla_put_failure;
	}
	if (nla_put_be32(skb, NFTA_DYNSET_FLAGS, htonl(flags)))
		goto nla_put_failure;
	return 0;
+20 −7
Original line number Diff line number Diff line
@@ -293,6 +293,22 @@ cont:
	rhashtable_walk_exit(&hti);
}

static bool nft_rhash_expr_needs_gc_run(const struct nft_set *set,
					struct nft_set_ext *ext)
{
	struct nft_set_elem_expr *elem_expr = nft_set_ext_expr(ext);
	struct nft_expr *expr;
	u32 size;

	nft_setelem_expr_foreach(expr, elem_expr, size) {
		if (expr->ops->gc &&
		    expr->ops->gc(read_pnet(&set->net), expr))
			return true;
	}

	return false;
}

static void nft_rhash_gc(struct work_struct *work)
{
	struct nft_set *set;
@@ -314,16 +330,13 @@ static void nft_rhash_gc(struct work_struct *work)
			continue;
		}

		if (nft_set_ext_exists(&he->ext, NFT_SET_EXT_EXPR)) {
			struct nft_expr *expr = nft_set_ext_expr(&he->ext);
		if (nft_set_ext_exists(&he->ext, NFT_SET_EXT_EXPRESSIONS) &&
		    nft_rhash_expr_needs_gc_run(set, &he->ext))
			goto needs_gc_run;

			if (expr->ops->gc &&
			    expr->ops->gc(read_pnet(&set->net), expr))
				goto gc;
		}
		if (!nft_set_elem_expired(&he->ext))
			continue;
gc:
needs_gc_run:
		if (nft_set_elem_mark_busy(&he->ext))
			continue;