diff mbox series

[nf-next,v1,4/6] netfilter: nf_tables: add new binding infrastructure

Message ID 20250514214216.828862-5-pablo@netfilter.org
State Changes Requested
Headers show
Series revisiting nf_tables ruleset validation | expand

Commit Message

Pablo Neira Ayuso May 14, 2025, 9:42 p.m. UTC
Add new binding infrastructure to build a graph that relates chains and
sets via jump/goto such as:

- chain to chain, ie. rule jump/goto chain.
- set to chain, ie. set element jump/goto chain.
- chain to set, ie. rule lookup to set.

The binding is composed of two tuples that describe [ from, to ], each
component of this tuple is defined as the object type and the pointer to
the object. This patch adds a hashtable for bindings per table to allow
for lookups of existing bindings in the preparation phase.

The bindings allows to backtrack to the basechain that refers to
this object for validation, and to go forward to check for loops
and to perform the rule validation.

This patch adds interfaces to deactivate/activate these bindings during
the preparation phase. Reference counter of zero tells us this binding
will be removed after this transaction.

This is still a preparation patch, a follow patch uses this infrastructure.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 include/net/netfilter/nf_tables.h |  45 ++++
 net/netfilter/nf_tables_api.c     | 352 ++++++++++++++++++++++++++++++
 2 files changed, 397 insertions(+)

Comments

Phil Sutter May 15, 2025, 12:32 p.m. UTC | #1
On Wed, May 14, 2025 at 11:42:14PM +0200, Pablo Neira Ayuso wrote:
> Add new binding infrastructure to build a graph that relates chains and
> sets via jump/goto such as:
> 
> - chain to chain, ie. rule jump/goto chain.
> - set to chain, ie. set element jump/goto chain.
> - chain to set, ie. rule lookup to set.
> 
> The binding is composed of two tuples that describe [ from, to ], each
> component of this tuple is defined as the object type and the pointer to
> the object. This patch adds a hashtable for bindings per table to allow
> for lookups of existing bindings in the preparation phase.
> 
> The bindings allows to backtrack to the basechain that refers to
> this object for validation, and to go forward to check for loops
> and to perform the rule validation.
> 
> This patch adds interfaces to deactivate/activate these bindings during
> the preparation phase. Reference counter of zero tells us this binding
> will be removed after this transaction.
> 
> This is still a preparation patch, a follow patch uses this infrastructure.
> 
> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
> ---
>  include/net/netfilter/nf_tables.h |  45 ++++
>  net/netfilter/nf_tables_api.c     | 352 ++++++++++++++++++++++++++++++
>  2 files changed, 397 insertions(+)
> 
> diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
> index d391990d1a96..807097746d24 100644
> --- a/include/net/netfilter/nf_tables.h
> +++ b/include/net/netfilter/nf_tables.h
> @@ -585,6 +585,8 @@ struct nft_set_elem_expr {
>  struct nft_set {
>  	struct list_head		list;
>  	struct list_head		bindings;
> +	struct list_head		binding_list;
> +	struct list_head		backbinding_list;
>  	refcount_t			refs;
>  	struct nft_table		*table;
>  	possible_net_t			net;
> @@ -1117,6 +1119,8 @@ struct nft_rule_blob {
>  struct nft_chain {
>  	struct nft_rule_blob		__rcu *blob_gen_0;
>  	struct nft_rule_blob		__rcu *blob_gen_1;
> +	struct list_head		binding_list;
> +	struct list_head		backbinding_list;
>  	struct list_head		rules;
>  	struct list_head		list;
>  	struct list_head		validate_list;

One could get away with just a single list by inserting the reverse ones
up front and appending the forward ones. While traversing, one would
either traverse forward or reverse and stop once the 'to' or 'from'
pointer doesn't match the current chain/set anymore. Maybe not worth to
save the 16B per set and chain, though.

> @@ -1293,6 +1297,7 @@ struct nft_table {
>  	struct list_head		sets;
>  	struct list_head		objects;
>  	struct list_head		flowtables;
> +	struct rhashtable		bindings_ht;
>  	u64				hgenerator;
>  	u64				handle;
>  	u32				use;
> @@ -1628,6 +1633,46 @@ static inline int nft_set_elem_is_dead(const struct nft_set_ext *ext)
>  	return test_bit(NFT_SET_ELEM_DEAD_BIT, word);
>  }
>  
> +enum nft_binding_type {
> +        NFT_BIND_CHAIN	= 0,
> +        NFT_BIND_SET,
> +};
> +
> +/* Bindings. */
> +struct nft_binding_key {
> +	union {
> +		const struct nft_chain	*chain;
> +		const struct nft_set	*set;
> +		const void		*ptr;
> +	};
> +	enum nft_binding_type		type;
> +};
> +
> +struct nft_binding {
> +	struct rhash_head		node;
> +	struct list_head		list;
> +	struct list_head		backlist;
> +	struct nft_binding_key		from;
> +	struct nft_binding_key		to;
> +	uint32_t			use;
> +};
> +
> +struct nft_chain;
> +struct nft_set;
> +
> +int nft_add_chain_binding(struct nft_chain *chain1, struct nft_chain *chain2);
> +void nft_activate_chain_binding(struct nft_chain *chain1, struct nft_chain *chain2);
> +void nft_deactivate_chain_binding(struct nft_chain *chain1, struct nft_chain *chain2);
> +void nft_del_chain_binding(struct nft_chain *chain1, struct nft_chain *chain2);
> +int nft_add_chain_set_binding(struct nft_chain *chain, struct nft_set *set);
> +void nft_deactivate_chain_set_binding(struct nft_chain *chain, struct nft_set *set);
> +void nft_activate_chain_set_binding(struct nft_chain *chain, struct nft_set *set);
> +void nft_del_chain_set_binding(struct nft_chain *chain, struct nft_set *set);
> +int nft_add_set_chain_binding(struct nft_set *set, struct nft_chain *chain);
> +void nft_deactivate_set_chain_binding(struct nft_set *set, struct nft_chain *chain);
> +void nft_activate_set_chain_binding(struct nft_set *set, struct nft_chain *chain);
> +void nft_del_set_chain_binding(struct nft_set *set, struct nft_chain *chain);
> +
>  /**
>   * struct nft_trans - nf_tables object update in transaction
>   *
> diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
> index d35cad55c99b..463ee7b4ec19 100644
> --- a/net/netfilter/nf_tables_api.c
> +++ b/net/netfilter/nf_tables_api.c
> @@ -954,6 +954,347 @@ void __nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg)
>  }
>  EXPORT_SYMBOL_GPL(__nft_reg_track_cancel);
>  
> +struct nft_binding_cmp_key {
> +	const struct nft_binding_key	*from;
> +	const struct nft_binding_key	*to;
> +};
> +
> +static u32 nft_binding_hash(const void *data, u32 len, u32 seed)
> +{
> +	const struct nft_binding_cmp_key *key = data;
> +	unsigned long tuple[4];
> +
> +	tuple[0] = (unsigned long)key->from->ptr;
> +	tuple[1] = (unsigned long)key->from->type;
> +	tuple[2] = (unsigned long)key->to->ptr;
> +	tuple[3] = (unsigned long)key->to->type;
> +
> +	return jhash(tuple, sizeof(tuple), seed);
> +}
> +
> +static u32 nft_binding_hash_obj(const void *data, u32 len, u32 seed)
> +{
> +	const struct nft_binding *binding = data;
> +	unsigned long tuple[4];
> +
> +	tuple[0] = (unsigned long)binding->from.ptr;
> +	tuple[1] = (unsigned long)binding->from.type;
> +	tuple[2] = (unsigned long)binding->to.ptr;
> +	tuple[3] = (unsigned long)binding->to.type;
> +
> +	return jhash(tuple, sizeof(tuple), seed);
> +}
> +
> +static int nft_binding_hash_cmp(struct rhashtable_compare_arg *arg,
> +				const void *ptr)
> +{
> +	const struct nft_binding_cmp_key *key = arg->key;
> +	const struct nft_binding *binding = ptr;
> +
> +	return key->from->ptr != binding->from.ptr ||
> +	       key->from->type != binding->from.type ||
> +	       key->to->ptr != binding->to.ptr ||
> +	       key->to->type != binding->to.type;
> +}
> +
> +static const struct rhashtable_params nft_binding_ht_params = {
> +	.head_offset		= offsetof(struct nft_binding, node),
> +	.hashfn			= nft_binding_hash,
> +	.obj_hashfn		= nft_binding_hash_obj,
> +	.obj_cmpfn		= nft_binding_hash_cmp,
> +	.automatic_shrinking	= true,
> +};
> +
> +static struct nft_binding *nft_binding_lookup(struct nft_table *table,
> +					      const struct nft_binding_key *from,
> +					      const struct nft_binding_key *to)
> +{
> +	struct nft_binding_cmp_key key = {
> +		.from	= from,
> +		.to	= to,
> +	};
> +
> +	return rhashtable_lookup_fast(&table->bindings_ht, &key,
> +				      nft_binding_ht_params);
> +}
> +
> +static void nft_deactivate_binding(struct nft_table *table,
> +				   const struct nft_binding_key *from,
> +				   const struct nft_binding_key *to)

Changing the signature to:

| static void nft_deactivate_binding(struct nft_table *table,
|				     void *from, enum nft_binding_type typef,
|				     void *to, enum nft_binding_type typet)

> +{
> +	struct nft_binding *binding;

And creating the keys here:

|	struct nft_binding_key from = { .ptr = from, .type = typef };
|	struct nft_binding_key to = { .ptr = to, .type = typet };

> +
> +	binding = nft_binding_lookup(table, from, to);
> +	if (WARN_ON_ONCE(!binding))
> +		return;
> +	if (WARN_ON_ONCE(binding->use == 0))
> +		return;
> +
> +	binding->use--;
> +}
> +
> +void nft_deactivate_chain_binding(struct nft_chain *chain1,
> +				  struct nft_chain *chain2)
> +{
> +	struct nft_binding_key from = {
> +		.ptr	= chain1,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +	struct nft_binding_key to = {
> +		.ptr	= chain2,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +
> +	nft_deactivate_binding(chain1->table, &from, &to);
> +}

Would reduce the above and following functions to one-line wrappers,
like:

| {
|	nft_deactivate_binding(chain1->table,
|			       chain1, NFT_BIND_CHAIN,
|			       chain2, NFT_BIND_CHAIN);
| }

This could also be just a macro, too.

> +
> +void nft_deactivate_chain_set_binding(struct nft_chain *chain,
> +				      struct nft_set *set)
> +{
> +	struct nft_binding_key from = {
> +		.ptr	= chain,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +	struct nft_binding_key to = {
> +		.ptr	= set,
> +		.type	= NFT_BIND_SET,
> +	};
> +
> +	nft_deactivate_binding(chain->table, &from, &to);
> +}
> +
> +void nft_deactivate_set_chain_binding(struct nft_set *set,
> +				      struct nft_chain *chain)
> +{
> +	struct nft_binding_key from = {
> +		.ptr	= set,
> +		.type	= NFT_BIND_SET,
> +	};
> +	struct nft_binding_key to = {
> +		.ptr	= chain,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +
> +	nft_deactivate_binding(chain->table, &from, &to);
> +}
> +
> +static void nft_activate_binding(struct nft_table *table,
> +				 const struct nft_binding_key *from,
> +				 const struct nft_binding_key *to)
> +{
> +	struct nft_binding *binding;
> +
> +	binding = nft_binding_lookup(table, from, to);
> +	if (WARN_ON_ONCE(!binding))
> +		return;
> +
> +	binding->use++;
> +}
> +
> +void nft_activate_chain_binding(struct nft_chain *chain1,
> +				struct nft_chain *chain2)
> +{
> +	struct nft_binding_key from = {
> +		.ptr	= chain1,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +	struct nft_binding_key to = {
> +		.ptr	= chain2,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +
> +	nft_activate_binding(chain1->table, &from, &to);
> +}
> +
> +void nft_activate_chain_set_binding(struct nft_chain *chain,
> +				    struct nft_set *set)
> +{
> +	struct nft_binding_key from = {
> +		.ptr	= chain,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +	struct nft_binding_key to = {
> +		.ptr	= set,
> +		.type	= NFT_BIND_SET,
> +	};
> +
> +	nft_activate_binding(chain->table, &from, &to);
> +}
> +
> +void nft_activate_set_chain_binding(struct nft_set *set,
> +				    struct nft_chain *chain)
> +{
> +	struct nft_binding_key from = {
> +		.ptr	= set,
> +		.type	= NFT_BIND_SET,
> +	};
> +	struct nft_binding_key to = {
> +		.ptr	= chain,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +
> +	nft_activate_binding(chain->table, &from, &to);
> +}
> +
> +static void nft_del_binding(struct nft_table *table,
> +			    const struct nft_binding_key *from,
> +			    const struct nft_binding_key *to)
> +{
> +	struct nft_binding *binding;
> +	int err;
> +
> +	binding = nft_binding_lookup(table, from, to);
> +	/* With several references to object, deactivate deals to zero use,
> +	 * then first delete binding call remove it.
> +	 */
> +	if (!binding)
> +		return;
> +
> +	if (binding->use != 0)
> +		return;

The asymmetry between add and del in that add increments the use-counter
while del does not decrement is a bit confusing. I believe it is an
optimization since add is always followed by enable otherwise?

I also wonder, does the code expectedly call del for enabled bindings or
is this a bug and there should be a warning above?

Cheers, Phil

> +
> +	list_del(&binding->list);
> +	list_del(&binding->backlist);
> +
> +	err = rhashtable_remove_fast(&table->bindings_ht,
> +				     &binding->node, nft_binding_ht_params);
> +	if (WARN_ON_ONCE(err < 0))
> +		return;
> +
> +	kfree(binding);
> +}
> +
> +void nft_del_chain_binding(struct nft_chain *chain1, struct nft_chain *chain2)
> +{
> +	struct nft_binding_key from = {
> +		.ptr	= chain1,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +	struct nft_binding_key to = {
> +		.ptr	= chain2,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +
> +	nft_del_binding(chain1->table, &from, &to);
> +}
> +
> +void nft_del_chain_set_binding(struct nft_chain *chain, struct nft_set *set)
> +{
> +	struct nft_binding_key from = {
> +		.ptr	= chain,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +	struct nft_binding_key to = {
> +		.ptr	= set,
> +		.type	= NFT_BIND_SET,
> +	};
> +
> +	nft_del_binding(chain->table, &from, &to);
> +}
> +
> +void nft_del_set_chain_binding(struct nft_set *set, struct nft_chain *chain)
> +{
> +	struct nft_binding_key from = {
> +		.ptr	= set,
> +		.type	= NFT_BIND_SET,
> +	};
> +	struct nft_binding_key to = {
> +		.ptr	= chain,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +
> +	nft_del_binding(chain->table, &from, &to);
> +}
> +
> +static int __nft_add_binding(struct nft_table *table,
> +			     const struct nft_binding_key *from,
> +			     const struct nft_binding_key *to,
> +			     struct list_head *binding_list,
> +			     struct list_head *backbinding_list)
> +{
> +	struct nft_binding *binding;
> +
> +	binding = kzalloc(sizeof(struct nft_binding), GFP_KERNEL);
> +	if (!binding)
> +		return -ENOMEM;
> +
> +	binding->from = *from;
> +	binding->to = *to;
> +	binding->use++;
> +
> +	list_add_tail(&binding->list, binding_list);
> +	list_add_tail(&binding->backlist, backbinding_list);
> +
> +	return rhashtable_insert_fast(&table->bindings_ht, &binding->node,
> +				      nft_binding_ht_params);
> +}
> +
> +static int nft_add_binding(struct nft_table *table,
> +			   const struct nft_binding_key *from,
> +			   const struct nft_binding_key *to,
> +			   struct list_head *binding_list,
> +			   struct list_head *backbinding_list)
> +{
> +	struct nft_binding *binding;
> +
> +	binding = nft_binding_lookup(table, from, to);
> +	if (!binding)
> +		return __nft_add_binding(table, from, to,
> +					 binding_list, backbinding_list);
> +
> +	if (binding->use == UINT_MAX)
> +		return -EOVERFLOW;
> +
> +	binding->use++;
> +
> +	return 0;
> +}
> +
> +int nft_add_chain_binding(struct nft_chain *chain1, struct nft_chain *chain2)
> +{
> +	struct nft_binding_key from = {
> +		.ptr	= chain1,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +	struct nft_binding_key to = {
> +		.ptr	= chain2,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +
> +	return nft_add_binding(chain1->table, &from, &to,
> +			       &chain1->binding_list, &chain2->backbinding_list);
> +}
> +
> +int nft_add_chain_set_binding(struct nft_chain *chain, struct nft_set *set)
> +{
> +	struct nft_binding_key from = {
> +		.ptr	= chain,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +	struct nft_binding_key to = {
> +		.ptr	= set,
> +		.type	= NFT_BIND_SET,
> +	};
> +
> +	return nft_add_binding(chain->table, &from, &to,
> +			       &chain->binding_list, &set->backbinding_list);
> +}
> +
> +int nft_add_set_chain_binding(struct nft_set *set, struct nft_chain *chain)
> +{
> +	struct nft_binding_key from = {
> +		.ptr	= set,
> +		.type	= NFT_BIND_SET,
> +	};
> +	struct nft_binding_key to = {
> +		.ptr	= chain,
> +		.type	= NFT_BIND_CHAIN,
> +	};
> +
> +	return nft_add_binding(chain->table, &from, &to,
> +			       &set->binding_list, &chain->backbinding_list);
> +}
> +
>  /*
>   * Tables
>   */
> @@ -1611,6 +1952,10 @@ static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info,
>  	if (err)
>  		goto err_chain_ht;
>  
> +	err = rhashtable_init(&table->bindings_ht, &nft_binding_ht_params);
> +	if (err)
> +		goto err_binding_ht;
> +
>  	INIT_LIST_HEAD(&table->chains);
>  	INIT_LIST_HEAD(&table->sets);
>  	INIT_LIST_HEAD(&table->objects);
> @@ -1629,6 +1974,8 @@ static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info,
>  	list_add_tail_rcu(&table->list, &nft_net->tables);
>  	return 0;
>  err_trans:
> +	rhashtable_destroy(&table->bindings_ht);
> +err_binding_ht:
>  	rhltable_destroy(&table->chains_ht);
>  err_chain_ht:
>  	kfree(table->udata);
> @@ -1794,6 +2141,7 @@ static void nf_tables_table_destroy(struct nft_table *table)
>  	if (WARN_ON(table->use > 0))
>  		return;
>  
> +	rhashtable_destroy(&table->bindings_ht);
>  	rhltable_destroy(&table->chains_ht);
>  	kfree(table->name);
>  	kfree(table->udata);
> @@ -2691,6 +3039,8 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 policy,
>  	ctx->chain = chain;
>  
>  	INIT_LIST_HEAD(&chain->rules);
> +	INIT_LIST_HEAD(&chain->binding_list);
> +	INIT_LIST_HEAD(&chain->backbinding_list);
>  	chain->handle = nf_tables_alloc_handle(table);
>  	chain->table = table;
>  
> @@ -5523,6 +5873,8 @@ static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info,
>  	}
>  
>  	INIT_LIST_HEAD(&set->bindings);
> +	INIT_LIST_HEAD(&set->binding_list);
> +	INIT_LIST_HEAD(&set->backbinding_list);
>  	INIT_LIST_HEAD(&set->catchall_list);
>  	refcount_set(&set->refs, 1);
>  	set->table = table;
> -- 
> 2.30.2
> 
> 
>
diff mbox series

Patch

diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
index d391990d1a96..807097746d24 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -585,6 +585,8 @@  struct nft_set_elem_expr {
 struct nft_set {
 	struct list_head		list;
 	struct list_head		bindings;
+	struct list_head		binding_list;
+	struct list_head		backbinding_list;
 	refcount_t			refs;
 	struct nft_table		*table;
 	possible_net_t			net;
@@ -1117,6 +1119,8 @@  struct nft_rule_blob {
 struct nft_chain {
 	struct nft_rule_blob		__rcu *blob_gen_0;
 	struct nft_rule_blob		__rcu *blob_gen_1;
+	struct list_head		binding_list;
+	struct list_head		backbinding_list;
 	struct list_head		rules;
 	struct list_head		list;
 	struct list_head		validate_list;
@@ -1293,6 +1297,7 @@  struct nft_table {
 	struct list_head		sets;
 	struct list_head		objects;
 	struct list_head		flowtables;
+	struct rhashtable		bindings_ht;
 	u64				hgenerator;
 	u64				handle;
 	u32				use;
@@ -1628,6 +1633,46 @@  static inline int nft_set_elem_is_dead(const struct nft_set_ext *ext)
 	return test_bit(NFT_SET_ELEM_DEAD_BIT, word);
 }
 
+enum nft_binding_type {
+        NFT_BIND_CHAIN	= 0,
+        NFT_BIND_SET,
+};
+
+/* Bindings. */
+struct nft_binding_key {
+	union {
+		const struct nft_chain	*chain;
+		const struct nft_set	*set;
+		const void		*ptr;
+	};
+	enum nft_binding_type		type;
+};
+
+struct nft_binding {
+	struct rhash_head		node;
+	struct list_head		list;
+	struct list_head		backlist;
+	struct nft_binding_key		from;
+	struct nft_binding_key		to;
+	uint32_t			use;
+};
+
+struct nft_chain;
+struct nft_set;
+
+int nft_add_chain_binding(struct nft_chain *chain1, struct nft_chain *chain2);
+void nft_activate_chain_binding(struct nft_chain *chain1, struct nft_chain *chain2);
+void nft_deactivate_chain_binding(struct nft_chain *chain1, struct nft_chain *chain2);
+void nft_del_chain_binding(struct nft_chain *chain1, struct nft_chain *chain2);
+int nft_add_chain_set_binding(struct nft_chain *chain, struct nft_set *set);
+void nft_deactivate_chain_set_binding(struct nft_chain *chain, struct nft_set *set);
+void nft_activate_chain_set_binding(struct nft_chain *chain, struct nft_set *set);
+void nft_del_chain_set_binding(struct nft_chain *chain, struct nft_set *set);
+int nft_add_set_chain_binding(struct nft_set *set, struct nft_chain *chain);
+void nft_deactivate_set_chain_binding(struct nft_set *set, struct nft_chain *chain);
+void nft_activate_set_chain_binding(struct nft_set *set, struct nft_chain *chain);
+void nft_del_set_chain_binding(struct nft_set *set, struct nft_chain *chain);
+
 /**
  * struct nft_trans - nf_tables object update in transaction
  *
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index d35cad55c99b..463ee7b4ec19 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -954,6 +954,347 @@  void __nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg)
 }
 EXPORT_SYMBOL_GPL(__nft_reg_track_cancel);
 
+struct nft_binding_cmp_key {
+	const struct nft_binding_key	*from;
+	const struct nft_binding_key	*to;
+};
+
+static u32 nft_binding_hash(const void *data, u32 len, u32 seed)
+{
+	const struct nft_binding_cmp_key *key = data;
+	unsigned long tuple[4];
+
+	tuple[0] = (unsigned long)key->from->ptr;
+	tuple[1] = (unsigned long)key->from->type;
+	tuple[2] = (unsigned long)key->to->ptr;
+	tuple[3] = (unsigned long)key->to->type;
+
+	return jhash(tuple, sizeof(tuple), seed);
+}
+
+static u32 nft_binding_hash_obj(const void *data, u32 len, u32 seed)
+{
+	const struct nft_binding *binding = data;
+	unsigned long tuple[4];
+
+	tuple[0] = (unsigned long)binding->from.ptr;
+	tuple[1] = (unsigned long)binding->from.type;
+	tuple[2] = (unsigned long)binding->to.ptr;
+	tuple[3] = (unsigned long)binding->to.type;
+
+	return jhash(tuple, sizeof(tuple), seed);
+}
+
+static int nft_binding_hash_cmp(struct rhashtable_compare_arg *arg,
+				const void *ptr)
+{
+	const struct nft_binding_cmp_key *key = arg->key;
+	const struct nft_binding *binding = ptr;
+
+	return key->from->ptr != binding->from.ptr ||
+	       key->from->type != binding->from.type ||
+	       key->to->ptr != binding->to.ptr ||
+	       key->to->type != binding->to.type;
+}
+
+static const struct rhashtable_params nft_binding_ht_params = {
+	.head_offset		= offsetof(struct nft_binding, node),
+	.hashfn			= nft_binding_hash,
+	.obj_hashfn		= nft_binding_hash_obj,
+	.obj_cmpfn		= nft_binding_hash_cmp,
+	.automatic_shrinking	= true,
+};
+
+static struct nft_binding *nft_binding_lookup(struct nft_table *table,
+					      const struct nft_binding_key *from,
+					      const struct nft_binding_key *to)
+{
+	struct nft_binding_cmp_key key = {
+		.from	= from,
+		.to	= to,
+	};
+
+	return rhashtable_lookup_fast(&table->bindings_ht, &key,
+				      nft_binding_ht_params);
+}
+
+static void nft_deactivate_binding(struct nft_table *table,
+				   const struct nft_binding_key *from,
+				   const struct nft_binding_key *to)
+{
+	struct nft_binding *binding;
+
+	binding = nft_binding_lookup(table, from, to);
+	if (WARN_ON_ONCE(!binding))
+		return;
+	if (WARN_ON_ONCE(binding->use == 0))
+		return;
+
+	binding->use--;
+}
+
+void nft_deactivate_chain_binding(struct nft_chain *chain1,
+				  struct nft_chain *chain2)
+{
+	struct nft_binding_key from = {
+		.ptr	= chain1,
+		.type	= NFT_BIND_CHAIN,
+	};
+	struct nft_binding_key to = {
+		.ptr	= chain2,
+		.type	= NFT_BIND_CHAIN,
+	};
+
+	nft_deactivate_binding(chain1->table, &from, &to);
+}
+
+void nft_deactivate_chain_set_binding(struct nft_chain *chain,
+				      struct nft_set *set)
+{
+	struct nft_binding_key from = {
+		.ptr	= chain,
+		.type	= NFT_BIND_CHAIN,
+	};
+	struct nft_binding_key to = {
+		.ptr	= set,
+		.type	= NFT_BIND_SET,
+	};
+
+	nft_deactivate_binding(chain->table, &from, &to);
+}
+
+void nft_deactivate_set_chain_binding(struct nft_set *set,
+				      struct nft_chain *chain)
+{
+	struct nft_binding_key from = {
+		.ptr	= set,
+		.type	= NFT_BIND_SET,
+	};
+	struct nft_binding_key to = {
+		.ptr	= chain,
+		.type	= NFT_BIND_CHAIN,
+	};
+
+	nft_deactivate_binding(chain->table, &from, &to);
+}
+
+static void nft_activate_binding(struct nft_table *table,
+				 const struct nft_binding_key *from,
+				 const struct nft_binding_key *to)
+{
+	struct nft_binding *binding;
+
+	binding = nft_binding_lookup(table, from, to);
+	if (WARN_ON_ONCE(!binding))
+		return;
+
+	binding->use++;
+}
+
+void nft_activate_chain_binding(struct nft_chain *chain1,
+				struct nft_chain *chain2)
+{
+	struct nft_binding_key from = {
+		.ptr	= chain1,
+		.type	= NFT_BIND_CHAIN,
+	};
+	struct nft_binding_key to = {
+		.ptr	= chain2,
+		.type	= NFT_BIND_CHAIN,
+	};
+
+	nft_activate_binding(chain1->table, &from, &to);
+}
+
+void nft_activate_chain_set_binding(struct nft_chain *chain,
+				    struct nft_set *set)
+{
+	struct nft_binding_key from = {
+		.ptr	= chain,
+		.type	= NFT_BIND_CHAIN,
+	};
+	struct nft_binding_key to = {
+		.ptr	= set,
+		.type	= NFT_BIND_SET,
+	};
+
+	nft_activate_binding(chain->table, &from, &to);
+}
+
+void nft_activate_set_chain_binding(struct nft_set *set,
+				    struct nft_chain *chain)
+{
+	struct nft_binding_key from = {
+		.ptr	= set,
+		.type	= NFT_BIND_SET,
+	};
+	struct nft_binding_key to = {
+		.ptr	= chain,
+		.type	= NFT_BIND_CHAIN,
+	};
+
+	nft_activate_binding(chain->table, &from, &to);
+}
+
+static void nft_del_binding(struct nft_table *table,
+			    const struct nft_binding_key *from,
+			    const struct nft_binding_key *to)
+{
+	struct nft_binding *binding;
+	int err;
+
+	binding = nft_binding_lookup(table, from, to);
+	/* With several references to object, deactivate deals to zero use,
+	 * then first delete binding call remove it.
+	 */
+	if (!binding)
+		return;
+
+	if (binding->use != 0)
+		return;
+
+	list_del(&binding->list);
+	list_del(&binding->backlist);
+
+	err = rhashtable_remove_fast(&table->bindings_ht,
+				     &binding->node, nft_binding_ht_params);
+	if (WARN_ON_ONCE(err < 0))
+		return;
+
+	kfree(binding);
+}
+
+void nft_del_chain_binding(struct nft_chain *chain1, struct nft_chain *chain2)
+{
+	struct nft_binding_key from = {
+		.ptr	= chain1,
+		.type	= NFT_BIND_CHAIN,
+	};
+	struct nft_binding_key to = {
+		.ptr	= chain2,
+		.type	= NFT_BIND_CHAIN,
+	};
+
+	nft_del_binding(chain1->table, &from, &to);
+}
+
+void nft_del_chain_set_binding(struct nft_chain *chain, struct nft_set *set)
+{
+	struct nft_binding_key from = {
+		.ptr	= chain,
+		.type	= NFT_BIND_CHAIN,
+	};
+	struct nft_binding_key to = {
+		.ptr	= set,
+		.type	= NFT_BIND_SET,
+	};
+
+	nft_del_binding(chain->table, &from, &to);
+}
+
+void nft_del_set_chain_binding(struct nft_set *set, struct nft_chain *chain)
+{
+	struct nft_binding_key from = {
+		.ptr	= set,
+		.type	= NFT_BIND_SET,
+	};
+	struct nft_binding_key to = {
+		.ptr	= chain,
+		.type	= NFT_BIND_CHAIN,
+	};
+
+	nft_del_binding(chain->table, &from, &to);
+}
+
+static int __nft_add_binding(struct nft_table *table,
+			     const struct nft_binding_key *from,
+			     const struct nft_binding_key *to,
+			     struct list_head *binding_list,
+			     struct list_head *backbinding_list)
+{
+	struct nft_binding *binding;
+
+	binding = kzalloc(sizeof(struct nft_binding), GFP_KERNEL);
+	if (!binding)
+		return -ENOMEM;
+
+	binding->from = *from;
+	binding->to = *to;
+	binding->use++;
+
+	list_add_tail(&binding->list, binding_list);
+	list_add_tail(&binding->backlist, backbinding_list);
+
+	return rhashtable_insert_fast(&table->bindings_ht, &binding->node,
+				      nft_binding_ht_params);
+}
+
+static int nft_add_binding(struct nft_table *table,
+			   const struct nft_binding_key *from,
+			   const struct nft_binding_key *to,
+			   struct list_head *binding_list,
+			   struct list_head *backbinding_list)
+{
+	struct nft_binding *binding;
+
+	binding = nft_binding_lookup(table, from, to);
+	if (!binding)
+		return __nft_add_binding(table, from, to,
+					 binding_list, backbinding_list);
+
+	if (binding->use == UINT_MAX)
+		return -EOVERFLOW;
+
+	binding->use++;
+
+	return 0;
+}
+
+int nft_add_chain_binding(struct nft_chain *chain1, struct nft_chain *chain2)
+{
+	struct nft_binding_key from = {
+		.ptr	= chain1,
+		.type	= NFT_BIND_CHAIN,
+	};
+	struct nft_binding_key to = {
+		.ptr	= chain2,
+		.type	= NFT_BIND_CHAIN,
+	};
+
+	return nft_add_binding(chain1->table, &from, &to,
+			       &chain1->binding_list, &chain2->backbinding_list);
+}
+
+int nft_add_chain_set_binding(struct nft_chain *chain, struct nft_set *set)
+{
+	struct nft_binding_key from = {
+		.ptr	= chain,
+		.type	= NFT_BIND_CHAIN,
+	};
+	struct nft_binding_key to = {
+		.ptr	= set,
+		.type	= NFT_BIND_SET,
+	};
+
+	return nft_add_binding(chain->table, &from, &to,
+			       &chain->binding_list, &set->backbinding_list);
+}
+
+int nft_add_set_chain_binding(struct nft_set *set, struct nft_chain *chain)
+{
+	struct nft_binding_key from = {
+		.ptr	= set,
+		.type	= NFT_BIND_SET,
+	};
+	struct nft_binding_key to = {
+		.ptr	= chain,
+		.type	= NFT_BIND_CHAIN,
+	};
+
+	return nft_add_binding(chain->table, &from, &to,
+			       &set->binding_list, &chain->backbinding_list);
+}
+
 /*
  * Tables
  */
@@ -1611,6 +1952,10 @@  static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info,
 	if (err)
 		goto err_chain_ht;
 
+	err = rhashtable_init(&table->bindings_ht, &nft_binding_ht_params);
+	if (err)
+		goto err_binding_ht;
+
 	INIT_LIST_HEAD(&table->chains);
 	INIT_LIST_HEAD(&table->sets);
 	INIT_LIST_HEAD(&table->objects);
@@ -1629,6 +1974,8 @@  static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info,
 	list_add_tail_rcu(&table->list, &nft_net->tables);
 	return 0;
 err_trans:
+	rhashtable_destroy(&table->bindings_ht);
+err_binding_ht:
 	rhltable_destroy(&table->chains_ht);
 err_chain_ht:
 	kfree(table->udata);
@@ -1794,6 +2141,7 @@  static void nf_tables_table_destroy(struct nft_table *table)
 	if (WARN_ON(table->use > 0))
 		return;
 
+	rhashtable_destroy(&table->bindings_ht);
 	rhltable_destroy(&table->chains_ht);
 	kfree(table->name);
 	kfree(table->udata);
@@ -2691,6 +3039,8 @@  static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 policy,
 	ctx->chain = chain;
 
 	INIT_LIST_HEAD(&chain->rules);
+	INIT_LIST_HEAD(&chain->binding_list);
+	INIT_LIST_HEAD(&chain->backbinding_list);
 	chain->handle = nf_tables_alloc_handle(table);
 	chain->table = table;
 
@@ -5523,6 +5873,8 @@  static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info,
 	}
 
 	INIT_LIST_HEAD(&set->bindings);
+	INIT_LIST_HEAD(&set->binding_list);
+	INIT_LIST_HEAD(&set->backbinding_list);
 	INIT_LIST_HEAD(&set->catchall_list);
 	refcount_set(&set->refs, 1);
 	set->table = table;