diff mbox series

[nft,v2] expr: make map lookup expression as an argument in vmap statement

Message ID 20240410113154.4273-1-dinhtrason@gmail.com
State New
Headers show
Series [nft,v2] expr: make map lookup expression as an argument in vmap statement | expand

Commit Message

Son Dinh April 10, 2024, 11:31 a.m. UTC
Support nested map lookups combined with vmap lookup as shown
in the example below. This syntax enables flexibility to use the
values of a map as keys for looking up vmap when users have two
distinct maps for different purposes and do not want to alter any
packet-related objects (e.g., packet mark, ct mark, ip fields)
to store the value returned from the first map lookup for the
final vmap lookup.

Command:
   add rule ip table-a ip saddr map @map1 vmap @map2

Output:

   chain table-a {
           ip saddr map @map1 vmap @map2
   }

It also supports multiple map lookups prior to vmap if users need
to use multiple maps for the same query, such as

   chain table-a {
           ip saddr map @map1 map @map2 vmap @map3
   }

Closes: https://bugzilla.netfilter.org/show_bug.cgi?id=1736

Signed-off-by: Son Dinh <dinhtrason@gmail.com>
---
 src/evaluate.c                                |   2 +-
 src/netlink_delinearize.c                     |  12 +-
 src/parser_bison.y                            |   8 +
 src/parser_json.c                             |  10 +-
 tests/py/ip/sets.t                            |   5 +
 tests/py/ip/sets.t.json                       |  24 +++
 tests/py/ip/sets.t.payload.inet               |   9 +
 tests/py/ip/sets.t.payload.ip                 |   6 +
 tests/py/ip/sets.t.payload.netdev             |   8 +
 tests/py/ip6/sets.t                           |   6 +
 tests/py/ip6/sets.t.json                      |  24 +++
 tests/py/ip6/sets.t.payload.inet              |   9 +
 tests/py/ip6/sets.t.payload.ip6               |   6 +
 tests/py/ip6/sets.t.payload.netdev            |   8 +
 .../dumps/map_to_vmap_lookups.json-nft        | 192 ++++++++++++++++++
 .../packetpath/dumps/map_to_vmap_lookups.nft  |  25 +++
 .../testcases/packetpath/map_to_vmap_lookups  |  35 ++++
 17 files changed, 381 insertions(+), 8 deletions(-)
 create mode 100644 tests/shell/testcases/packetpath/dumps/map_to_vmap_lookups.json-nft
 create mode 100644 tests/shell/testcases/packetpath/dumps/map_to_vmap_lookups.nft
 create mode 100755 tests/shell/testcases/packetpath/map_to_vmap_lookups
diff mbox series

Patch

diff --git a/src/evaluate.c b/src/evaluate.c
index 1682ba58..07c26d16 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -2052,7 +2052,7 @@  static int expr_evaluate_map(struct eval_ctx *ctx, struct expr **expr)
 	expr_set_context(&ctx->ectx, NULL, 0);
 	if (expr_evaluate(ctx, &map->map) < 0)
 		return -1;
-	if (expr_is_constant(map->map))
+	if (map->map->etype != EXPR_MAP && expr_is_constant(map->map))
 		return expr_error(ctx->msgs, map->map,
 				  "Map expression can not be constant");
 
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index da9f7a91..f8968a25 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -428,11 +428,13 @@  static void netlink_parse_lookup(struct netlink_parse_ctx *ctx,
 		return netlink_error(ctx, loc,
 				     "Lookup expression has no left hand side");
 
-	if (left->len < set->key->len) {
-		expr_free(left);
-		left = netlink_parse_concat_expr(ctx, loc, sreg, set->key->len);
-		if (left == NULL)
-			return;
+	if (left->etype != EXPR_MAP) {
+		if (left->len < set->key->len) {
+			expr_free(left);
+			left = netlink_parse_concat_expr(ctx, loc, sreg, set->key->len);
+			if (left == NULL)
+				return;
+		}
 	}
 
 	right = set_ref_expr_alloc(loc, set);
diff --git a/src/parser_bison.y b/src/parser_bison.y
index 61bed761..ab2d5a57 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -3254,6 +3254,10 @@  verdict_stmt		:	verdict_expr
 			;
 
 verdict_map_stmt	:	concat_expr	VMAP	verdict_map_expr
+			{
+				$$ = map_expr_alloc(&@$, $1, $3);
+			}
+			|	map_expr	VMAP	verdict_map_expr
 			{
 				$$ = map_expr_alloc(&@$, $1, $3);
 			}
@@ -4579,6 +4583,10 @@  multiton_rhs_expr	:	prefix_rhs_expr
 			;
 
 map_expr		:	concat_expr	MAP	rhs_expr
+			{
+				$$ = map_expr_alloc(&@$, $1, $3);
+			}
+			|	map_expr MAP rhs_expr
 			{
 				$$ = map_expr_alloc(&@$, $1, $3);
 			}
diff --git a/src/parser_json.c b/src/parser_json.c
index efe49494..58492d97 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -1417,13 +1417,19 @@  static struct expr *json_parse_map_expr(struct json_ctx *ctx,
 			    "key", &jkey, "data", &jdata))
 		return NULL;
 
-	key = json_parse_map_lhs_expr(ctx, jkey);
+	if (json_typeof(jkey) == JSON_OBJECT)
+		key = json_parse_expr(ctx, jkey);
+	else
+		key = json_parse_map_lhs_expr(ctx, jkey);
 	if (!key) {
 		json_error(ctx, "Illegal map expression key.");
 		return NULL;
 	}
 
-	data = json_parse_rhs_expr(ctx, jdata);
+	if (json_typeof(jdata) == JSON_STRING)
+		data = json_parse_expr(ctx, jdata);
+	else
+		data = json_parse_rhs_expr(ctx, jdata);
 	if (!data) {
 		json_error(ctx, "Illegal map expression data.");
 		expr_free(key);
diff --git a/tests/py/ip/sets.t b/tests/py/ip/sets.t
index 46d9686b..3ec1460f 100644
--- a/tests/py/ip/sets.t
+++ b/tests/py/ip/sets.t
@@ -66,3 +66,8 @@  ip saddr @set6 drop;ok
 ip saddr vmap { 1.1.1.1 : drop, * : accept };ok
 meta mark set ip saddr map { 1.1.1.1 : 0x00000001, * : 0x00000002 };ok
 
+# test nested map lookups combined with vmap lookup
+!map2 type ipv4_addr : ipv4_addr;ok
+!map3 type ipv4_addr : inet_service;ok
+!map4 type inet_service : verdict;ok
+ip saddr map @map2 map @map3 vmap @map4;ok
\ No newline at end of file
diff --git a/tests/py/ip/sets.t.json b/tests/py/ip/sets.t.json
index 44ca1528..00d81a11 100644
--- a/tests/py/ip/sets.t.json
+++ b/tests/py/ip/sets.t.json
@@ -303,3 +303,27 @@ 
     }
 ]
 
+# ip saddr map @map2 map @map3 vmap @map4
+[
+    {
+        "vmap": {
+            "data": "@map4",
+            "key": {
+                "map": {
+                    "data": "@map3",
+                    "key": {
+                        "map": {
+                            "data": "@map2",
+                            "key": {
+                                "payload": {
+                                    "field": "saddr",
+                                    "protocol": "ip"
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+]
diff --git a/tests/py/ip/sets.t.payload.inet b/tests/py/ip/sets.t.payload.inet
index fd6517a5..7c498e85 100644
--- a/tests/py/ip/sets.t.payload.inet
+++ b/tests/py/ip/sets.t.payload.inet
@@ -104,3 +104,12 @@  inet
   [ payload load 4b @ network header + 12 => reg 1 ]
   [ lookup reg 1 set __map%d dreg 1 ]
   [ meta set mark with reg 1 ]
+
+# ip saddr map @map2 map @map3 vmap @map4
+inet test-inet input
+  [ meta load nfproto => reg 1 ]
+  [ cmp eq reg 1 0x00000002 ]
+  [ payload load 4b @ network header + 12 => reg 1 ]
+  [ lookup reg 1 set map2 dreg 1 ]
+  [ lookup reg 1 set map3 dreg 1 ]
+  [ lookup reg 1 set map4 dreg 0 ]
diff --git a/tests/py/ip/sets.t.payload.ip b/tests/py/ip/sets.t.payload.ip
index d9cc32b6..8a9cf72c 100644
--- a/tests/py/ip/sets.t.payload.ip
+++ b/tests/py/ip/sets.t.payload.ip
@@ -81,3 +81,9 @@  ip test-ip4 input
   [ meta load mark => reg 10 ]
   [ dynset add reg_key 1 set map1 sreg_data 10 ]
 
+# ip saddr map @map2 map @map3 vmap @map4
+ip test-ip4 input
+  [ payload load 4b @ network header + 12 => reg 1 ]
+  [ lookup reg 1 set map2 dreg 1 ]
+  [ lookup reg 1 set map3 dreg 1 ]
+  [ lookup reg 1 set map4 dreg 0 ]
diff --git a/tests/py/ip/sets.t.payload.netdev b/tests/py/ip/sets.t.payload.netdev
index d41b9e8b..295de8d0 100644
--- a/tests/py/ip/sets.t.payload.netdev
+++ b/tests/py/ip/sets.t.payload.netdev
@@ -105,3 +105,11 @@  netdev test-netdev ingress
   [ meta load mark => reg 10 ]
   [ dynset add reg_key 1 set map1 sreg_data 10 ]
 
+# ip saddr map @map2 map @map3 vmap @map4
+netdev test-netdev ingress
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x00000008 ]
+  [ payload load 4b @ network header + 12 => reg 1 ]
+  [ lookup reg 1 set map2 dreg 1 ]
+  [ lookup reg 1 set map3 dreg 1 ]
+  [ lookup reg 1 set map4 dreg 0 ]
diff --git a/tests/py/ip6/sets.t b/tests/py/ip6/sets.t
index 17fd62f5..b038594c 100644
--- a/tests/py/ip6/sets.t
+++ b/tests/py/ip6/sets.t
@@ -46,3 +46,9 @@  add @set5 { ip6 saddr . ip6 daddr };ok
 add @map1 { ip6 saddr . ip6 daddr : meta mark };ok
 
 delete @set5 { ip6 saddr . ip6 daddr };ok
+
+# test nested map lookups combined with vmap lookup
+!map2 type ipv6_addr : ipv6_addr;ok
+!map3 type ipv6_addr : inet_service;ok
+!map4 type inet_service : verdict;ok
+ip6 saddr map @map2 map @map3 vmap @map4;ok
\ No newline at end of file
diff --git a/tests/py/ip6/sets.t.json b/tests/py/ip6/sets.t.json
index 2029d2b5..ee4bf74d 100644
--- a/tests/py/ip6/sets.t.json
+++ b/tests/py/ip6/sets.t.json
@@ -148,3 +148,27 @@ 
     }
 ]
 
+# ip6 saddr map @map2 map @map3 vmap @map4
+[
+    {
+        "vmap": {
+            "data": "@map4",
+            "key": {
+                "map": {
+                    "data": "@map3",
+                    "key": {
+                        "map": {
+                            "data": "@map2",
+                            "key": {
+                                "payload": {
+                                    "field": "saddr",
+                                    "protocol": "ip6"
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+]
diff --git a/tests/py/ip6/sets.t.payload.inet b/tests/py/ip6/sets.t.payload.inet
index 2bbd5573..025fc4b0 100644
--- a/tests/py/ip6/sets.t.payload.inet
+++ b/tests/py/ip6/sets.t.payload.inet
@@ -47,3 +47,12 @@  inet test-inet input
   [ payload load 16b @ network header + 8 => reg 1 ]
   [ payload load 16b @ network header + 24 => reg 2 ]
   [ dynset delete reg_key 1 set set5 ]
+
+# ip6 saddr map @map2 map @map3 vmap @map4
+inet test-inet input
+  [ meta load nfproto => reg 1 ]
+  [ cmp eq reg 1 0x0000000a ]
+  [ payload load 16b @ network header + 8 => reg 1 ]
+  [ lookup reg 1 set map2 dreg 1 ]
+  [ lookup reg 1 set map3 dreg 1 ]
+  [ lookup reg 1 set map4 dreg 0 ]
diff --git a/tests/py/ip6/sets.t.payload.ip6 b/tests/py/ip6/sets.t.payload.ip6
index c59f7b5c..c1a92c8c 100644
--- a/tests/py/ip6/sets.t.payload.ip6
+++ b/tests/py/ip6/sets.t.payload.ip6
@@ -36,3 +36,9 @@  ip6 test-ip6 input
   [ meta load mark => reg 3 ]
   [ dynset add reg_key 1 set map1 sreg_data 3 ]
 
+# ip6 saddr map @map2 map @map3 vmap @map4
+ip6 test-ip6 input
+  [ payload load 16b @ network header + 8 => reg 1 ]
+  [ lookup reg 1 set map2 dreg 1 ]
+  [ lookup reg 1 set map3 dreg 1 ]
+  [ lookup reg 1 set map4 dreg 0 ]
diff --git a/tests/py/ip6/sets.t.payload.netdev b/tests/py/ip6/sets.t.payload.netdev
index 1866d26b..dd232c40 100644
--- a/tests/py/ip6/sets.t.payload.netdev
+++ b/tests/py/ip6/sets.t.payload.netdev
@@ -48,3 +48,11 @@  netdev test-netdev ingress
   [ meta load mark => reg 3 ]
   [ dynset add reg_key 1 set map1 sreg_data 3 ]
 
+# ip6 saddr map @map2 map @map3 vmap @map4
+netdev test-netdev ingress
+  [ meta load protocol => reg 1 ]
+  [ cmp eq reg 1 0x0000dd86 ]
+  [ payload load 16b @ network header + 8 => reg 1 ]
+  [ lookup reg 1 set map2 dreg 1 ]
+  [ lookup reg 1 set map3 dreg 1 ]
+  [ lookup reg 1 set map4 dreg 0 ]
diff --git a/tests/shell/testcases/packetpath/dumps/map_to_vmap_lookups.json-nft b/tests/shell/testcases/packetpath/dumps/map_to_vmap_lookups.json-nft
new file mode 100644
index 00000000..eb911501
--- /dev/null
+++ b/tests/shell/testcases/packetpath/dumps/map_to_vmap_lookups.json-nft
@@ -0,0 +1,192 @@ 
+{
+  "nftables": [
+    {
+      "metainfo": {
+        "version": "VERSION",
+        "release_name": "RELEASE_NAME",
+        "json_schema_version": 1
+      }
+    },
+    {
+      "table": {
+        "family": "ip",
+        "name": "t",
+        "handle": 0
+      }
+    },
+    {
+      "chain": {
+        "family": "ip",
+        "table": "t",
+        "name": "c1",
+        "handle": 0
+      }
+    },
+    {
+      "chain": {
+        "family": "ip",
+        "table": "t",
+        "name": "c2",
+        "handle": 0
+      }
+    },
+    {
+      "chain": {
+        "family": "ip",
+        "table": "t",
+        "name": "c",
+        "handle": 0,
+        "type": "filter",
+        "hook": "input",
+        "prio": 0,
+        "policy": "accept"
+      }
+    },
+    {
+      "map": {
+        "family": "ip",
+        "name": "s1",
+        "table": "t",
+        "type": "ipv4_addr",
+        "handle": 0,
+        "map": "ipv4_addr",
+        "elem": [
+          [
+            "127.0.0.1",
+            "10.0.0.1"
+          ],
+          [
+            "127.0.0.2",
+            "10.0.0.2"
+          ]
+        ]
+      }
+    },
+    {
+      "map": {
+        "family": "ip",
+        "name": "s2",
+        "table": "t",
+        "type": "ipv4_addr",
+        "handle": 0,
+        "map": "verdict",
+        "elem": [
+          [
+            "10.0.0.1",
+            {
+              "goto": {
+                "target": "c1"
+              }
+            }
+          ],
+          [
+            "10.0.0.2",
+            {
+              "goto": {
+                "target": "c2"
+              }
+            }
+          ]
+        ]
+      }
+    },
+    {
+      "rule": {
+        "family": "ip",
+        "table": "t",
+        "chain": "c1",
+        "handle": 0,
+        "expr": [
+          {
+            "counter": {
+              "packets": 1,
+              "bytes": 84
+            }
+          }
+        ]
+      }
+    },
+    {
+      "rule": {
+        "family": "ip",
+        "table": "t",
+        "chain": "c2",
+        "handle": 0,
+        "expr": [
+          {
+            "counter": {
+              "packets": 2,
+              "bytes": 168
+            }
+          }
+        ]
+      }
+    },
+    {
+      "rule": {
+        "family": "ip",
+        "table": "t",
+        "chain": "c",
+        "handle": 0,
+        "expr": [
+          {
+            "match": {
+              "op": "==",
+              "left": {
+                "payload": {
+                  "protocol": "icmp",
+                  "field": "type"
+                }
+              },
+              "right": "echo-request"
+            }
+          },
+          {
+            "vmap": {
+              "key": {
+                "map": {
+                  "key": {
+                    "payload": {
+                      "protocol": "ip",
+                      "field": "daddr"
+                    }
+                  },
+                  "data": "@s1"
+                }
+              },
+              "data": "@s2"
+            }
+          }
+        ]
+      }
+    },
+    {
+      "rule": {
+        "family": "ip",
+        "table": "t",
+        "chain": "c",
+        "handle": 0,
+        "expr": [
+          {
+            "match": {
+              "op": "==",
+              "left": {
+                "payload": {
+                  "protocol": "icmp",
+                  "field": "type"
+                }
+              },
+              "right": "echo-request"
+            }
+          },
+          {
+            "counter": {
+              "packets": 0,
+              "bytes": 0
+            }
+          }
+        ]
+      }
+    }
+  ]
+}
diff --git a/tests/shell/testcases/packetpath/dumps/map_to_vmap_lookups.nft b/tests/shell/testcases/packetpath/dumps/map_to_vmap_lookups.nft
new file mode 100644
index 00000000..362d9570
--- /dev/null
+++ b/tests/shell/testcases/packetpath/dumps/map_to_vmap_lookups.nft
@@ -0,0 +1,25 @@ 
+table ip t {
+	map s1 {
+		type ipv4_addr : ipv4_addr
+		elements = { 127.0.0.1 : 10.0.0.1, 127.0.0.2 : 10.0.0.2 }
+	}
+
+	map s2 {
+		type ipv4_addr : verdict
+		elements = { 10.0.0.1 : goto c1, 10.0.0.2 : goto c2 }
+	}
+
+	chain c1 {
+		counter packets 1 bytes 84
+	}
+
+	chain c2 {
+		counter packets 2 bytes 168
+	}
+
+	chain c {
+		type filter hook input priority filter; policy accept;
+		icmp type echo-request ip daddr map @s1 vmap @s2
+		icmp type echo-request counter packets 0 bytes 0
+	}
+}
diff --git a/tests/shell/testcases/packetpath/map_to_vmap_lookups b/tests/shell/testcases/packetpath/map_to_vmap_lookups
new file mode 100755
index 00000000..2264a3e4
--- /dev/null
+++ b/tests/shell/testcases/packetpath/map_to_vmap_lookups
@@ -0,0 +1,35 @@ 
+#!/bin/bash
+
+set -e
+
+$NFT -f /dev/stdin <<"EOF"
+table ip t {
+	map s1 {
+		type ipv4_addr : ipv4_addr
+		elements = { 127.0.0.1 : 10.0.0.1,  127.0.0.2 : 10.0.0.2 }
+	}
+
+	map s2 {
+		type ipv4_addr : verdict
+		elements = { 10.0.0.1 : goto c1, 10.0.0.2 : goto c2 }
+	}
+
+	chain c1 {
+		counter
+	}
+
+	chain c2 {
+		counter
+	}
+
+	chain c {
+		type filter hook input priority filter;
+		icmp type echo-request ip daddr map @s1 vmap @s2
+		icmp type echo-request counter
+	}
+}
+EOF
+
+ip link set lo up
+ping -q -c 1 127.0.0.1 > /dev/null
+ping -q -c 2 127.0.0.2 > /dev/null