public inbox for netfilter-devel@vger.kernel.org
 help / color / mirror / Atom feed
* [nft PATCH 0/2] A bit of non-constant binop follow-up
@ 2026-04-02 18:43 Phil Sutter
  2026-04-02 18:43 ` [nft PATCH 1/2] parser_json: Accept non-RHS expressions in binop RHS Phil Sutter
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Phil Sutter @ 2026-04-02 18:43 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel, Dion Bosschieter

When asked about how to translate ebtables' --arp-gratuitous match, I
noticed that basically everything is there already but the parser
rejects it.

While we can't do a simple 'arp saddr ip == arp daddr ip' because cmp
expression requires for one side of the equation to be constant, using
XOR on LHS we can work around this limitation:

arp saddr ip ^ arp daddr ip == 0.0.0.0

Thanks to Jeremy's work on bitwise expression (which one might want to
repeat for cmp), the above is possible in VM code:

[ payload load 4b @ network header + 14 => reg 1 ]
[ payload load 4b @ network header + 24 => reg 2 ]
[ bitwise reg 1 = ( reg 1 ^ reg 2 ) ]
[ cmp eq reg 1 0x00000000 ]

Patch 2 of this series relaxes the parser so it accepts the input.
Basically it undoes an old workaround needed before we introduced start
conditions.

Patch 1 removes a similar restriction in JSON parser. It is needed at
least to accept the JSON equivalent of above match (conversion to JSON
on output was already correct).

Phil Sutter (2):
  parser_json: Accept non-RHS expressions in binop RHS
  parser_bison: Accept non-constant binop on LHS of relationals

 doc/payload-expression.txt        |  6 ++++
 src/parser_bison.y                | 16 +++++-----
 src/parser_json.c                 |  2 +-
 src/scanner.l                     |  2 +-
 tests/py/arp/arp.t                |  4 +++
 tests/py/arp/arp.t.json           | 51 +++++++++++++++++++++++++++++++
 tests/py/arp/arp.t.payload        | 14 +++++++++
 tests/py/arp/arp.t.payload.netdev | 18 +++++++++++
 8 files changed, 104 insertions(+), 9 deletions(-)

-- 
2.51.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [nft PATCH 1/2] parser_json: Accept non-RHS expressions in binop RHS
  2026-04-02 18:43 [nft PATCH 0/2] A bit of non-constant binop follow-up Phil Sutter
@ 2026-04-02 18:43 ` Phil Sutter
  2026-04-02 18:43 ` [nft PATCH 2/2] parser_bison: Accept non-constant binop on LHS of relationals Phil Sutter
  2026-04-03 15:53 ` [nft PATCH 0/2] A bit of non-constant binop follow-up Jeremy Sowden
  2 siblings, 0 replies; 5+ messages in thread
From: Phil Sutter @ 2026-04-02 18:43 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel, Dion Bosschieter

No need to restrict this anymore, binop expressions may contain
non-constant expressions in all places nowadays.

Fixes: 54bfc38c522ba ("src: allow binop expressions with variable right-hand operands")
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 src/parser_json.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/parser_json.c b/src/parser_json.c
index 2f70b9877c6ed..b8b623ce05722 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -1296,7 +1296,7 @@ static struct expr *json_parse_binop_expr(struct json_ctx *ctx,
 		json_error(ctx, "Failed to parse LHS of binop expression.");
 		return NULL;
 	}
-	right = json_parse_rhs_expr(ctx, jright);
+	right = json_parse_primary_expr(ctx, jright);
 	if (!right) {
 		json_error(ctx, "Failed to parse RHS of binop expression.");
 		expr_free(left);
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [nft PATCH 2/2] parser_bison: Accept non-constant binop on LHS of relationals
  2026-04-02 18:43 [nft PATCH 0/2] A bit of non-constant binop follow-up Phil Sutter
  2026-04-02 18:43 ` [nft PATCH 1/2] parser_json: Accept non-RHS expressions in binop RHS Phil Sutter
@ 2026-04-02 18:43 ` Phil Sutter
  2026-04-03 15:53 ` [nft PATCH 0/2] A bit of non-constant binop follow-up Jeremy Sowden
  2 siblings, 0 replies; 5+ messages in thread
From: Phil Sutter @ 2026-04-02 18:43 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: netfilter-devel, Dion Bosschieter

This reverts commit 14a9968a56f8b35138bab172aa7ce796f5d98e03.

Thanks to start conditions, stray "ecn" is no longer recognized by flex
and returned as keyword, so the workaround is not needed anymore. All it
takes is to move it into the block for SCANSTATE_IP and SCANSTATE_IP6 in
scanner.l.

Likewise, keyword_expr no longer has to cover for ECN keyword.

Undoing the commit's workarounds in binop expression parser cases lifts
their restriction which is no longer needed since commit 54bfc38c522ba
("src: allow binop expressions with variable right-hand operands").

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 doc/payload-expression.txt        |  6 ++++
 src/parser_bison.y                | 16 +++++-----
 src/scanner.l                     |  2 +-
 tests/py/arp/arp.t                |  4 +++
 tests/py/arp/arp.t.json           | 51 +++++++++++++++++++++++++++++++
 tests/py/arp/arp.t.payload        | 14 +++++++++
 tests/py/arp/arp.t.payload.netdev | 18 +++++++++++
 7 files changed, 103 insertions(+), 8 deletions(-)

diff --git a/doc/payload-expression.txt b/doc/payload-expression.txt
index 8b538968c84b5..59be68be5e21b 100644
--- a/doc/payload-expression.txt
+++ b/doc/payload-expression.txt
@@ -87,6 +87,12 @@ IPv4 target address|
 ipv4_addr
 |======================
 
+.Using arp header expression
+----------------------------
+# matching on gratuitous ARP packets
+arp saddr ip ^ arp daddr ip == 0.0.0.0
+----------------------------
+
 IPV4 HEADER EXPRESSION
 ~~~~~~~~~~~~~~~~~~~~~~
 [verse]
diff --git a/src/parser_bison.y b/src/parser_bison.y
index 5a334bf0c4997..368d1004c0ec4 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -4466,32 +4466,32 @@ osf_ttl			:	/* empty */	{ $$ = NF_OSF_TTL_TRUE; }
 			;
 
 shift_expr		:	primary_expr
-			|	shift_expr		LSHIFT		primary_rhs_expr
+			|	shift_expr		LSHIFT		primary_expr
 			{
 				$$ = binop_expr_alloc(&@$, OP_LSHIFT, $1, $3);
 			}
-			|	shift_expr		RSHIFT		primary_rhs_expr
+			|	shift_expr		RSHIFT		primary_expr
 			{
 				$$ = binop_expr_alloc(&@$, OP_RSHIFT, $1, $3);
 			}
 			;
 
 and_expr		:	shift_expr
-			|	and_expr		AMPERSAND	shift_rhs_expr
+			|	and_expr		AMPERSAND	shift_expr
 			{
 				$$ = binop_expr_alloc(&@$, OP_AND, $1, $3);
 			}
 			;
 
 exclusive_or_expr	:	and_expr
-			|	exclusive_or_expr	CARET		and_rhs_expr
+			|	exclusive_or_expr	CARET		and_expr
 			{
 				$$ = binop_expr_alloc(&@$, OP_XOR, $1, $3);
 			}
 			;
 
 inclusive_or_expr	:	exclusive_or_expr
-			|	inclusive_or_expr	'|'		exclusive_or_rhs_expr
+			|	inclusive_or_expr	'|'		exclusive_or_expr
 			{
 				$$ = binop_expr_alloc(&@$, OP_OR, $1, $3);
 			}
@@ -5186,6 +5186,10 @@ relational_expr		:	expr	/* implicit */	rhs_expr
 			{
 				$$ = relational_expr_alloc(&@2, $2, $1, $3);
 			}
+			|	expr	relational_op	'(' rhs_expr ')'
+			{
+				$$ = relational_expr_alloc(&@2, $2, $1, $4);
+			}
 			|	expr	relational_op	list_rhs_expr
 			{
 				$$ = relational_expr_alloc(&@2, $2, $1, $3);
@@ -5287,7 +5291,6 @@ keyword_expr		:	ETHER   close_scope_eth { $$ = symbol_value(&@$, "ether"); }
 			|	ARP	close_scope_arp { $$ = symbol_value(&@$, "arp"); }
 			|	DNAT	close_scope_nat	{ $$ = symbol_value(&@$, "dnat"); }
 			|	SNAT	close_scope_nat	{ $$ = symbol_value(&@$, "snat"); }
-			|	ECN			{ $$ = symbol_value(&@$, "ecn"); }
 			|	RESET	close_scope_reset	{ $$ = symbol_value(&@$, "reset"); }
 			|	DESTROY	close_scope_destroy	{ $$ = symbol_value(&@$, "destroy"); }
 			|	ORIGINAL		{ $$ = symbol_value(&@$, "original"); }
@@ -5391,7 +5394,6 @@ primary_rhs_expr	:	symbol_expr		{ $$ = $1; }
 							 BYTEORDER_HOST_ENDIAN,
 							 sizeof(data) * BITS_PER_BYTE, &data);
 			}
-			|	'('	basic_rhs_expr	')'	{ $$ = $2; }
 			;
 
 relational_op		:	EQ		{ $$ = OP_EQ; }
diff --git a/src/scanner.l b/src/scanner.l
index 1b4eb1cf13a47..912788ee0e55b 100644
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -536,8 +536,8 @@ addrstring	({macaddr}|{ip4addr}|{ip6addr})
 }
 <SCANSTATE_IP,SCANSTATE_IP6,SCANSTATE_TYPE>{
 	"dscp"			{ return DSCP; }
+	"ecn"			{ return ECN; }
 }
-"ecn"			{ return ECN; }
 <SCANSTATE_EXPR_UDP,SCANSTATE_IP,SCANSTATE_IP6,SCANSTATE_META,SCANSTATE_TCP,SCANSTATE_SCTP,SCANSTATE_EXPR_SCTP_CHUNK>"length"		{ return LENGTH; }
 <SCANSTATE_EXPR_FRAG,SCANSTATE_IP>{
 	"frag-off"		{ return FRAG_OFF; }
diff --git a/tests/py/arp/arp.t b/tests/py/arp/arp.t
index 222b91cf08457..2c850315c662a 100644
--- a/tests/py/arp/arp.t
+++ b/tests/py/arp/arp.t
@@ -58,3 +58,7 @@ arp saddr ip 192.168.1.1 arp daddr ether fe:ed:00:c0:ff:ee;ok
 arp daddr ether fe:ed:00:c0:ff:ee arp saddr ip 192.168.1.1;ok;arp saddr ip 192.168.1.1 arp daddr ether fe:ed:00:c0:ff:ee
 
 meta iifname "invalid" arp ptype 0x0800 arp htype 1 arp hlen 6 arp plen 4 @nh,192,32 0xc0a88f10 @nh,144,48 set 0x112233445566;ok;iifname "invalid" arp htype 1 arp ptype ip arp hlen 6 arp plen 4 arp daddr ip 192.168.143.16 arp daddr ether set 11:22:33:44:55:66
+
+# gratuitous ARP or not?
+arp saddr ip ^ arp daddr ip == 0.0.0.0;ok
+arp saddr ip ^ arp daddr ip != 0.0.0.0;ok
diff --git a/tests/py/arp/arp.t.json b/tests/py/arp/arp.t.json
index 7ce7609539ba4..ac7ef28d94a2b 100644
--- a/tests/py/arp/arp.t.json
+++ b/tests/py/arp/arp.t.json
@@ -891,3 +891,54 @@
     }
 ]
 
+# arp saddr ip ^ arp daddr ip == 0.0.0.0
+[
+    {
+        "match": {
+            "left": {
+                "^": [
+                    {
+                        "payload": {
+                            "field": "saddr ip",
+                            "protocol": "arp"
+                        }
+                    },
+                    {
+                        "payload": {
+                            "field": "daddr ip",
+                            "protocol": "arp"
+                        }
+                    }
+                ]
+            },
+            "op": "==",
+            "right": "0.0.0.0"
+        }
+    }
+]
+
+# arp saddr ip ^ arp daddr ip != 0.0.0.0
+[
+    {
+        "match": {
+            "left": {
+                "^": [
+                    {
+                        "payload": {
+                            "field": "saddr ip",
+                            "protocol": "arp"
+                        }
+                    },
+                    {
+                        "payload": {
+                            "field": "daddr ip",
+                            "protocol": "arp"
+                        }
+                    }
+                ]
+            },
+            "op": "!=",
+            "right": "0.0.0.0"
+        }
+    }
+]
diff --git a/tests/py/arp/arp.t.payload b/tests/py/arp/arp.t.payload
index e23c540ca8bc1..649bda9739431 100644
--- a/tests/py/arp/arp.t.payload
+++ b/tests/py/arp/arp.t.payload
@@ -254,3 +254,17 @@ arp
 arp 
   [ payload load 10b @ network header + 14 => reg 1 ]
   [ cmp eq reg 1 0xc0a80101 0xfeed00c0 0xffee ]
+
+# arp saddr ip ^ arp daddr ip == 0.0.0.0
+arp test-arp input
+  [ payload load 4b @ network header + 14 => reg 1 ]
+  [ payload load 4b @ network header + 24 => reg 2 ]
+  [ bitwise reg 1 = ( reg 1 ^ reg 2 ) ]
+  [ cmp eq reg 1 0x00000000 ]
+
+# arp saddr ip ^ arp daddr ip != 0.0.0.0
+arp test-arp input
+  [ payload load 4b @ network header + 14 => reg 1 ]
+  [ payload load 4b @ network header + 24 => reg 2 ]
+  [ bitwise reg 1 = ( reg 1 ^ reg 2 ) ]
+  [ cmp neq reg 1 0x00000000 ]
diff --git a/tests/py/arp/arp.t.payload.netdev b/tests/py/arp/arp.t.payload.netdev
index ea238978fa73b..044f2712fd827 100644
--- a/tests/py/arp/arp.t.payload.netdev
+++ b/tests/py/arp/arp.t.payload.netdev
@@ -344,3 +344,21 @@ netdev
   [ cmp eq reg 1 0x0806 ]
   [ payload load 10b @ network header + 14 => reg 1 ]
   [ cmp eq reg 1 0xc0a80101 0xfeed00c0 0xffee ]
+
+# arp saddr ip ^ arp daddr ip == 0.0.0.0
+netdev test-netdev ingress
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0806 ]
+  [ payload load 4b @ network header + 14 => reg 1 ]
+  [ payload load 4b @ network header + 24 => reg 2 ]
+  [ bitwise reg 1 = ( reg 1 ^ reg 2 ) ]
+  [ cmp eq reg 1 0x00000000 ]
+
+# arp saddr ip ^ arp daddr ip != 0.0.0.0
+netdev test-netdev ingress
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0806 ]
+  [ payload load 4b @ network header + 14 => reg 1 ]
+  [ payload load 4b @ network header + 24 => reg 2 ]
+  [ bitwise reg 1 = ( reg 1 ^ reg 2 ) ]
+  [ cmp neq reg 1 0x00000000 ]
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [nft PATCH 0/2] A bit of non-constant binop follow-up
  2026-04-02 18:43 [nft PATCH 0/2] A bit of non-constant binop follow-up Phil Sutter
  2026-04-02 18:43 ` [nft PATCH 1/2] parser_json: Accept non-RHS expressions in binop RHS Phil Sutter
  2026-04-02 18:43 ` [nft PATCH 2/2] parser_bison: Accept non-constant binop on LHS of relationals Phil Sutter
@ 2026-04-03 15:53 ` Jeremy Sowden
  2026-04-04  9:32   ` Phil Sutter
  2 siblings, 1 reply; 5+ messages in thread
From: Jeremy Sowden @ 2026-04-03 15:53 UTC (permalink / raw)
  To: Phil Sutter; +Cc: netfilter-devel

[-- Attachment #1: Type: text/plain, Size: 1820 bytes --]

On 2026-04-02, at 20:43:18 +0200, Phil Sutter wrote:
> When asked about how to translate ebtables' --arp-gratuitous match, I
> noticed that basically everything is there already but the parser
> rejects it.
> 
> While we can't do a simple 'arp saddr ip == arp daddr ip' because cmp
> expression requires for one side of the equation to be constant, using
> XOR on LHS we can work around this limitation:
> 
> arp saddr ip ^ arp daddr ip == 0.0.0.0
> 
> Thanks to Jeremy's work on bitwise expression (which one might want to
> repeat for cmp),

I'll take a look. :)

J.

> the above is possible in VM code:
> 
> [ payload load 4b @ network header + 14 => reg 1 ]
> [ payload load 4b @ network header + 24 => reg 2 ]
> [ bitwise reg 1 = ( reg 1 ^ reg 2 ) ]
> [ cmp eq reg 1 0x00000000 ]
> 
> Patch 2 of this series relaxes the parser so it accepts the input.
> Basically it undoes an old workaround needed before we introduced start
> conditions.
> 
> Patch 1 removes a similar restriction in JSON parser. It is needed at
> least to accept the JSON equivalent of above match (conversion to JSON
> on output was already correct).
> 
> Phil Sutter (2):
>   parser_json: Accept non-RHS expressions in binop RHS
>   parser_bison: Accept non-constant binop on LHS of relationals
> 
>  doc/payload-expression.txt        |  6 ++++
>  src/parser_bison.y                | 16 +++++-----
>  src/parser_json.c                 |  2 +-
>  src/scanner.l                     |  2 +-
>  tests/py/arp/arp.t                |  4 +++
>  tests/py/arp/arp.t.json           | 51 +++++++++++++++++++++++++++++++
>  tests/py/arp/arp.t.payload        | 14 +++++++++
>  tests/py/arp/arp.t.payload.netdev | 18 +++++++++++
>  8 files changed, 104 insertions(+), 9 deletions(-)
> 
> -- 
> 2.51.0
> 
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 931 bytes --]

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [nft PATCH 0/2] A bit of non-constant binop follow-up
  2026-04-03 15:53 ` [nft PATCH 0/2] A bit of non-constant binop follow-up Jeremy Sowden
@ 2026-04-04  9:32   ` Phil Sutter
  0 siblings, 0 replies; 5+ messages in thread
From: Phil Sutter @ 2026-04-04  9:32 UTC (permalink / raw)
  To: Jeremy Sowden; +Cc: netfilter-devel

On Fri, Apr 03, 2026 at 04:53:14PM +0100, Jeremy Sowden wrote:
> On 2026-04-02, at 20:43:18 +0200, Phil Sutter wrote:
> > When asked about how to translate ebtables' --arp-gratuitous match, I
> > noticed that basically everything is there already but the parser
> > rejects it.
> > 
> > While we can't do a simple 'arp saddr ip == arp daddr ip' because cmp
> > expression requires for one side of the equation to be constant, using
> > XOR on LHS we can work around this limitation:
> > 
> > arp saddr ip ^ arp daddr ip == 0.0.0.0
> > 
> > Thanks to Jeremy's work on bitwise expression (which one might want to
> > repeat for cmp),
> 
> I'll take a look. :)

Cool, thanks! The ability for cmp to operate on two registers instead of
one register and payload ("data reg") would allow user space to
implement the above as 'arp saddr ip == arp daddr ip' (without the need
for an internal conversion into XOR). It is not a short-term solution
though due to the needed kernel support.

Cheers, Phil

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-04-04  9:32 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-02 18:43 [nft PATCH 0/2] A bit of non-constant binop follow-up Phil Sutter
2026-04-02 18:43 ` [nft PATCH 1/2] parser_json: Accept non-RHS expressions in binop RHS Phil Sutter
2026-04-02 18:43 ` [nft PATCH 2/2] parser_bison: Accept non-constant binop on LHS of relationals Phil Sutter
2026-04-03 15:53 ` [nft PATCH 0/2] A bit of non-constant binop follow-up Jeremy Sowden
2026-04-04  9:32   ` Phil Sutter

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox