* [RFC PATCH net-next] netfilter: nf_conntrack: add ct expression support for netdev egress chains
@ 2026-10-06 8:58 Hari Chandrakanthan
2026-10-06 11:39 ` Florian Westphal
2026-10-06 14:04 ` Pablo Neira Ayuso
0 siblings, 2 replies; 7+ messages in thread
From: Hari Chandrakanthan @ 2026-10-06 8:58 UTC (permalink / raw)
To: pablo, fw
Cc: phil, netfilter-devel, coreteam, netdev, linux-kernel,
Hari Chandrakanthan
Add support for using the ct expression in nftables netdev egress chains.
This enables QoS policy enforcement at the netdev egress hook by
allowing ct operations such as copying connmark to packet mark.
Add an explicit NFPROTO_NETDEV case in nf_ct_netns_get() and
nf_ct_netns_put() that enables conntrack for IPv4, IPv6 and bridge when a
ct expression is added to a netdev chain.
Restrict ct expression use in the netdev family to egress hooks only, as
the connection entry is not yet available at ingress.
Sharing this change as an RFC, to get feedback. The patch has been tested
by configuring nft rules at netdev egress hook to set ct mark and copy
ct mark into skb->mark. Also, the patch is validated at netdev ingress to
ensure the nft rule with ct mark set action is rejected.
Signed-off-by: Hari Chandrakanthan <hari.chandrakanthan@oss.qualcomm.com>
---
net/netfilter/nf_conntrack_proto.c | 26 ++++++++++++++++++++++++++
net/netfilter/nft_ct.c | 22 ++++++++++++++++++++++
2 files changed, 48 insertions(+)
diff --git a/net/netfilter/nf_conntrack_proto.c b/net/netfilter/nf_conntrack_proto.c
index 7a40e4e0e33e..b8ee46262901 100644
--- a/net/netfilter/nf_conntrack_proto.c
+++ b/net/netfilter/nf_conntrack_proto.c
@@ -587,11 +587,36 @@ static int nf_ct_netns_inet_get(struct net *net)
int nf_ct_netns_get(struct net *net, u8 nfproto)
{
int err;
+ bool bridge_acquired = false;
switch (nfproto) {
case NFPROTO_INET:
err = nf_ct_netns_inet_get(net);
break;
+ case NFPROTO_NETDEV:
+ err = nf_ct_netns_do_get(net, NFPROTO_BRIDGE);
+ if (err < 0) {
+ mutex_lock(&nf_ct_proto_mutex);
+ if (nf_ct_bridge_info) {
+ /* Module present but hook registration failed.*/
+ mutex_unlock(&nf_ct_proto_mutex);
+ return err;
+ }
+ mutex_unlock(&nf_ct_proto_mutex);
+ /* Bridge module absent, netdev egress handles routed
+ * traffic too, bridge conntrack is only needed for
+ * bridged frames.
+ */
+ } else {
+ bridge_acquired = true;
+ }
+ err = nf_ct_netns_inet_get(net);
+ if (err < 0) {
+ if (bridge_acquired)
+ nf_ct_netns_put(net, NFPROTO_BRIDGE);
+ return err;
+ }
+ break;
case NFPROTO_BRIDGE:
err = nf_ct_netns_do_get(net, NFPROTO_BRIDGE);
if (err < 0)
@@ -615,6 +640,7 @@ void nf_ct_netns_put(struct net *net, uint8_t nfproto)
{
switch (nfproto) {
case NFPROTO_BRIDGE:
+ case NFPROTO_NETDEV:
nf_ct_netns_do_put(net, NFPROTO_BRIDGE);
fallthrough;
case NFPROTO_INET:
diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c
index 3c4c2faa7398..e90e73475b0c 100644
--- a/net/netfilter/nft_ct.c
+++ b/net/netfilter/nft_ct.c
@@ -649,6 +649,15 @@ static void nft_ct_get_destroy(const struct nft_ctx *ctx,
nf_ct_netns_put(ctx->net, ctx->family);
}
+static int nft_ct_validate(const struct nft_ctx *ctx,
+ const struct nft_expr *expr)
+{
+ if (ctx->family != NFPROTO_NETDEV)
+ return 0;
+
+ return nft_chain_validate_hooks(ctx->chain, 1 << NF_NETDEV_EGRESS);
+}
+
static void nft_ct_set_destroy(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
@@ -732,6 +741,7 @@ static const struct nft_expr_ops nft_ct_get_ops = {
.init = nft_ct_get_init,
.destroy = nft_ct_get_destroy,
.dump = nft_ct_get_dump,
+ .validate = nft_ct_validate,
};
#ifdef CONFIG_MITIGATION_RETPOLINE
@@ -742,6 +752,7 @@ static const struct nft_expr_ops nft_ct_get_fast_ops = {
.init = nft_ct_get_init,
.destroy = nft_ct_get_destroy,
.dump = nft_ct_get_dump,
+ .validate = nft_ct_validate,
};
#endif
@@ -752,9 +763,19 @@ static const struct nft_expr_ops nft_ct_set_ops = {
.init = nft_ct_set_init,
.destroy = nft_ct_set_destroy,
.dump = nft_ct_set_dump,
+ .validate = nft_ct_validate,
};
#ifdef CONFIG_NF_CONNTRACK_ZONES
+static int nft_ct_set_zone_validate(const struct nft_ctx *ctx,
+ const struct nft_expr *expr)
+{
+ if (ctx->family == NFPROTO_NETDEV)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
static const struct nft_expr_ops nft_ct_set_zone_ops = {
.type = &nft_ct_type,
.size = NFT_EXPR_SIZE(sizeof(struct nft_ct)),
@@ -762,6 +783,7 @@ static const struct nft_expr_ops nft_ct_set_zone_ops = {
.init = nft_ct_set_init,
.destroy = nft_ct_set_destroy,
.dump = nft_ct_set_dump,
+ .validate = nft_ct_set_zone_validate,
};
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [RFC PATCH net-next] netfilter: nf_conntrack: add ct expression support for netdev egress chains
2026-10-06 8:58 [RFC PATCH net-next] netfilter: nf_conntrack: add ct expression support for netdev egress chains Hari Chandrakanthan
@ 2026-10-06 11:39 ` Florian Westphal
2026-10-12 12:16 ` Hari Chandrakanthan
2026-10-06 14:04 ` Pablo Neira Ayuso
1 sibling, 1 reply; 7+ messages in thread
From: Florian Westphal @ 2026-10-06 11:39 UTC (permalink / raw)
To: Hari Chandrakanthan
Cc: pablo, phil, netfilter-devel, coreteam, netdev, linux-kernel
Hari Chandrakanthan <hari.chandrakanthan@oss.qualcomm.com> wrote:
> Add support for using the ct expression in nftables netdev egress chains.
> This enables QoS policy enforcement at the netdev egress hook by
> allowing ct operations such as copying connmark to packet mark.
Ok so far.
> Add an explicit NFPROTO_NETDEV case in nf_ct_netns_get() and
> nf_ct_netns_put() that enables conntrack for IPv4, IPv6 and bridge when a
> ct expression is added to a netdev chain.
Why? The egress chain is passive (its a 'read property off ct'). why
does it have to turn on conntrack, let alone for bridge too?
> Restrict ct expression use in the netdev family to egress hooks only, as
> the connection entry is not yet available at ingress.
This makes sense.
> Sharing this change as an RFC, to get feedback. The patch has been tested
> by configuring nft rules at netdev egress hook to set ct mark and copy
> ct mark into skb->mark. Also, the patch is validated at netdev ingress to
> ensure the nft rule with ct mark set action is rejected.
>
> Signed-off-by: Hari Chandrakanthan <hari.chandrakanthan@oss.qualcomm.com>
> ---
> net/netfilter/nf_conntrack_proto.c | 26 ++++++++++++++++++++++++++
> net/netfilter/nft_ct.c | 22 ++++++++++++++++++++++
> 2 files changed, 48 insertions(+)
>
> diff --git a/net/netfilter/nf_conntrack_proto.c b/net/netfilter/nf_conntrack_proto.c
> index 7a40e4e0e33e..b8ee46262901 100644
> --- a/net/netfilter/nf_conntrack_proto.c
> +++ b/net/netfilter/nf_conntrack_proto.c
> @@ -587,11 +587,36 @@ static int nf_ct_netns_inet_get(struct net *net)
> int nf_ct_netns_get(struct net *net, u8 nfproto)
> {
> int err;
> + bool bridge_acquired = false;
>
> switch (nfproto) {
> case NFPROTO_INET:
> err = nf_ct_netns_inet_get(net);
> break;
> + case NFPROTO_NETDEV:
> + err = nf_ct_netns_do_get(net, NFPROTO_BRIDGE);
> + if (err < 0) {
> + mutex_lock(&nf_ct_proto_mutex);
> + if (nf_ct_bridge_info) {
> + /* Module present but hook registration failed.*/
> + mutex_unlock(&nf_ct_proto_mutex);
> + return err;
> + }
> + mutex_unlock(&nf_ct_proto_mutex);
> + /* Bridge module absent, netdev egress handles routed
> + * traffic too, bridge conntrack is only needed for
> + * bridged frames.
> + */
> + } else {
> + bridge_acquired = true;
> + }
> + err = nf_ct_netns_inet_get(net);
> + if (err < 0) {
> + if (bridge_acquired)
> + nf_ct_netns_put(net, NFPROTO_BRIDGE);
> + return err;
> + }
> + break;
I don't understand the need for this. Conntrack needs to be enabled to
track, but your use case makes no sense if conntrack isn't being used
already.
> diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c
> index 3c4c2faa7398..e90e73475b0c 100644
> --- a/net/netfilter/nft_ct.c
> +++ b/net/netfilter/nft_ct.c
> @@ -649,6 +649,15 @@ static void nft_ct_get_destroy(const struct nft_ctx *ctx,
> nf_ct_netns_put(ctx->net, ctx->family);
> }
>
> +static int nft_ct_validate(const struct nft_ctx *ctx,
> + const struct nft_expr *expr)
> +{
> + if (ctx->family != NFPROTO_NETDEV)
> + return 0;
> +
> + return nft_chain_validate_hooks(ctx->chain, 1 << NF_NETDEV_EGRESS);
> +}
> +
> static void nft_ct_set_destroy(const struct nft_ctx *ctx,
> const struct nft_expr *expr)
> {
> @@ -732,6 +741,7 @@ static const struct nft_expr_ops nft_ct_get_ops = {
> .init = nft_ct_get_init,
> .destroy = nft_ct_get_destroy,
> .dump = nft_ct_get_dump,
> + .validate = nft_ct_validate,
> };
OK.
> #ifdef CONFIG_MITIGATION_RETPOLINE
> @@ -742,6 +752,7 @@ static const struct nft_expr_ops nft_ct_get_fast_ops = {
> .init = nft_ct_get_init,
> .destroy = nft_ct_get_destroy,
> .dump = nft_ct_get_dump,
> + .validate = nft_ct_validate,
> };
> #endif
OK.
> @@ -752,9 +763,19 @@ static const struct nft_expr_ops nft_ct_set_ops = {
> .init = nft_ct_set_init,
> .destroy = nft_ct_set_destroy,
> .dump = nft_ct_set_dump,
> + .validate = nft_ct_validate,
> };
Not sure. Whats the use case to set connmark, labels etc. at netdev
egress stage? I think use case was to READ those at egress stage
to set skb->mark etc?
> #ifdef CONFIG_NF_CONNTRACK_ZONES
> +static int nft_ct_set_zone_validate(const struct nft_ctx *ctx,
> + const struct nft_expr *expr)
> +{
> + if (ctx->family == NFPROTO_NETDEV)
> + return -EOPNOTSUPP;
> +
> + return 0;
> +}
> +
> static const struct nft_expr_ops nft_ct_set_zone_ops = {
> .type = &nft_ct_type,
> .size = NFT_EXPR_SIZE(sizeof(struct nft_ct)),
> @@ -762,6 +783,7 @@ static const struct nft_expr_ops nft_ct_set_zone_ops = {
> .init = nft_ct_set_init,
> .destroy = nft_ct_set_destroy,
> .dump = nft_ct_set_dump,
> + .validate = nft_ct_set_zone_validate,
Makes sense to reject it.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH net-next] netfilter: nf_conntrack: add ct expression support for netdev egress chains
2026-10-06 8:58 [RFC PATCH net-next] netfilter: nf_conntrack: add ct expression support for netdev egress chains Hari Chandrakanthan
2026-10-06 11:39 ` Florian Westphal
@ 2026-10-06 14:04 ` Pablo Neira Ayuso
2026-10-12 12:28 ` Hari Chandrakanthan
1 sibling, 1 reply; 7+ messages in thread
From: Pablo Neira Ayuso @ 2026-10-06 14:04 UTC (permalink / raw)
To: Hari Chandrakanthan
Cc: fw, phil, netfilter-devel, coreteam, netdev, linux-kernel
On Tue, Oct 06, 2026 at 02:28:44PM +0530, Hari Chandrakanthan wrote:
> Add support for using the ct expression in nftables netdev egress chains.
> This enables QoS policy enforcement at the netdev egress hook by
> allowing ct operations such as copying connmark to packet mark.
>
> Add an explicit NFPROTO_NETDEV case in nf_ct_netns_get() and
> nf_ct_netns_put() that enables conntrack for IPv4, IPv6 and bridge when a
> ct expression is added to a netdev chain.
>
> Restrict ct expression use in the netdev family to egress hooks only, as
> the connection entry is not yet available at ingress.
>
> Sharing this change as an RFC, to get feedback. The patch has been tested
> by configuring nft rules at netdev egress hook to set ct mark and copy
> ct mark into skb->mark. Also, the patch is validated at netdev ingress to
> ensure the nft rule with ct mark set action is rejected.
>
> Signed-off-by: Hari Chandrakanthan <hari.chandrakanthan@oss.qualcomm.com>
> ---
> net/netfilter/nf_conntrack_proto.c | 26 ++++++++++++++++++++++++++
> net/netfilter/nft_ct.c | 22 ++++++++++++++++++++++
> 2 files changed, 48 insertions(+)
>
> diff --git a/net/netfilter/nf_conntrack_proto.c b/net/netfilter/nf_conntrack_proto.c
> index 7a40e4e0e33e..b8ee46262901 100644
> --- a/net/netfilter/nf_conntrack_proto.c
> +++ b/net/netfilter/nf_conntrack_proto.c
> @@ -587,11 +587,36 @@ static int nf_ct_netns_inet_get(struct net *net)
> int nf_ct_netns_get(struct net *net, u8 nfproto)
> {
> int err;
> + bool bridge_acquired = false;
>
> switch (nfproto) {
> case NFPROTO_INET:
> err = nf_ct_netns_inet_get(net);
> break;
> + case NFPROTO_NETDEV:
> + err = nf_ct_netns_do_get(net, NFPROTO_BRIDGE);
Hm. This is pulling in the bridge conntrack hooks.
> + if (err < 0) {
> + mutex_lock(&nf_ct_proto_mutex);
> + if (nf_ct_bridge_info) {
> + /* Module present but hook registration failed.*/
> + mutex_unlock(&nf_ct_proto_mutex);
> + return err;
> + }
> + mutex_unlock(&nf_ct_proto_mutex);
> + /* Bridge module absent, netdev egress handles routed
> + * traffic too, bridge conntrack is only needed for
> + * bridged frames.
> + */
> + } else {
> + bridge_acquired = true;
> + }
> + err = nf_ct_netns_inet_get(net);
And inet too.
I understand this is to deal with a situation where only ct rule is
placed in netdev/egress.
Is it really worth? I would expect users already have rules refering
to ct from either bridge and/or netdev would match here.
It looks a bit like too much to enable them all for the "a ct rule in
netdev/egress only".
Probably better solution would be to enable nf_conntrack_in() from the
netdev hook (just an earlier call), but this also needs to enable
packet defrag from there. Then, enabling all inet and bridge is not
required, because netdev/ingress would handle the creation/lookup of
the conntrack.
> + if (err < 0) {
> + if (bridge_acquired)
> + nf_ct_netns_put(net, NFPROTO_BRIDGE);
> + return err;
> + }
> + break;
> case NFPROTO_BRIDGE:
> err = nf_ct_netns_do_get(net, NFPROTO_BRIDGE);
> if (err < 0)
> @@ -615,6 +640,7 @@ void nf_ct_netns_put(struct net *net, uint8_t nfproto)
> {
> switch (nfproto) {
> case NFPROTO_BRIDGE:
> + case NFPROTO_NETDEV:
> nf_ct_netns_do_put(net, NFPROTO_BRIDGE);
> fallthrough;
> case NFPROTO_INET:
> diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c
> index 3c4c2faa7398..e90e73475b0c 100644
> --- a/net/netfilter/nft_ct.c
> +++ b/net/netfilter/nft_ct.c
> @@ -649,6 +649,15 @@ static void nft_ct_get_destroy(const struct nft_ctx *ctx,
> nf_ct_netns_put(ctx->net, ctx->family);
> }
>
> +static int nft_ct_validate(const struct nft_ctx *ctx,
> + const struct nft_expr *expr)
> +{
> + if (ctx->family != NFPROTO_NETDEV)
> + return 0;
> +
> + return nft_chain_validate_hooks(ctx->chain, 1 << NF_NETDEV_EGRESS);
> +}
> +
> static void nft_ct_set_destroy(const struct nft_ctx *ctx,
> const struct nft_expr *expr)
> {
> @@ -732,6 +741,7 @@ static const struct nft_expr_ops nft_ct_get_ops = {
> .init = nft_ct_get_init,
> .destroy = nft_ct_get_destroy,
> .dump = nft_ct_get_dump,
> + .validate = nft_ct_validate,
> };
>
> #ifdef CONFIG_MITIGATION_RETPOLINE
> @@ -742,6 +752,7 @@ static const struct nft_expr_ops nft_ct_get_fast_ops = {
> .init = nft_ct_get_init,
> .destroy = nft_ct_get_destroy,
> .dump = nft_ct_get_dump,
> + .validate = nft_ct_validate,
> };
> #endif
>
> @@ -752,9 +763,19 @@ static const struct nft_expr_ops nft_ct_set_ops = {
> .init = nft_ct_set_init,
> .destroy = nft_ct_set_destroy,
> .dump = nft_ct_set_dump,
> + .validate = nft_ct_validate,
> };
>
> #ifdef CONFIG_NF_CONNTRACK_ZONES
> +static int nft_ct_set_zone_validate(const struct nft_ctx *ctx,
> + const struct nft_expr *expr)
> +{
> + if (ctx->family == NFPROTO_NETDEV)
> + return -EOPNOTSUPP;
> +
> + return 0;
> +}
> +
> static const struct nft_expr_ops nft_ct_set_zone_ops = {
> .type = &nft_ct_type,
> .size = NFT_EXPR_SIZE(sizeof(struct nft_ct)),
> @@ -762,6 +783,7 @@ static const struct nft_expr_ops nft_ct_set_zone_ops = {
> .init = nft_ct_set_init,
> .destroy = nft_ct_set_destroy,
> .dump = nft_ct_set_dump,
> + .validate = nft_ct_set_zone_validate,
> };
> #endif
>
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH net-next] netfilter: nf_conntrack: add ct expression support for netdev egress chains
2026-10-06 11:39 ` Florian Westphal
@ 2026-10-12 12:16 ` Hari Chandrakanthan
2026-10-12 16:13 ` Florian Westphal
0 siblings, 1 reply; 7+ messages in thread
From: Hari Chandrakanthan @ 2026-10-12 12:16 UTC (permalink / raw)
To: Florian Westphal
Cc: pablo, phil, netfilter-devel, coreteam, netdev, linux-kernel
On 10/6/2026 5:09 PM, Florian Westphal wrote:
> Hari Chandrakanthan<hari.chandrakanthan@oss.qualcomm.com> wrote:
>> Add support for using the ct expression in nftables netdev egress chains.
>> This enables QoS policy enforcement at the netdev egress hook by
>> allowing ct operations such as copying connmark to packet mark.
> Ok so far.
>
>> Add an explicit NFPROTO_NETDEV case in nf_ct_netns_get() and
>> nf_ct_netns_put() that enables conntrack for IPv4, IPv6 and bridge when a
>> ct expression is added to a netdev chain.
> Why? The egress chain is passive (its a 'read property off ct'). why
> does it have to turn on conntrack, let alone for bridge too?
I agree that enabling inet/bridge conntrack solely for netdev/egress
would be unnecessary overhead.
To address this, as suggested by Pablo, we are considering enabling
conntrack lookup/creation at
netdev/ingress. This would allow netdev chains to access conntrack state
without requiring conntrack
to be enabled for the inet or bridge families, avoiding unnecessary
overhead for netdev-only use cases.
Kindly share your thoughts on this approach.
>> Restrict ct expression use in the netdev family to egress hooks only, as
>> the connection entry is not yet available at ingress.
> This makes sense.
>
>> Sharing this change as an RFC, to get feedback. The patch has been tested
>> by configuring nft rules at netdev egress hook to set ct mark and copy
>> ct mark into skb->mark. Also, the patch is validated at netdev ingress to
>> ensure the nft rule with ct mark set action is rejected.
>>
>> Signed-off-by: Hari Chandrakanthan<hari.chandrakanthan@oss.qualcomm.com>
>> ---
>> net/netfilter/nf_conntrack_proto.c | 26 ++++++++++++++++++++++++++
>> net/netfilter/nft_ct.c | 22 ++++++++++++++++++++++
>> 2 files changed, 48 insertions(+)
>>
>> diff --git a/net/netfilter/nf_conntrack_proto.c b/net/netfilter/nf_conntrack_proto.c
>> index 7a40e4e0e33e..b8ee46262901 100644
>> --- a/net/netfilter/nf_conntrack_proto.c
>> +++ b/net/netfilter/nf_conntrack_proto.c
>> @@ -587,11 +587,36 @@ static int nf_ct_netns_inet_get(struct net *net)
>> int nf_ct_netns_get(struct net *net, u8 nfproto)
>> {
>> int err;
>> + bool bridge_acquired = false;
>>
>> switch (nfproto) {
>> case NFPROTO_INET:
>> err = nf_ct_netns_inet_get(net);
>> break;
>> + case NFPROTO_NETDEV:
>> + err = nf_ct_netns_do_get(net, NFPROTO_BRIDGE);
>> + if (err < 0) {
>> + mutex_lock(&nf_ct_proto_mutex);
>> + if (nf_ct_bridge_info) {
>> + /* Module present but hook registration failed.*/
>> + mutex_unlock(&nf_ct_proto_mutex);
>> + return err;
>> + }
>> + mutex_unlock(&nf_ct_proto_mutex);
>> + /* Bridge module absent, netdev egress handles routed
>> + * traffic too, bridge conntrack is only needed for
>> + * bridged frames.
>> + */
>> + } else {
>> + bridge_acquired = true;
>> + }
>> + err = nf_ct_netns_inet_get(net);
>> + if (err < 0) {
>> + if (bridge_acquired)
>> + nf_ct_netns_put(net, NFPROTO_BRIDGE);
>> + return err;
>> + }
>> + break;
> I don't understand the need for this. Conntrack needs to be enabled to
> track, but your use case makes no sense if conntrack isn't being used
> already.
As mentioned in the response to your earlier question, we are looking at
enabling conntrack
creation/lookup at netdev/ingress instead. This would establish the ct
entry earlier in the packet path,
making it available at egress without requiring the netdev family rule
to initialize conntrack for inet and bridge.
The NFPROTO_NETDEV case in nf_ct_netns_get() would then be simplified
accordingly.
>> diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c
>> index 3c4c2faa7398..e90e73475b0c 100644
>> --- a/net/netfilter/nft_ct.c
>> +++ b/net/netfilter/nft_ct.c
>> @@ -649,6 +649,15 @@ static void nft_ct_get_destroy(const struct nft_ctx *ctx,
>> nf_ct_netns_put(ctx->net, ctx->family);
>> }
>>
>> +static int nft_ct_validate(const struct nft_ctx *ctx,
>> + const struct nft_expr *expr)
>> +{
>> + if (ctx->family != NFPROTO_NETDEV)
>> + return 0;
>> +
>> + return nft_chain_validate_hooks(ctx->chain, 1 << NF_NETDEV_EGRESS);
>> +}
>> +
>> static void nft_ct_set_destroy(const struct nft_ctx *ctx,
>> const struct nft_expr *expr)
>> {
>> @@ -732,6 +741,7 @@ static const struct nft_expr_ops nft_ct_get_ops = {
>> .init = nft_ct_get_init,
>> .destroy = nft_ct_get_destroy,
>> .dump = nft_ct_get_dump,
>> + .validate = nft_ct_validate,
>> };
> OK.
>
>> #ifdef CONFIG_MITIGATION_RETPOLINE
>> @@ -742,6 +752,7 @@ static const struct nft_expr_ops nft_ct_get_fast_ops = {
>> .init = nft_ct_get_init,
>> .destroy = nft_ct_get_destroy,
>> .dump = nft_ct_get_dump,
>> + .validate = nft_ct_validate,
>> };
>> #endif
> OK.
>> @@ -752,9 +763,19 @@ static const struct nft_expr_ops nft_ct_set_ops = {
>> .init = nft_ct_set_init,
>> .destroy = nft_ct_set_destroy,
>> .dump = nft_ct_set_dump,
>> + .validate = nft_ct_validate,
>> };
> Not sure. Whats the use case to set connmark, labels etc. at netdev
> egress stage? I think use case was to READ those at egress stage
> to set skb->mark etc?
One use case for setting connmark at netdev/egress is when flow
classification itself is performed only at egress.
For example, in the WiFi QoS (SCS) use case, the Access Point can now
classify the downlink flows
destined for a client at the WLAN netdevice/egress hook.
Classifying traffic at the netdev/egress limits the operation to packets
transmitted through that interface,
without impacting traffic for other interfaces in the system.
The resulting QoS classification metadata can be stored in connmark at
the time of processing the first packet,
allowing subsequent packets belonging to the same flow to reuse the
previously determined classification state,
without affecting traffic on other interfaces.
>> #ifdef CONFIG_NF_CONNTRACK_ZONES
>> +static int nft_ct_set_zone_validate(const struct nft_ctx *ctx,
>> + const struct nft_expr *expr)
>> +{
>> + if (ctx->family == NFPROTO_NETDEV)
>> + return -EOPNOTSUPP;
>> +
>> + return 0;
>> +}
>> +
>> static const struct nft_expr_ops nft_ct_set_zone_ops = {
>> .type = &nft_ct_type,
>> .size = NFT_EXPR_SIZE(sizeof(struct nft_ct)),
>> @@ -762,6 +783,7 @@ static const struct nft_expr_ops nft_ct_set_zone_ops = {
>> .init = nft_ct_set_init,
>> .destroy = nft_ct_set_destroy,
>> .dump = nft_ct_set_dump,
>> + .validate = nft_ct_set_zone_validate,
> Makes sense to reject it.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH net-next] netfilter: nf_conntrack: add ct expression support for netdev egress chains
2026-10-06 14:04 ` Pablo Neira Ayuso
@ 2026-10-12 12:28 ` Hari Chandrakanthan
0 siblings, 0 replies; 7+ messages in thread
From: Hari Chandrakanthan @ 2026-10-12 12:28 UTC (permalink / raw)
To: Pablo Neira Ayuso
Cc: fw, phil, netfilter-devel, coreteam, netdev, linux-kernel
On 10/6/2026 7:34 PM, Pablo Neira Ayuso wrote:
> On Tue, Oct 06, 2026 at 02:28:44PM +0530, Hari Chandrakanthan wrote:
>> Add support for using the ct expression in nftables netdev egress chains.
>> This enables QoS policy enforcement at the netdev egress hook by
>> allowing ct operations such as copying connmark to packet mark.
>>
>> Add an explicit NFPROTO_NETDEV case in nf_ct_netns_get() and
>> nf_ct_netns_put() that enables conntrack for IPv4, IPv6 and bridge when a
>> ct expression is added to a netdev chain.
>>
>> Restrict ct expression use in the netdev family to egress hooks only, as
>> the connection entry is not yet available at ingress.
>>
>> Sharing this change as an RFC, to get feedback. The patch has been tested
>> by configuring nft rules at netdev egress hook to set ct mark and copy
>> ct mark into skb->mark. Also, the patch is validated at netdev ingress to
>> ensure the nft rule with ct mark set action is rejected.
>>
>> Signed-off-by: Hari Chandrakanthan <hari.chandrakanthan@oss.qualcomm.com>
>> ---
>> net/netfilter/nf_conntrack_proto.c | 26 ++++++++++++++++++++++++++
>> net/netfilter/nft_ct.c | 22 ++++++++++++++++++++++
>> 2 files changed, 48 insertions(+)
>>
>> diff --git a/net/netfilter/nf_conntrack_proto.c b/net/netfilter/nf_conntrack_proto.c
>> index 7a40e4e0e33e..b8ee46262901 100644
>> --- a/net/netfilter/nf_conntrack_proto.c
>> +++ b/net/netfilter/nf_conntrack_proto.c
>> @@ -587,11 +587,36 @@ static int nf_ct_netns_inet_get(struct net *net)
>> int nf_ct_netns_get(struct net *net, u8 nfproto)
>> {
>> int err;
>> + bool bridge_acquired = false;
>>
>> switch (nfproto) {
>> case NFPROTO_INET:
>> err = nf_ct_netns_inet_get(net);
>> break;
>> + case NFPROTO_NETDEV:
>> + err = nf_ct_netns_do_get(net, NFPROTO_BRIDGE);
> Hm. This is pulling in the bridge conntrack hooks.
>
>> + if (err < 0) {
>> + mutex_lock(&nf_ct_proto_mutex);
>> + if (nf_ct_bridge_info) {
>> + /* Module present but hook registration failed.*/
>> + mutex_unlock(&nf_ct_proto_mutex);
>> + return err;
>> + }
>> + mutex_unlock(&nf_ct_proto_mutex);
>> + /* Bridge module absent, netdev egress handles routed
>> + * traffic too, bridge conntrack is only needed for
>> + * bridged frames.
>> + */
>> + } else {
>> + bridge_acquired = true;
>> + }
>> + err = nf_ct_netns_inet_get(net);
> And inet too.
>
> I understand this is to deal with a situation where only ct rule is
> placed in netdev/egress.
Yes.
>
> Is it really worth? I would expect users already have rules refering
> to ct from either bridge and/or netdev would match here.
>
> It looks a bit like too much to enable them all for the "a ct rule in
> netdev/egress only".
>
> Probably better solution would be to enable nf_conntrack_in() from the
> netdev hook (just an earlier call), but this also needs to enable
> packet defrag from there. Then, enabling all inet and bridge is not
> required, because netdev/ingress would handle the creation/lookup of
> the conntrack.
Thanks for suggesting the alternate solution.
Yes, we can call nf_conntrack_in() at netdev/ingress and nf_confirm()
at netdev/egress, avoiding the need to enable the inet and bridge
hooks when ct expressions are used only with netdev hooks.
I’ll share tested patches to add this support, including the handling of
packet defrag.
>> + if (err < 0) {
>> + if (bridge_acquired)
>> + nf_ct_netns_put(net, NFPROTO_BRIDGE);
>> + return err;
>> + }
>> + break;
>> case NFPROTO_BRIDGE:
>> err = nf_ct_netns_do_get(net, NFPROTO_BRIDGE);
>> if (err < 0)
>> @@ -615,6 +640,7 @@ void nf_ct_netns_put(struct net *net, uint8_t nfproto)
>> {
>> switch (nfproto) {
>> case NFPROTO_BRIDGE:
>> + case NFPROTO_NETDEV:
>> nf_ct_netns_do_put(net, NFPROTO_BRIDGE);
>> fallthrough;
>> case NFPROTO_INET:
>> diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c
>> index 3c4c2faa7398..e90e73475b0c 100644
>> --- a/net/netfilter/nft_ct.c
>> +++ b/net/netfilter/nft_ct.c
>> @@ -649,6 +649,15 @@ static void nft_ct_get_destroy(const struct nft_ctx *ctx,
>> nf_ct_netns_put(ctx->net, ctx->family);
>> }
>>
>> +static int nft_ct_validate(const struct nft_ctx *ctx,
>> + const struct nft_expr *expr)
>> +{
>> + if (ctx->family != NFPROTO_NETDEV)
>> + return 0;
>> +
>> + return nft_chain_validate_hooks(ctx->chain, 1 << NF_NETDEV_EGRESS);
>> +}
>> +
>> static void nft_ct_set_destroy(const struct nft_ctx *ctx,
>> const struct nft_expr *expr)
>> {
>> @@ -732,6 +741,7 @@ static const struct nft_expr_ops nft_ct_get_ops = {
>> .init = nft_ct_get_init,
>> .destroy = nft_ct_get_destroy,
>> .dump = nft_ct_get_dump,
>> + .validate = nft_ct_validate,
>> };
>>
>> #ifdef CONFIG_MITIGATION_RETPOLINE
>> @@ -742,6 +752,7 @@ static const struct nft_expr_ops nft_ct_get_fast_ops = {
>> .init = nft_ct_get_init,
>> .destroy = nft_ct_get_destroy,
>> .dump = nft_ct_get_dump,
>> + .validate = nft_ct_validate,
>> };
>> #endif
>>
>> @@ -752,9 +763,19 @@ static const struct nft_expr_ops nft_ct_set_ops = {
>> .init = nft_ct_set_init,
>> .destroy = nft_ct_set_destroy,
>> .dump = nft_ct_set_dump,
>> + .validate = nft_ct_validate,
>> };
>>
>> #ifdef CONFIG_NF_CONNTRACK_ZONES
>> +static int nft_ct_set_zone_validate(const struct nft_ctx *ctx,
>> + const struct nft_expr *expr)
>> +{
>> + if (ctx->family == NFPROTO_NETDEV)
>> + return -EOPNOTSUPP;
>> +
>> + return 0;
>> +}
>> +
>> static const struct nft_expr_ops nft_ct_set_zone_ops = {
>> .type = &nft_ct_type,
>> .size = NFT_EXPR_SIZE(sizeof(struct nft_ct)),
>> @@ -762,6 +783,7 @@ static const struct nft_expr_ops nft_ct_set_zone_ops = {
>> .init = nft_ct_set_init,
>> .destroy = nft_ct_set_destroy,
>> .dump = nft_ct_set_dump,
>> + .validate = nft_ct_set_zone_validate,
>> };
>> #endif
>>
>> --
>> 2.34.1
>>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH net-next] netfilter: nf_conntrack: add ct expression support for netdev egress chains
2026-10-12 12:16 ` Hari Chandrakanthan
@ 2026-10-12 16:13 ` Florian Westphal
2026-10-15 14:36 ` Hari Chandrakanthan
0 siblings, 1 reply; 7+ messages in thread
From: Florian Westphal @ 2026-10-12 16:13 UTC (permalink / raw)
To: Hari Chandrakanthan
Cc: pablo, phil, netfilter-devel, coreteam, netdev, linux-kernel
Hari Chandrakanthan <hari.chandrakanthan@oss.qualcomm.com> wrote:
> I agree that enabling inet/bridge conntrack solely for netdev/egress
> would be unnecessary overhead.
Not following. What is 'netdev egress only'?
Where are the skbs coming from, i.e. though what pipeline?
Bridge?
Routing?
> To address this, as suggested by Pablo, we are considering enabling
> conntrack lookup/creation at
> netdev/ingress. This would allow netdev chains to access conntrack state
> without requiring conntrack
> to be enabled for the inet or bridge families, avoiding unnecessary
> overhead for netdev-only use cases.
FWIW bridge conntrack is already problematic (see recent RFC patch
to refag in INPUT, conntrack is pretty much routing-only.
> Kindly share your thoughts on this approach.
I think you'd have to explain your use case.
I thought you only want
skb-> mark = ct->mark
from egress. That would imply something else already enabled conntrack
anyway, else you'd have no conntrack marks set that you could read.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [RFC PATCH net-next] netfilter: nf_conntrack: add ct expression support for netdev egress chains
2026-10-12 16:13 ` Florian Westphal
@ 2026-10-15 14:36 ` Hari Chandrakanthan
0 siblings, 0 replies; 7+ messages in thread
From: Hari Chandrakanthan @ 2026-10-15 14:36 UTC (permalink / raw)
To: Florian Westphal
Cc: pablo, phil, netfilter-devel, coreteam, netdev, linux-kernel
On 10/12/2026 9:43 PM, Florian Westphal wrote:
> Hari Chandrakanthan <hari.chandrakanthan@oss.qualcomm.com> wrote:
>> I agree that enabling inet/bridge conntrack solely for netdev/egress
>> would be unnecessary overhead.
> Not following. What is 'netdev egress only'?
By netdev/egress only, I mean that the nft rule with a ct expression
(e.g., ct mark set) is present only in the egress chain for a netdev,
with no other nft conntrack rules in inet or bridge chains.
For example, it may be possible that only rules similar to the below
nft rules are added at the netdev egress chain, with no other ct rule
in inet or bridge hooks.
nft add table netdev qos_table
nft add chain netdev qos_table wlan0_qos \
{ type filter hook egress device "wlan0" priority 0\; }
nft add rule netdev qos_table wlan0_qos \
ether daddr 00:03:7f:01:50:65 \
ip saddr 192.168.1.10 \
ip daddr 192.168.1.20 \
ip protocol tcp \
tcp sport 12345 \
tcp dport 443 \
ct mark set 0x10
> Where are the skbs coming from, i.e. though what pipeline?
>
> Bridge?
> Routing?
In the use case that we are trying to address, the device is a Wi-Fi
access point router. It receives the classification rules from a Wi-Fi
client device for QoS prioritization of down-link traffic to the client
(n-tuple to Wi-Fi TID mappings) via SCS. We would like to apply these
classification rules on the access point router using nft rules
(ct/skb mark set rules) at the WLAN netdev's egress chain.
The skbs of interest that are processed by these rules at the netdev
egress hook are primarily expected to be forwarded traffic (bridged or
routed), before they reach the netdev egress hook. However, it is
possible that the traffic can also be originated from the device.
>> To address this, as suggested by Pablo, we are considering enabling
>> conntrack lookup/creation at
>> netdev/ingress. This would allow netdev chains to access conntrack state
>> without requiring conntrack
>> to be enabled for the inet or bridge families, avoiding unnecessary
>> overhead for netdev-only use cases.
> FWIW bridge conntrack is already problematic (see recent RFC patch
> to refag in INPUT, conntrack is pretty much routing-only.
>
>> Kindly share your thoughts on this approach.
> I think you'd have to explain your use case.
>
> I thought you only want
>
> skb-> mark = ct->mark
>
> from egress. That would imply something else already enabled conntrack
> anyway, else you'd have no conntrack marks set that you could read.
As mentioned in the previous comment, in the use case that we are
trying to address, the device is a Wi-Fi access point router. It
receives classification rules from a Wi-Fi client device for
prioritizing downlink traffic to the client (n-tuple to Wi-Fi TID
mappings) via SCS. We would like to apply these rules on the access
point router using nft rules (ct/skb mark set rules) at the WLAN
netdev egress chain.
The nft rules at netdev egress chain achieve the below functionality:
1. If connmark is not set on the first skb seen by the rule,
perform flow classification (based on dmac addr and n-tuple) and
store the result in the ct's mark via ct mark set. Typically, this rule
will be skipped when the connmark is already set for the flow.
2. If connmark is already set (for subsequent packets), copy it to
skb->mark. Ideally, only this rule should be processed by the vast
majority of packets for the flow.
There are three advantages we see in this solution for this use case:
1. We wanted to avoid incurring the cost of n-tuple match for every
single packet at the netdev egress hook, by applying the
classification rule (tuple match and connmark set) only for the
first packets until connmark is set.
2. Adding the rules at the egress hook of the netdev restricts the
scope of these rules only to packets egressing the netdev of
interest, without incurring the cost of processing these rules for
traffic egressing on other netdevs in the system or other hooks.
Hence, we preferred adding the connmark set rule in the netdev egress
chain itself, instead of the other hooks (such as
forward/post-routing).
3. The destination MAC address (Wi-Fi client) should also be available
in the skb for rule matching, which is available at the netdev egress hook.
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-15 14:37 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-06 8:58 [RFC PATCH net-next] netfilter: nf_conntrack: add ct expression support for netdev egress chains Hari Chandrakanthan
2026-10-06 11:39 ` Florian Westphal
2026-10-12 12:16 ` Hari Chandrakanthan
2026-10-12 16:13 ` Florian Westphal
2026-10-15 14:36 ` Hari Chandrakanthan
2026-10-06 14:04 ` Pablo Neira Ayuso
2026-10-12 12:28 ` Hari Chandrakanthan
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox