All of lore.kernel.org
 help / color / mirror / Atom feed
From: Phil Sutter <phil@nwl.cc>
To: Pablo Neira Ayuso <pablo@netfilter.org>
Cc: netfilter-devel@vger.kernel.org
Subject: Re: [PATCH nf-next,v1 6/6] netfilter: nf_tables: add support for validating incremental ruleset updates
Date: Thu, 15 May 2025 14:05:34 +0200	[thread overview]
Message-ID: <aCXYjttDxXj_d2EH@orbyte.nwl.cc> (raw)
In-Reply-To: <20250514214216.828862-7-pablo@netfilter.org>

On Wed, May 14, 2025 at 11:42:16PM +0200, Pablo Neira Ayuso wrote:
> Use the new binding graph to validate incremental ruleset updates.
> 
> Perform full validation if the table is new, which is the case of the
> initial ruleset reload. Subsequent incremental ruleset updates use the
> validation list which contains chains with new rules or chains that can
> be now reached via jump/goto from another rule/set element.
> 
> When validating a chain from commit/abort phase, backtrack to collect
> the basechains that can reach this chain, then perform the validation of
> rules in this chain. If no basechains can be reached, then skip
> validation for this chain. However, if basechains are off the jump stack
> limit, then resort to full ruleset validation. This is to prevent
> inconsistent validation between the preparation and commit/abort phase
> validations.
> 
> As for loop checking, stick to the existing approach which uses the jump
> stack limit to detect cycles.
> 
> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
> ---
>  net/netfilter/nf_tables_api.c | 188 +++++++++++++++++++++++++++++++++-
>  1 file changed, 183 insertions(+), 5 deletions(-)
> 
> diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
> index e92cccc834d9..0f183abbc94f 100644
> --- a/net/netfilter/nf_tables_api.c
> +++ b/net/netfilter/nf_tables_api.c
> @@ -10397,6 +10397,160 @@ static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = {
>  	},
>  };
>  
> +struct basechain_array {
> +	const struct nft_chain	**basechain;
> +	uint32_t		num_basechains;
> +	uint32_t		max_basechains;
> +	int			max_level;
> +};
> +
> +static int basechain_array_add(struct basechain_array *array,
> +			       const struct nft_chain *chain, int level)
> +{
> +	const struct nft_chain **new_basechain;
> +	uint32_t new_max_basechains;
> +
> +	if (array->num_basechains == array->max_basechains) {
> +		new_max_basechains = array->max_basechains + 16;
> +		new_basechain = krealloc_array(array->basechain, new_max_basechains, sizeof(struct nft_chain *), GFP_KERNEL);
> +		if (!new_basechain)
> +			return -ENOMEM;
> +
> +		array->basechain = new_basechain;
> +		array->max_basechains = new_max_basechains;
> +	}
> +	array->basechain[array->num_basechains++] = chain;
> +
> +	if (level > array->max_level)
> +		array->max_level = level;
> +
> +	return 0;
> +}
> +
> +static int nft_chain_validate_backtrack(struct basechain_array *array,
> +					const struct list_head *backbinding_list,
> +					int *level)
> +{
> +	struct nft_binding *binding;
> +	int err;
> +
> +	/* Basechain is unreachable, fall back to slow path validation. */
> +	if (*level >= NFT_JUMP_STACK_SIZE)
> +		return -ENOENT;
> +
> +	list_for_each_entry(binding, backbinding_list, backlist) {
> +		if (binding->from.type == NFT_BIND_CHAIN &&
> +		    binding->from.chain->flags & NFT_CHAIN_BASE &&
> +		    binding->use > 0) {
> +			if (basechain_array_add(array, binding->from.chain, *level) < 0)
> +				return -ENOMEM;
> +
> +			continue;
> +		}
> +
> +		switch (binding->from.type) {
> +		case NFT_BIND_CHAIN:
> +			if (binding->use == 0)
> +				break;
> +
> +			(*level)++;
> +			err = nft_chain_validate_backtrack(array,
> +							   &binding->from.chain->backbinding_list,
> +							   level);
> +			if (err < 0)
> +				return err;
> +
> +			(*level)--;

It looks like you're trying to record the max level value for error
path, but I don't see it used in callers. Is this a leftover from before
introduction of basechain_array::max_level? If so, one may just pass
'level + 1' by value to the recursive call, right?

> +			break;
> +		case NFT_BIND_SET:
> +			if (binding->use == 0)
> +				break;
> +
> +			/* no level update for sets. */
> +			err = nft_chain_validate_backtrack(array,
> +							   &binding->from.set->backbinding_list,
> +							   level);
> +			if (err < 0)
> +				return err;
> +
> +			break;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int nft_chain_validate_incremental(struct net *net,
> +					  const struct nft_chain *chain)
> +{
> +	struct basechain_array array = {};
> +	uint32_t i, level = 1;
> +	int err;
> +
> +	array.max_basechains = 16;
> +	array.basechain = kcalloc(16, sizeof(struct nft_chain *), GFP_KERNEL);
> +	if (!array.basechain)
> +		return -ENOMEM;
> +
> +	if (nft_is_base_chain(chain)) {
> +		err = basechain_array_add(&array, chain, 0);
> +		if (err < 0) {
> +			kfree(array.basechain);
> +			return -ENOMEM;
> +		}
> +	} else {
> +		err = nft_chain_validate_backtrack(&array,
> +						   &chain->backbinding_list,
> +						   &level);
> +		if (err < 0) {
> +			kfree(array.basechain);
> +			return err;
> +		}
> +	}
> +
> +	for (i = 0; i < array.num_basechains; i++) {
> +		struct nft_ctx ctx = {
> +			.net	= net,
> +			.family	= chain->table->family,
> +			.table	= chain->table,
> +			.chain	= (struct nft_chain *)array.basechain[i],
> +			.level	= array.max_level,
> +		};
> +
> +		if (WARN_ON_ONCE(!nft_is_base_chain(array.basechain[i])))
> +			continue;
> +
> +		err = nft_chain_validate(&ctx, chain);
> +		if (err < 0)
> +			break;
> +	}
> +
> +	kfree(array.basechain);
> +
> +	return err;
> +}
> +
> +static int nft_validate_incremental(struct net *net, struct nft_table *table)
> +{
> +	struct nftables_pernet *nft_net = nft_pernet(net);
> +	struct nft_chain *chain, *next;
> +	int err;
> +
> +	err = 0;
> +	list_for_each_entry_safe(chain, next, &nft_net->validate_list, validate_list) {
> +		if (chain->table != table)
> +			continue;
> +
> +		if (err >= 0)
> +			err = nft_chain_validate_incremental(net, chain);

Is there a future use-case for err > 0? I don't see where that function
would return a positive value. If this is not to change, I guess just
checking err || !err would simplify things a bit and also avoid the
EINTR != !EINTR pitfall (see below).

> +
> +		list_del(&chain->validate_list);
> +		chain->validate = 0;
> +	}
> +
> +	return err;
> +}
> +
>  static void nft_validate_chain_release(struct net *net)
>  {
>  	struct nftables_pernet *nft_net = nft_pernet(net);
> @@ -10422,12 +10576,36 @@ static int nf_tables_validate(struct net *net)
>  			nft_validate_state_update(table, NFT_VALIDATE_DO);
>  			fallthrough;
>  		case NFT_VALIDATE_DO:
> -			err = nft_table_validate(net, table);
> -			if (err < 0) {
> -				if (err == EINTR)

This should be -EINTR, right?

> -					goto err_eintr;
> +			/* If this table is new, then this is the initial
> +			 * ruleset restore, perform full table validation,
> +			 * otherwise, perform incremental validation.
> +			 */
> +			if (!nft_is_active(net, table)) {
> +				err = nft_table_validate(net, table);
> +				if (err < 0) {
> +					if (err == EINTR)

Same here?

> +						goto err_eintr;
>  
> -				return -EAGAIN;
> +					return -EAGAIN;
> +				}
> +			} else {
> +				err = nft_validate_incremental(net, table);
> +				if (err < 0) {
> +					if (err != -ENOMEM && err != -ENOENT)
> +						return -EAGAIN;
> +
> +					/* Either no memory or it cannot reach
> +					 * basechain, then fallback to full
> +					 * validation.
> +					 */
> +					err = nft_table_validate(net, table);
> +					if (err < 0) {
> +						if (err == EINTR)

And here as well?

Cheers, Phil

> +							goto err_eintr;
> +
> +						return -EAGAIN;
> +					}
> +				}
>  			}
>  			nft_validate_state_update(table, NFT_VALIDATE_SKIP);
>  			break;
> -- 
> 2.30.2
> 
> 
> 

  reply	other threads:[~2025-05-15 12:05 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-05-14 21:42 [PATCH nf-next,v1 0/6] revisiting nf_tables ruleset validation Pablo Neira Ayuso
2025-05-14 21:42 ` [PATCH nf-next,v1 1/6] netfilter: nf_tables: honor EINTR in ruleset validation from commit/abort path Pablo Neira Ayuso
2025-05-15  9:56   ` Phil Sutter
2025-05-14 21:42 ` [PATCH nf-next,v1 2/6] netfilter: nf_tables: honor validation state in preparation phase Pablo Neira Ayuso
2025-05-14 21:42 ` [PATCH nf-next,v1 3/6] netfilter: nf_tables: add infrastructure for chain validation on updates Pablo Neira Ayuso
2025-05-14 21:42 ` [PATCH nf-next,v1 4/6] netfilter: nf_tables: add new binding infrastructure Pablo Neira Ayuso
2025-05-15 12:32   ` Phil Sutter
2025-05-14 21:42 ` [PATCH nf-next,v1 5/6] netfilter: nf_tables: use " Pablo Neira Ayuso
2025-05-15 12:34   ` Phil Sutter
2025-05-14 21:42 ` [PATCH nf-next,v1 6/6] netfilter: nf_tables: add support for validating incremental ruleset updates Pablo Neira Ayuso
2025-05-15 12:05   ` Phil Sutter [this message]
2025-05-15  6:03 ` [PATCH nf-next,v1 0/6] revisiting nf_tables ruleset validation Florian Westphal
2025-05-19 15:10   ` Pablo Neira Ayuso
2025-05-20 10:42     ` Florian Westphal
2025-05-20 11:29       ` Pablo Neira Ayuso

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aCXYjttDxXj_d2EH@orbyte.nwl.cc \
    --to=phil@nwl.cc \
    --cc=netfilter-devel@vger.kernel.org \
    --cc=pablo@netfilter.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.