Add infrastructure to validate rulesets at chain granularity to improve the situation for incremental rule and set element updates. Instead of fully validating the table on updates, annotate chains in the table that needs to be validated again after the updates in this batch. Add a validation list per netns that contains chains that are pending to be validated. A chain is added to the validation list under the following circunstances: - A new rule is added, then add the chain that contains this rule. This allows to validate if the rule expressions are supported from this chain. - A new rule performs a jump/goto another chain. The destination chain is added to the validation list. - A new set element is added, then add the jump/goto chain via element (verdict maps). Add the chain that need validation to the validation list, then from the commit/abort path, remove from the validation list. The validation list becomes empty after the commit/abort phase. This is a preparation patch, note that full table validation is still in place, the next patch adds more infrastructure to enable chain validation. Signed-off-by: Pablo Neira Ayuso <pablo@xxxxxxxxxxxxx> --- include/net/netfilter/nf_tables.h | 5 +++- net/netfilter/nf_tables_api.c | 45 ++++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 803d5f1601f9..d391990d1a96 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -1119,11 +1119,13 @@ struct nft_chain { struct nft_rule_blob __rcu *blob_gen_1; struct list_head rules; struct list_head list; + struct list_head validate_list; struct rhlist_head rhlhead; struct nft_table *table; u64 handle; u32 use; - u8 flags:5, + u8 flags:4, + validate:1, bound:1, genmask:2; char *name; @@ -1910,6 +1912,7 @@ struct nftables_pernet { struct list_head binding_list; struct list_head module_list; struct list_head notify_list; + struct list_head validate_list; struct mutex commit_mutex; u64 table_handle; u64 tstamp; diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 46465a8c255f..d35cad55c99b 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -123,6 +123,25 @@ static void nft_validate_state_update(struct nft_table *table, u8 new_validate_s table->validate_state = new_validate_state; } + +static void nft_validate_chain_pending(struct net *net, struct nft_chain *chain) +{ + struct nftables_pernet *nft_net = nft_pernet(net); + + if (chain->validate) + return; + + chain->validate = 1; + list_add_tail(&chain->validate_list, &nft_net->validate_list); +} + +static void nft_validate_chain_need(struct nft_ctx *ctx, + struct nft_chain *chain) +{ + nft_validate_chain_pending(ctx->net, chain); + nft_validate_state_update(ctx->table, NFT_VALIDATE_NEED); +} + static void nf_tables_trans_destroy_work(struct work_struct *w); static void nft_trans_gc_work(struct work_struct *work); @@ -274,6 +293,8 @@ static void nft_chain_trans_bind(const struct nft_ctx *ctx, int nf_tables_bind_chain(const struct nft_ctx *ctx, struct nft_chain *chain) { + nft_validate_chain_need((struct nft_ctx *)ctx, chain); + if (!nft_chain_binding(chain)) return 0; @@ -4297,7 +4318,7 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, } if (expr_info[i].ops->validate) - nft_validate_state_update(table, NFT_VALIDATE_NEED); + nft_validate_chain_need(&ctx, ctx.chain); expr_info[i].ops = NULL; expr = nft_expr_next(expr); @@ -7392,8 +7413,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, if (desc.type == NFT_DATA_VERDICT && (elem.data.val.verdict.code == NFT_GOTO || elem.data.val.verdict.code == NFT_JUMP)) - nft_validate_state_update(ctx->table, - NFT_VALIDATE_NEED); + nft_validate_chain_need(ctx, elem.data.val.verdict.chain); } err = nft_set_ext_add_length(&tmpl, NFT_SET_EXT_DATA, desc.len); @@ -9898,6 +9918,17 @@ static const struct nfnl_callback nf_tables_cb[NFT_MSG_MAX] = { }, }; +static void nft_validate_chain_release(struct net *net) +{ + struct nftables_pernet *nft_net = nft_pernet(net); + struct nft_chain *chain, *next; + + list_for_each_entry_safe(chain, next, &nft_net->validate_list, validate_list) { + list_del(&chain->validate_list); + chain->validate = 0; + } +} + static int nf_tables_validate(struct net *net) { struct nftables_pernet *nft_net = nft_pernet(net); @@ -10506,6 +10537,8 @@ static void nf_tables_module_autoload_cleanup(struct net *net) struct nft_module_request *req, *next; WARN_ON_ONCE(!list_empty(&nft_net->commit_list)); + WARN_ON_ONCE(!list_empty(&nft_net->validate_list)); + list_for_each_entry_safe(req, next, &nft_net->module_list, list) { WARN_ON_ONCE(!req->done); list_del(&req->list); @@ -10705,6 +10738,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) int err; if (list_empty(&nft_net->commit_list)) { + nft_validate_chain_release(net); mutex_unlock(&nft_net->commit_mutex); return 0; } @@ -10745,6 +10779,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) nft_net->validate_state = NFT_VALIDATE_DO; return -EAGAIN; } + nft_validate_chain_release(net); err = nft_flow_rule_offload_commit(net); if (err < 0) @@ -11099,6 +11134,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) err = -EAGAIN; break; } + nft_validate_chain_release(net); list_for_each_entry_safe_reverse(trans, next, &nft_net->commit_list, list) { @@ -11311,6 +11347,7 @@ static int nf_tables_abort(struct net *net, struct sk_buff *skb, nft_gc_seq_end(nft_net, gc_seq); WARN_ON_ONCE(!list_empty(&nft_net->commit_list)); + WARN_ON_ONCE(!list_empty(&nft_net->validate_list)); /* module autoload needs to happen after GC sequence update because it * temporarily releases and grabs mutex again. @@ -11969,6 +12006,7 @@ static int __net_init nf_tables_init_net(struct net *net) INIT_LIST_HEAD(&nft_net->binding_list); INIT_LIST_HEAD(&nft_net->module_list); INIT_LIST_HEAD(&nft_net->notify_list); + INIT_LIST_HEAD(&nft_net->validate_list); mutex_init(&nft_net->commit_mutex); nft_net->base_seq = 1; nft_net->gc_seq = 0; @@ -12013,6 +12051,7 @@ static void __net_exit nf_tables_exit_net(struct net *net) WARN_ON_ONCE(!list_empty(&nft_net->module_list)); WARN_ON_ONCE(!list_empty(&nft_net->notify_list)); WARN_ON_ONCE(!list_empty(&nft_net->destroy_list)); + WARN_ON_ONCE(!list_empty(&nft_net->validate_list)); } static void nf_tables_exit_batch(struct list_head *net_exit_list) -- 2.30.2