* [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