netfilter-devel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [nft PATCH] src: Implement ip {s,d}addr6 expressions
@ 2025-12-09 15:40 Phil Sutter
  2025-12-09 16:04 ` Florian Westphal
  0 siblings, 1 reply; 2+ messages in thread
From: Phil Sutter @ 2025-12-09 15:40 UTC (permalink / raw)
  To: Pablo Neira Ayuso; +Cc: Florian Westphal, Eric Garver, netfilter-devel

These are pseudo payload expressions which represent an IPv4 packet's
source or destination address as an IPv4-mapped IPv6 address as
described in RFC4291 section 2.5.5.2[1]. It helps sharing ruleset
elements like IP address-based sets/maps between rules for IPv4 and IPv6
traffic.

Internally, this is implemented as a new binop expression which contains
a respective ip {s,d}addr expression. Upon serialization, it resolves
into an immediate expression followed by said payload expression.
Therefore it does not require specific kernel support.

Upon deserialization of an ip {s,d}addr expression, the new construct is
detected by probing for a previous immediate expression loading the
expected value into preceeding registers.

Special casing is needed in binop_expr_clone() and expr_postprocess() to
avoid expr->right dereference and to maintain payload expression's
behaviour regarding OP_EQ printing.

[1] https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.2

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 doc/payload-expression.txt      |   8 ++-
 include/expression.h            |   1 +
 src/evaluate.c                  |   9 +++
 src/expression.c                |  11 +++-
 src/json.c                      |  10 ++++
 src/netlink_delinearize.c       |  34 +++++++++++
 src/netlink_linearize.c         |  23 +++++++
 src/parser_bison.y              |  12 ++++
 src/parser_json.c               |  30 ++++++++--
 src/scanner.l                   |   4 ++
 tests/py/ip/ip.t                |   6 ++
 tests/py/ip/ip.t.json           | 102 ++++++++++++++++++++++++++++++++
 tests/py/ip/ip.t.payload        |  34 +++++++++++
 tests/py/ip/ip.t.payload.bridge |  44 ++++++++++++++
 tests/py/ip/ip.t.payload.inet   |  44 ++++++++++++++
 tests/py/ip/ip.t.payload.netdev |  44 ++++++++++++++
 16 files changed, 408 insertions(+), 8 deletions(-)

diff --git a/doc/payload-expression.txt b/doc/payload-expression.txt
index 8b538968c84b5..e301e2e9b4ce2 100644
--- a/doc/payload-expression.txt
+++ b/doc/payload-expression.txt
@@ -90,7 +90,7 @@ ipv4_addr
 IPV4 HEADER EXPRESSION
 ~~~~~~~~~~~~~~~~~~~~~~
 [verse]
-*ip* {*version* | *hdrlength* | *dscp* | *ecn* | *length* | *id* | *frag-off* | *ttl* | *protocol* | *checksum* | *saddr* | *daddr* }
+*ip* {*version* | *hdrlength* | *dscp* | *ecn* | *length* | *id* | *frag-off* | *ttl* | *protocol* | *checksum* | *saddr* | *daddr* | *saddr6* | *daddr6* }
 
 .IPv4 header expression
 [options="header"]
@@ -132,6 +132,12 @@ ipv4_addr
 |daddr|
 Destination address |
 ipv4_addr
+|saddr6|
+Source address in mapped notation |
+ipv6_addr
+|daddr6|
+Destination address in mapped notation |
+ipv6_addr
 |======================
 
 Careful with matching on *ip length*: The Linux kernel might aggregate several
diff --git a/include/expression.h b/include/expression.h
index a960f8cb8b087..1e494b3f14230 100644
--- a/include/expression.h
+++ b/include/expression.h
@@ -107,6 +107,7 @@ enum ops {
 	OP_LTE,
 	OP_GTE,
 	OP_NEG,
+	OP_V6MAP,
 	__OP_MAX
 };
 #define OP_MAX		(__OP_MAX - 1)
diff --git a/src/evaluate.c b/src/evaluate.c
index 204845554f0b0..1ea859e5143c6 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -1531,6 +1531,15 @@ static int expr_evaluate_binop(struct eval_ctx *ctx, struct expr **expr)
 	unsigned int max_shift_len = ctx->ectx.len;
 	int ret = -1;
 
+	if (op->op == OP_V6MAP) {
+		op->len = sizeof(struct in6_addr) * BITS_PER_BYTE;
+		op->dtype = datatype_get(&ip6addr_type);
+		ret = expr_evaluate(ctx, &op->left);
+		__expr_set_context(&ctx->ectx, op->dtype,
+				   op->byteorder, op->len, 0);
+		return ret;
+	}
+
 	if (ctx->recursion.binop >= USHRT_MAX)
 		return expr_binary_error(ctx->msgs, op, NULL,
 					 "Binary operation limit %u reached ",
diff --git a/src/expression.c b/src/expression.c
index e036c4bb69965..2a8f5153c01d3 100644
--- a/src/expression.c
+++ b/src/expression.c
@@ -854,13 +854,19 @@ bool must_print_eq_op(const struct expr *expr)
 	    expr->right->etype == EXPR_VALUE)
 		return true;
 
-	return expr->left->etype == EXPR_BINOP;
+	return expr->left->etype == EXPR_BINOP &&
+	       expr->left->op != OP_V6MAP;
 }
 
 static void binop_expr_print(const struct expr *expr, struct output_ctx *octx)
 {
 	binop_arg_print(expr, expr->left, octx);
 
+	if (expr->op == OP_V6MAP) {
+		nft_print(octx, "6");
+		return;
+	}
+
 	if (expr_op_symbols[expr->op] &&
 	    (expr->op != OP_EQ || must_print_eq_op(expr)))
 		nft_print(octx, " %s ", expr_op_symbols[expr->op]);
@@ -873,7 +879,8 @@ static void binop_expr_print(const struct expr *expr, struct output_ctx *octx)
 static void binop_expr_clone(struct expr *new, const struct expr *expr)
 {
 	new->left  = expr_clone(expr->left);
-	new->right = expr_clone(expr->right);
+	if (expr->right)
+		new->right = expr_clone(expr->right);
 }
 
 static void binop_expr_destroy(struct expr *expr)
diff --git a/src/json.c b/src/json.c
index 9fb6d715a53de..86bb9d9351262 100644
--- a/src/json.c
+++ b/src/json.c
@@ -668,6 +668,16 @@ __binop_expr_json(int op, const struct expr *expr, struct output_ctx *octx)
 
 json_t *binop_expr_json(const struct expr *expr, struct output_ctx *octx)
 {
+	if (expr->op == OP_V6MAP) {
+		json_t *root = payload_expr_json(expr->left, octx);
+		json_t *payload = json_object_get(root, "payload");
+		const char *field;
+
+		json_unpack(payload, "{s:s}", "field", &field);
+		json_object_set_new(payload, "field",
+				    json_sprintf("%s6", field));
+		return root;
+	}
 	return nft_json_pack("{s:o}", expr_op_symbols[expr->op],
 			 __binop_expr_json(expr->op, expr, octx));
 }
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index 9561e298aebb5..1699b247cb55d 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -17,6 +17,7 @@
 #include <linux/netfilter/nf_nat.h>
 #include <linux/netfilter.h>
 #include <net/ethernet.h>
+#include <netinet/ip.h>
 #include <netlink.h>
 #include <rule.h>
 #include <statement.h>
@@ -649,10 +650,32 @@ static void netlink_parse_byteorder(struct netlink_parse_ctx *ctx,
 	netlink_set_register(ctx, dreg, expr);
 }
 
+static bool payload_is_ip_addr(const struct expr *expr)
+{
+	size_t saddr_offset = offsetof(struct iphdr, saddr) * BITS_PER_BYTE;
+	size_t daddr_offset = offsetof(struct iphdr, daddr) * BITS_PER_BYTE;
+
+	return expr->payload.base == PROTO_BASE_NETWORK_HDR &&
+	       expr->len == sizeof(struct in_addr) * BITS_PER_BYTE &&
+	       (expr->payload.offset == saddr_offset ||
+		expr->payload.offset == daddr_offset);
+}
+
+static bool immediate_is_v6map_pfx(const struct expr *expr)
+{
+	return expr &&
+	       expr->etype == EXPR_VALUE &&
+	       expr->len == (sizeof(struct in6_addr) -
+			     sizeof(struct in_addr)) * BITS_PER_BYTE &&
+	       !mpz_cmp_ui(expr->value, 0xffff);
+}
+
 static void netlink_parse_payload_expr(struct netlink_parse_ctx *ctx,
 				       const struct location *loc,
 				       const struct nftnl_expr *nle)
 {
+	size_t v6map_reg_off = (sizeof(struct in6_addr) -
+				sizeof(struct in_addr)) / NFT_REG32_SIZE;
 	enum nft_registers dreg;
 	uint32_t base, offset, len;
 	struct expr *expr;
@@ -670,6 +693,15 @@ static void netlink_parse_payload_expr(struct netlink_parse_ctx *ctx,
 
 	dreg = netlink_parse_register(nle, NFTNL_EXPR_PAYLOAD_DREG);
 
+	if (dreg >= v6map_reg_off &&
+	    payload_is_ip_addr(expr) &&
+	    immediate_is_v6map_pfx(ctx->registers[dreg - v6map_reg_off])) {
+		expr = binop_expr_alloc(loc, OP_V6MAP, expr, NULL);
+		expr->len = sizeof(struct in6_addr) * BITS_PER_BYTE;
+		expr->dtype = datatype_get(&ip6addr_type);
+		dreg -= v6map_reg_off;
+	}
+
 	if (ctx->inner)
 		ctx->inner_reg = dreg;
 
@@ -2925,6 +2957,8 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
 				return;
 			}
 			break;
+		case OP_V6MAP:
+			return;
 		default:
 			if (expr->right->len > expr->left->len) {
 				expr_set_type(expr->right, expr->left->dtype,
diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
index ac0eaff9a23ca..61577fe06326d 100644
--- a/src/netlink_linearize.c
+++ b/src/netlink_linearize.c
@@ -807,6 +807,26 @@ static void netlink_gen_bitwise_bool(struct netlink_linearize_ctx *ctx,
 	nftnl_rule_add_expr(ctx->nlr, nle);
 }
 
+static void netlink_gen_v6map(struct netlink_linearize_ctx *ctx,
+			      const struct expr *expr, enum nft_registers dreg)
+{
+	size_t pfxlen = sizeof(struct in6_addr) - sizeof(struct in_addr);
+	struct nft_data_linearize nld = {
+		.len		= pfxlen,
+		.value		= { 0, 0, htonl(0xffff) },
+	};
+	struct nftnl_expr *nle;
+
+	nle = alloc_nft_expr("immediate");
+	netlink_put_register(nle, NFTNL_EXPR_IMM_DREG, dreg);
+	nftnl_expr_set_imm(nle, NFTNL_EXPR_IMM_DATA,
+			   nld.value, nld.len, nld.byteorder);
+	nftnl_rule_add_expr(ctx->nlr, nle);
+
+	dreg += netlink_register_space(nld.len * BITS_PER_BYTE);
+	netlink_gen_expr(ctx, expr->left, dreg);
+}
+
 static void netlink_gen_binop(struct netlink_linearize_ctx *ctx,
 			      const struct expr *expr,
 			      enum nft_registers dreg)
@@ -816,6 +836,9 @@ static void netlink_gen_binop(struct netlink_linearize_ctx *ctx,
 	case OP_RSHIFT:
 		netlink_gen_bitwise_shift(ctx, expr, dreg);
 		break;
+	case OP_V6MAP:
+		netlink_gen_v6map(ctx, expr, dreg);
+		break;
 	default:
 		if (expr_is_constant(expr->right))
 			netlink_gen_bitwise_mask_xor(ctx, expr, dreg);
diff --git a/src/parser_bison.y b/src/parser_bison.y
index 3ceef79469d7d..d2dbb5354f0c6 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -403,6 +403,8 @@ int nft_lex(void *, void *, void *);
 %token ETHER			"ether"
 %token SADDR			"saddr"
 %token DADDR			"daddr"
+%token SADDR6			"saddr6"
+%token DADDR6			"daddr6"
 %token TYPE			"type"
 
 %token VLAN			"vlan"
@@ -926,6 +928,7 @@ int nft_lex(void *, void *, void *);
 %type <expr>			ip_hdr_expr	icmp_hdr_expr		igmp_hdr_expr numgen_expr	hash_expr
 %destructor { expr_free($$); }	ip_hdr_expr	icmp_hdr_expr		igmp_hdr_expr numgen_expr	hash_expr
 %type <val>			ip_hdr_field	icmp_hdr_field		igmp_hdr_field
+%type <val>			ip_pseudo_hdr_field
 %type <val>			ip_option_type	ip_option_field
 %type <expr>			ip6_hdr_expr    icmp6_hdr_expr
 %destructor { expr_free($$); }	ip6_hdr_expr	icmp6_hdr_expr
@@ -5956,6 +5959,11 @@ ip_hdr_expr		:	IP	ip_hdr_field	close_scope_ip
 			{
 				$$ = payload_expr_alloc(&@$, &proto_ip, $2);
 			}
+			|	IP	ip_pseudo_hdr_field	close_scope_ip
+			{
+				$$ = payload_expr_alloc(&@$, &proto_ip, $2);
+				$$ = binop_expr_alloc(&@$, OP_V6MAP, $$, NULL);
+			}
 			|	IP	OPTION	ip_option_type ip_option_field	close_scope_ip
 			{
 				$$ = ipopt_expr_alloc(&@$, $3, $4);
@@ -5988,6 +5996,10 @@ ip_hdr_field		:	HDRVERSION	{ $$ = IPHDR_VERSION; }
 			|	DADDR		{ $$ = IPHDR_DADDR; }
 			;
 
+ip_pseudo_hdr_field	:	SADDR6		{ $$ = IPHDR_SADDR; }
+			|	DADDR6		{ $$ = IPHDR_DADDR; }
+			;
+
 ip_option_type		:	LSRR		{ $$ = IPOPT_LSRR; }
 			|	RR		{ $$ = IPOPT_RR; }
 			|	SSRR		{ $$ = IPOPT_SSRR; }
diff --git a/src/parser_json.c b/src/parser_json.c
index 7b4f33848ce12..fb330159be659 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -528,6 +528,22 @@ static int json_parse_payload_field(const struct proto_desc *desc,
 	return 1;
 }
 
+static int json_parse_ip_pseudo_hdr_field(const struct proto_desc *desc,
+					  const char *name, int *field)
+{
+	if (desc != &proto_ip)
+		return 1;
+	if (!strcmp(name, "saddr6")) {
+		*field = IPHDR_SADDR;
+		return 0;
+	}
+	if (!strcmp(name, "daddr6")) {
+		*field = IPHDR_DADDR;
+		return 0;
+	}
+	return 1;
+}
+
 static int json_parse_tcp_option_type(const char *name, int *val)
 {
 	unsigned int i;
@@ -715,15 +731,19 @@ static struct expr *json_parse_payload_expr(struct json_ctx *ctx,
 				   protocol);
 			return NULL;
 		}
-		if (json_parse_payload_field(proto, field, &val)) {
+		if (!json_parse_payload_field(proto, field, &val)) {
+			expr = payload_expr_alloc(int_loc, proto, val);
+
+			if (proto == &proto_th)
+				expr->payload.is_raw = true;
+		} else if (!json_parse_ip_pseudo_hdr_field(proto, field, &val)) {
+			expr = payload_expr_alloc(int_loc, proto, val);
+			expr = binop_expr_alloc(int_loc, OP_V6MAP, expr, NULL);
+		} else {
 			json_error(ctx, "Unknown %s field '%s'.",
 				   protocol, field);
 			return NULL;
 		}
-		expr = payload_expr_alloc(int_loc, proto, val);
-
-		if (proto == &proto_th)
-			expr->payload.is_raw = true;
 
 		return expr;
 	}
diff --git a/src/scanner.l b/src/scanner.l
index df8e536be2276..6d9bbb1b289f6 100644
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -491,6 +491,10 @@ addrstring	({macaddr}|{ip4addr}|{ip6addr})
 	"saddr"			{ return SADDR; }
 	"daddr"			{ return DADDR; }
 }
+<SCANSTATE_IP>{
+	"saddr6"		{ return SADDR6; }
+	"daddr6"		{ return DADDR6; }
+}
 "type"			{ scanner_push_start_cond(yyscanner, SCANSTATE_TYPE); return TYPE; }
 "typeof"		{ return TYPEOF; }
 
diff --git a/tests/py/ip/ip.t b/tests/py/ip/ip.t
index 47262d9a43617..32d791ac26db4 100644
--- a/tests/py/ip/ip.t
+++ b/tests/py/ip/ip.t
@@ -110,6 +110,12 @@ ip saddr & 0.0.0.255 < 0.0.0.127;ok
 
 ip saddr & 0xffff0000 == 0xffff0000;ok;ip saddr 255.255.0.0/16
 
+ip saddr6 ::ffff:1.2.3.4;ok
+ip daddr6 ::ffff:1.2.3.4;ok
+ip saddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee };ok
+ip daddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee };ok
+ip saddr6 ::ffff:1.2.3.4 ip daddr 5.6.7.8;ok
+
 ip version 4 ip hdrlength 5;ok
 ip hdrlength 0;ok
 ip hdrlength 15;ok
diff --git a/tests/py/ip/ip.t.json b/tests/py/ip/ip.t.json
index 3c3a12d7117ce..40c1e0728348c 100644
--- a/tests/py/ip/ip.t.json
+++ b/tests/py/ip/ip.t.json
@@ -1350,6 +1350,108 @@
     }
 ]
 
+# ip saddr6 ::ffff:1.2.3.4
+[
+    {
+        "match": {
+            "left": {
+                "payload": {
+                    "field": "saddr6",
+                    "protocol": "ip"
+                }
+            },
+            "op": "==",
+            "right": "::ffff:1.2.3.4"
+        }
+    }
+]
+
+# ip daddr6 ::ffff:1.2.3.4
+[
+    {
+        "match": {
+            "left": {
+                "payload": {
+                    "field": "daddr6",
+                    "protocol": "ip"
+                }
+            },
+            "op": "==",
+            "right": "::ffff:1.2.3.4"
+        }
+    }
+]
+
+# ip saddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee }
+[
+    {
+        "match": {
+            "left": {
+                "payload": {
+                    "field": "saddr6",
+                    "protocol": "ip"
+                }
+            },
+            "op": "==",
+            "right": {
+                "set": [
+                    "::ffff:1.2.3.4",
+                    "feed::c0:ff:ee"
+                ]
+            }
+        }
+    }
+]
+
+# ip daddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee }
+[
+    {
+        "match": {
+            "left": {
+                "payload": {
+                    "field": "daddr6",
+                    "protocol": "ip"
+                }
+            },
+            "op": "==",
+            "right": {
+                "set": [
+                    "::ffff:1.2.3.4",
+                    "feed::c0:ff:ee"
+                ]
+            }
+        }
+    }
+]
+
+# ip saddr6 ::ffff:1.2.3.4 ip daddr 5.6.7.8
+[
+    {
+        "match": {
+            "left": {
+                "payload": {
+                    "field": "saddr6",
+                    "protocol": "ip"
+                }
+            },
+            "op": "==",
+            "right": "::ffff:1.2.3.4"
+        }
+    },
+    {
+        "match": {
+            "left": {
+                "payload": {
+                    "field": "daddr",
+                    "protocol": "ip"
+                }
+            },
+            "op": "==",
+            "right": "5.6.7.8"
+        }
+    }
+]
+
 # ip version 4 ip hdrlength 5
 [
     {
diff --git a/tests/py/ip/ip.t.payload b/tests/py/ip/ip.t.payload
index b3442e49e5979..2d488b3e8dd9a 100644
--- a/tests/py/ip/ip.t.payload
+++ b/tests/py/ip/ip.t.payload
@@ -413,6 +413,40 @@ ip test-ip4 input
   [ bitwise reg 1 = ( reg 1 & 0xffff0000 ) ^ 0x00000000 ]
   [ cmp eq reg 1 0xffff0000 ]
 
+# ip saddr6 ::ffff:1.2.3.4
+ip test-ip4 input
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 12 => reg 11 ]
+  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]
+
+# ip daddr6 ::ffff:1.2.3.4
+ip test-ip4 input
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 16 => reg 11 ]
+  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]
+
+# ip saddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee }
+	element 00000000 00000000 0000ffff 01020304	element feed0000 00000000 000000c0 00ff00ee
+ip test-ip4 input
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 12 => reg 11 ]
+  [ lookup reg 1 set __set%d ]
+
+# ip daddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee }
+	element 00000000 00000000 0000ffff 01020304	element feed0000 00000000 000000c0 00ff00ee
+ip test-ip4 input
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 16 => reg 11 ]
+  [ lookup reg 1 set __set%d ]
+
+# ip saddr6 ::ffff:1.2.3.4 ip daddr 5.6.7.8
+ip test-ip4 input
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 12 => reg 11 ]
+  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]
+  [ payload load 4b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x05060708 ]
+
 # ip version 4 ip hdrlength 5
 ip test-ip4 input
   [ payload load 1b @ network header + 0 => reg 1 ]
diff --git a/tests/py/ip/ip.t.payload.bridge b/tests/py/ip/ip.t.payload.bridge
index 9da3fc266f52f..376ad6686e7c8 100644
--- a/tests/py/ip/ip.t.payload.bridge
+++ b/tests/py/ip/ip.t.payload.bridge
@@ -549,6 +549,50 @@ bridge test-bridge input
   [ bitwise reg 1 = ( reg 1 & 0xffff0000 ) ^ 0x00000000 ]
   [ cmp eq reg 1 0xffff0000 ]
 
+# ip saddr6 ::ffff:1.2.3.4
+bridge test-bridge input
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0800 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 12 => reg 11 ]
+  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]
+
+# ip daddr6 ::ffff:1.2.3.4
+bridge test-bridge input
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0800 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 16 => reg 11 ]
+  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]
+
+# ip saddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee }
+	element 00000000 00000000 0000ffff 01020304	element feed0000 00000000 000000c0 00ff00ee
+bridge test-bridge input
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0800 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 12 => reg 11 ]
+  [ lookup reg 1 set __set%d ]
+
+# ip daddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee }
+	element 00000000 00000000 0000ffff 01020304	element feed0000 00000000 000000c0 00ff00ee
+bridge test-bridge input
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0800 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 16 => reg 11 ]
+  [ lookup reg 1 set __set%d ]
+
+# ip saddr6 ::ffff:1.2.3.4 ip daddr 5.6.7.8
+bridge test-bridge input
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0800 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 12 => reg 11 ]
+  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]
+  [ payload load 4b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x05060708 ]
+
 # ip version 4 ip hdrlength 5
 bridge test-bridge input 
   [ meta load protocol => reg 1 ]
diff --git a/tests/py/ip/ip.t.payload.inet b/tests/py/ip/ip.t.payload.inet
index 912ce58aa8bcf..abb73f4668dec 100644
--- a/tests/py/ip/ip.t.payload.inet
+++ b/tests/py/ip/ip.t.payload.inet
@@ -549,6 +549,50 @@ inet test-inet input
   [ bitwise reg 1 = ( reg 1 & 0xffff0000 ) ^ 0x00000000 ]
   [ cmp eq reg 1 0xffff0000 ]
 
+# ip saddr6 ::ffff:1.2.3.4
+inet test-inet input
+  [ meta load nfproto => reg 1 ]
+  [ cmp eq reg 1 0x02 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 12 => reg 11 ]
+  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]
+
+# ip daddr6 ::ffff:1.2.3.4
+inet test-inet input
+  [ meta load nfproto => reg 1 ]
+  [ cmp eq reg 1 0x02 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 16 => reg 11 ]
+  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]
+
+# ip saddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee }
+	element 00000000 00000000 0000ffff 01020304	element feed0000 00000000 000000c0 00ff00ee
+inet test-inet input
+  [ meta load nfproto => reg 1 ]
+  [ cmp eq reg 1 0x02 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 12 => reg 11 ]
+  [ lookup reg 1 set __set%d ]
+
+# ip daddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee }
+	element 00000000 00000000 0000ffff 01020304	element feed0000 00000000 000000c0 00ff00ee
+inet test-inet input
+  [ meta load nfproto => reg 1 ]
+  [ cmp eq reg 1 0x02 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 16 => reg 11 ]
+  [ lookup reg 1 set __set%d ]
+
+# ip saddr6 ::ffff:1.2.3.4 ip daddr 5.6.7.8
+inet test-inet input
+  [ meta load nfproto => reg 1 ]
+  [ cmp eq reg 1 0x02 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 12 => reg 11 ]
+  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]
+  [ payload load 4b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x05060708 ]
+
 # ip version 4 ip hdrlength 5
 inet test-inet input
   [ meta load nfproto => reg 1 ]
diff --git a/tests/py/ip/ip.t.payload.netdev b/tests/py/ip/ip.t.payload.netdev
index 9103ffcc0e8e7..9c8eb0a927338 100644
--- a/tests/py/ip/ip.t.payload.netdev
+++ b/tests/py/ip/ip.t.payload.netdev
@@ -448,6 +448,50 @@ netdev test-netdev ingress
   [ bitwise reg 1 = ( reg 1 & 0xffff0000 ) ^ 0x00000000 ]
   [ cmp eq reg 1 0xffff0000 ]
 
+# ip saddr6 ::ffff:1.2.3.4
+netdev test-netdev ingress
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0800 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 12 => reg 11 ]
+  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]
+
+# ip daddr6 ::ffff:1.2.3.4
+netdev test-netdev ingress
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0800 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 16 => reg 11 ]
+  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]
+
+# ip saddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee }
+	element 00000000 00000000 0000ffff 01020304	element feed0000 00000000 000000c0 00ff00ee
+netdev test-netdev ingress
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0800 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 12 => reg 11 ]
+  [ lookup reg 1 set __set%d ]
+
+# ip daddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee }
+	element 00000000 00000000 0000ffff 01020304	element feed0000 00000000 000000c0 00ff00ee
+netdev test-netdev ingress
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0800 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 16 => reg 11 ]
+  [ lookup reg 1 set __set%d ]
+
+# ip saddr6 ::ffff:1.2.3.4 ip daddr 5.6.7.8
+netdev test-netdev egress
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0800 ]
+  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
+  [ payload load 4b @ network header + 12 => reg 11 ]
+  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]
+  [ payload load 4b @ network header + 16 => reg 1 ]
+  [ cmp eq reg 1 0x05060708 ]
+
 # ip version 4 ip hdrlength 5
 netdev test-netdev ingress 
   [ meta load protocol => reg 1 ]
-- 
2.51.0


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

* Re: [nft PATCH] src: Implement ip {s,d}addr6 expressions
  2025-12-09 15:40 [nft PATCH] src: Implement ip {s,d}addr6 expressions Phil Sutter
@ 2025-12-09 16:04 ` Florian Westphal
  0 siblings, 0 replies; 2+ messages in thread
From: Florian Westphal @ 2025-12-09 16:04 UTC (permalink / raw)
  To: Phil Sutter; +Cc: Pablo Neira Ayuso, Eric Garver, netfilter-devel

Phil Sutter <phil@nwl.cc> wrote:
> These are pseudo payload expressions which represent an IPv4 packet's
> source or destination address as an IPv4-mapped IPv6 address as
> described in RFC4291 section 2.5.5.2[1]. It helps sharing ruleset
> elements like IP address-based sets/maps between rules for IPv4 and IPv6
> traffic.

OK, but why do we need a new keyword for this?

> +ip saddr6 ::ffff:1.2.3.4;ok
> +ip daddr6 ::ffff:1.2.3.4;ok
> +ip saddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee };ok
> +ip daddr6 { ::ffff:1.2.3.4, feed::c0:ff:ee };ok
> +ip saddr6 ::ffff:1.2.3.4 ip daddr 5.6.7.8;ok

None of these examples make sense to me.  How is this useful?

> --- a/tests/py/ip/ip.t.payload
> +++ b/tests/py/ip/ip.t.payload
> @@ -413,6 +413,40 @@ ip test-ip4 input
>    [ bitwise reg 1 = ( reg 1 & 0xffff0000 ) ^ 0x00000000 ]
>    [ cmp eq reg 1 0xffff0000 ]
>  
> +# ip saddr6 ::ffff:1.2.3.4
> +ip test-ip4 input
> +  [ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
> +  [ payload load 4b @ network header + 12 => reg 11 ]
> +  [ cmp eq reg 1 0x00000000 0x00000000 0x0000ffff 0x01020304 ]

Its just a more expensive way to express 'ip saddr 1.2.3.4'?
What would be useful is:

set s {
	typeof ip6 saddr
	...
}

nft add element inet t s { 1.2.3.4 }

... which makes nft autotranslate to '::ffff:1.2.3.4', combined
with

add rule inet t c ip saddr @s ...

... where, instead of rejecting this for the wrong size, autopads
the lookup, i.e.

[ immediate reg 1 0x00000000 0x00000000 0x0000ffff ]
[ payload load 4b @ network header + 12 => reg 11 ]
[ lookup ...


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

end of thread, other threads:[~2025-12-09 16:04 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-09 15:40 [nft PATCH] src: Implement ip {s,d}addr6 expressions Phil Sutter
2025-12-09 16:04 ` Florian Westphal

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).