From: Phil Sutter Date: Wed, 20 Mar 2024 14:54:54 +0000 (+0100) Subject: json: Accept more than two operands in binary expressions X-Git-Tag: v1.0.6.1~214 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=40f31b5e56c7b13799c136a9861e38dda6578b37;p=thirdparty%2Fnftables.git json: Accept more than two operands in binary expressions commit 0ac39384fd9e48ff6bcc5605df2cbeb33af64b9e upstream. The most common use case is ORing flags like | syn | ack | rst but nft seems to be fine with less intuitive stuff like | meta mark set ip dscp << 2 << 3 so support all of them. Signed-off-by: Phil Sutter --- diff --git a/doc/libnftables-json.adoc b/doc/libnftables-json.adoc index 6f86d149..1cce0e69 100644 --- a/doc/libnftables-json.adoc +++ b/doc/libnftables-json.adoc @@ -1337,15 +1337,17 @@ Perform kernel Forwarding Information Base lookups. === BINARY OPERATION [verse] -*{ "|": [* 'EXPRESSION'*,* 'EXPRESSION' *] }* -*{ "^": [* 'EXPRESSION'*,* 'EXPRESSION' *] }* -*{ "&": [* 'EXPRESSION'*,* 'EXPRESSION' *] }* -*{ "+<<+": [* 'EXPRESSION'*,* 'EXPRESSION' *] }* -*{ ">>": [* 'EXPRESSION'*,* 'EXPRESSION' *] }* - -All binary operations expect an array of exactly two expressions, of which the +*{ "|": [* 'EXPRESSION'*,* 'EXPRESSIONS' *] }* +*{ "^": [* 'EXPRESSION'*,* 'EXPRESSIONS' *] }* +*{ "&": [* 'EXPRESSION'*,* 'EXPRESSIONS' *] }* +*{ "+<<+": [* 'EXPRESSION'*,* 'EXPRESSIONS' *] }* +*{ ">>": [* 'EXPRESSION'*,* 'EXPRESSIONS' *] }* +'EXPRESSIONS' := 'EXPRESSION' | 'EXPRESSION'*,* 'EXPRESSIONS' + +All binary operations expect an array of at least two expressions, of which the first element denotes the left hand side and the second one the right hand -side. +side. Extra elements are accepted in the given array and appended to the term +accordingly. === VERDICT [verse] diff --git a/src/json.c b/src/json.c index 7e2bf433..f2625046 100644 --- a/src/json.c +++ b/src/json.c @@ -530,11 +530,24 @@ json_t *flagcmp_expr_json(const struct expr *expr, struct output_ctx *octx) "right", expr_print_json(expr->flagcmp.value, octx)); } +static json_t * +__binop_expr_json(int op, const struct expr *expr, struct output_ctx *octx) +{ + json_t *a = json_array(); + + if (expr->etype == EXPR_BINOP && expr->op == op) { + json_array_extend(a, __binop_expr_json(op, expr->left, octx)); + json_array_extend(a, __binop_expr_json(op, expr->right, octx)); + } else { + json_array_append_new(a, expr_print_json(expr, octx)); + } + return a; +} + json_t *binop_expr_json(const struct expr *expr, struct output_ctx *octx) { - return json_pack("{s:[o, o]}", expr_op_symbols[expr->op], - expr_print_json(expr->left, octx), - expr_print_json(expr->right, octx)); + return json_pack("{s:o}", expr_op_symbols[expr->op], + __binop_expr_json(expr->op, expr, octx)); } json_t *relational_expr_json(const struct expr *expr, struct output_ctx *octx) diff --git a/src/parser_json.c b/src/parser_json.c index b59918c0..97e1172c 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -1133,6 +1133,18 @@ static struct expr *json_parse_binop_expr(struct json_ctx *ctx, return NULL; } + if (json_array_size(root) > 2) { + left = json_parse_primary_expr(ctx, json_array_get(root, 0)); + right = json_parse_primary_expr(ctx, json_array_get(root, 1)); + left = binop_expr_alloc(int_loc, thisop, left, right); + for (i = 2; i < json_array_size(root); i++) { + jright = json_array_get(root, i); + right = json_parse_primary_expr(ctx, jright); + left = binop_expr_alloc(int_loc, thisop, left, right); + } + return left; + } + if (json_unpack_err(ctx, root, "[o, o!]", &jleft, &jright)) return NULL; diff --git a/tests/py/inet/tcp.t.json b/tests/py/inet/tcp.t.json index d3a846cf..bd589cf0 100644 --- a/tests/py/inet/tcp.t.json +++ b/tests/py/inet/tcp.t.json @@ -954,12 +954,12 @@ } }, { - "|": [ "fin", { "|": [ "syn", { "|": [ "rst", { "|": [ "psh", { "|": [ "ack", { "|": [ "urg", { "|": [ "ecn", "cwr" ] } ] } ] } ] } ] } ] } ] + "|": [ "fin", "syn", "rst", "psh", "ack", "urg", "ecn", "cwr" ] } ] }, "op": "==", - "right": { "|": [ "fin", { "|": [ "syn", { "|": [ "rst", { "|": [ "psh", { "|": [ "ack", { "|": [ "urg", { "|": [ "ecn", "cwr" ] } ] } ] } ] } ] } ] } ] } + "right": { "|": [ "fin", "syn", "rst", "psh", "ack", "urg", "ecn", "cwr" ] } } } ] @@ -1395,55 +1395,15 @@ "protocol": "tcp" } }, - { - "|": [ - { - "|": [ - { - "|": [ - { - "|": [ - { - "|": [ - "fin", - "syn" - ] - }, - "rst" - ] - }, - "psh" - ] - }, - "ack" - ] - }, - "urg" - ] - } + { "|": [ "fin", "syn", "rst", "psh", "ack", "urg" ] } ] }, "op": "==", "right": { "set": [ - { - "|": [ - { - "|": [ - "fin", - "psh" - ] - }, - "ack" - ] - }, + { "|": [ "fin", "psh", "ack" ] }, "fin", - { - "|": [ - "psh", - "ack" - ] - }, + { "|": [ "psh", "ack" ] }, "ack" ] } @@ -1780,22 +1740,7 @@ "protocol": "tcp" } }, - { - "|": [ - { - "|": [ - { - "|": [ - "fin", - "syn" - ] - }, - "rst" - ] - }, - "ack" - ] - } + { "|": [ "fin", "syn", "rst", "ack" ] } ] }, "op": "!=", diff --git a/tests/py/inet/tcp.t.json.output b/tests/py/inet/tcp.t.json.output index e186e127..3f03c0dd 100644 --- a/tests/py/inet/tcp.t.json.output +++ b/tests/py/inet/tcp.t.json.output @@ -155,27 +155,11 @@ }, { "|": [ - { - "|": [ - { - "|": [ - { - "|": [ - { - "|": [ - "fin", - "syn" - ] - }, - "rst" - ] - }, - "psh" - ] - }, - "ack" - ] - }, + "fin", + "syn", + "rst", + "psh", + "ack", "urg" ] } @@ -187,12 +171,8 @@ "fin", { "|": [ - { - "|": [ - "fin", - "psh" - ] - }, + "fin", + "psh", "ack" ] }, @@ -280,17 +260,9 @@ }, { "|": [ - { - "|": [ - { - "|": [ - "fin", - "syn" - ] - }, - "rst" - ] - }, + "fin", + "syn", + "rst", "ack" ] } @@ -316,17 +288,9 @@ }, { "|": [ - { - "|": [ - { - "|": [ - "fin", - "syn" - ] - }, - "rst" - ] - }, + "fin", + "syn", + "rst", "ack" ] } @@ -352,17 +316,9 @@ }, { "|": [ - { - "|": [ - { - "|": [ - "fin", - "syn" - ] - }, - "rst" - ] - }, + "fin", + "syn", + "rst", "ack" ] } @@ -388,17 +344,9 @@ }, { "|": [ - { - "|": [ - { - "|": [ - "fin", - "syn" - ] - }, - "rst" - ] - }, + "fin", + "syn", + "rst", "ack" ] } @@ -429,17 +377,9 @@ }, { "|": [ - { - "|": [ - { - "|": [ - "fin", - "syn" - ] - }, - "rst" - ] - }, + "fin", + "syn", + "rst", "ack" ] } diff --git a/tests/shell/testcases/nft-f/dumps/0012different_defines_0.json-nft b/tests/shell/testcases/nft-f/dumps/0012different_defines_0.json-nft new file mode 100644 index 00000000..1b2e3420 --- /dev/null +++ b/tests/shell/testcases/nft-f/dumps/0012different_defines_0.json-nft @@ -0,0 +1,778 @@ +{ + "nftables": [ + { + "metainfo": { + "version": "VERSION", + "release_name": "RELEASE_NAME", + "json_schema_version": 1 + } + }, + { + "table": { + "family": "inet", + "name": "t", + "handle": 0 + } + }, + { + "chain": { + "family": "inet", + "table": "t", + "name": "c", + "handle": 0 + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifname" + } + }, + "right": "whatever" + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifname" + } + }, + "right": "whatever" + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iif" + } + }, + "right": "lo" + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oif" + } + }, + "right": "lo" + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iifname" + } + }, + "right": { + "set": [ + "whatever" + ] + } + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "iif" + } + }, + "right": { + "set": [ + "lo" + ] + } + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "mark" + } + }, + "right": 123 + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "in", + "left": { + "ct": { + "key": "state" + } + }, + "right": [ + "established", + "related", + "new" + ] + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "!=", + "left": { + "ct": { + "key": "state" + } + }, + "right": { + "|": [ + "established", + "related", + "new" + ] + } + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "saddr" + } + }, + "right": "10.0.0.0" + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "daddr" + } + }, + "right": "10.0.0.2" + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "saddr" + } + }, + "right": "10.0.0.0" + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip6", + "field": "daddr" + } + }, + "right": "fe0::1" + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip6", + "field": "saddr" + } + }, + "right": "fe0::2" + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "vmap": { + "key": { + "payload": { + "protocol": "ip", + "field": "saddr" + } + }, + "data": { + "set": [ + [ + "10.0.0.0", + { + "drop": null + } + ], + [ + "10.0.0.2", + { + "accept": null + } + ] + ] + } + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "vmap": { + "key": { + "payload": { + "protocol": "ip6", + "field": "daddr" + } + }, + "data": { + "set": [ + [ + "fe0::1", + { + "drop": null + } + ], + [ + "fe0::2", + { + "accept": null + } + ] + ] + } + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "==", + "left": { + "concat": [ + { + "payload": { + "protocol": "ip6", + "field": "saddr" + } + }, + { + "payload": { + "protocol": "ip6", + "field": "nexthdr" + } + } + ] + }, + "right": { + "set": [ + { + "concat": [ + "fe0::2", + "tcp" + ] + }, + { + "concat": [ + "fe0::1", + "udp" + ] + } + ] + } + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "vmap": { + "key": { + "concat": [ + { + "payload": { + "protocol": "ip", + "field": "daddr" + } + }, + { + "meta": { + "key": "iif" + } + } + ] + }, + "data": { + "set": [ + [ + { + "concat": [ + "10.0.0.0", + "lo" + ] + }, + { + "accept": null + } + ] + ] + } + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + "right": { + "range": [ + 100, + 222 + ] + } + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "vmap": { + "key": { + "payload": { + "protocol": "udp", + "field": "dport" + } + }, + "data": { + "set": [ + [ + { + "range": [ + 100, + 222 + ] + }, + { + "accept": null + } + ] + ] + } + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "sport" + } + }, + "right": 1 + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + "right": 1 + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifname" + } + }, + "right": "foobar" + } + }, + { + "queue": { + "num": 0, + "flags": "bypass" + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "sport" + } + }, + "right": 1 + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + "right": 1 + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifname" + } + }, + "right": "foobar" + } + }, + { + "queue": { + "num": { + "range": [ + 1, + 42 + ] + } + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "sport" + } + }, + "right": 1 + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + "right": 1 + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifname" + } + }, + "right": "foobar" + } + }, + { + "queue": { + "num": { + "range": [ + 1, + 42 + ] + }, + "flags": [ + "bypass", + "fanout" + ] + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "sport" + } + }, + "right": 1 + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + "right": 1 + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifname" + } + }, + "right": "foobar" + } + }, + { + "queue": { + "num": { + "symhash": { + "mod": 2 + } + } + } + } + ] + } + }, + { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "handle": 0, + "expr": [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "sport" + } + }, + "right": 1 + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + "right": 1 + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifname" + } + }, + "right": "foobar" + } + }, + { + "queue": { + "num": { + "jhash": { + "mod": 4, + "expr": { + "concat": [ + { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + { + "payload": { + "protocol": "tcp", + "field": "sport" + } + } + ] + } + } + }, + "flags": "bypass" + } + } + ] + } + } + ] +} diff --git a/tests/shell/testcases/sets/dumps/0055tcpflags_0.json-nft b/tests/shell/testcases/sets/dumps/0055tcpflags_0.json-nft new file mode 100644 index 00000000..6a351151 --- /dev/null +++ b/tests/shell/testcases/sets/dumps/0055tcpflags_0.json-nft @@ -0,0 +1,138 @@ +{ + "nftables": [ + { + "metainfo": { + "version": "VERSION", + "release_name": "RELEASE_NAME", + "json_schema_version": 1 + } + }, + { + "table": { + "family": "ip", + "name": "test", + "handle": 0 + } + }, + { + "set": { + "family": "ip", + "name": "tcp_good_flags", + "table": "test", + "type": "tcp_flag", + "handle": 0, + "flags": [ + "constant" + ], + "elem": [ + { + "|": [ + "fin", + "psh", + "ack", + "urg" + ] + }, + { + "|": [ + "fin", + "psh", + "ack" + ] + }, + { + "|": [ + "fin", + "ack", + "urg" + ] + }, + { + "|": [ + "fin", + "ack" + ] + }, + { + "|": [ + "syn", + "psh", + "ack", + "urg" + ] + }, + { + "|": [ + "syn", + "psh", + "ack" + ] + }, + { + "|": [ + "syn", + "ack", + "urg" + ] + }, + { + "|": [ + "syn", + "ack" + ] + }, + "syn", + { + "|": [ + "rst", + "psh", + "ack", + "urg" + ] + }, + { + "|": [ + "rst", + "psh", + "ack" + ] + }, + { + "|": [ + "rst", + "ack", + "urg" + ] + }, + { + "|": [ + "rst", + "ack" + ] + }, + "rst", + { + "|": [ + "psh", + "ack", + "urg" + ] + }, + { + "|": [ + "psh", + "ack" + ] + }, + { + "|": [ + "ack", + "urg" + ] + }, + "ack" + ] + } + } + ] +}