From patchwork Tue May 8 11:08:32 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910136 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGwy3khlz9rvt for ; Tue, 8 May 2018 21:10:10 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753135AbeEHLKK (ORCPT ); Tue, 8 May 2018 07:10:10 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36756 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752869AbeEHLKJ (ORCPT ); Tue, 8 May 2018 07:10:09 -0400 Received: from localhost ([::1]:52032 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0VU-0003vp-TO; Tue, 08 May 2018 13:10:08 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 01/14] include/linux: Add required NFT_CT_MAX macro Date: Tue, 8 May 2018 13:08:32 +0200 Message-Id: <20180508110845.26364-2-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org This should be dropped for a real UAPI header update. Signed-off-by: Phil Sutter --- include/linux/netfilter/nf_tables.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h index 517a39a00e3d5..3395faf8c0261 100644 --- a/include/linux/netfilter/nf_tables.h +++ b/include/linux/netfilter/nf_tables.h @@ -941,7 +941,9 @@ enum nft_ct_keys { NFT_CT_AVGPKT, NFT_CT_ZONE, NFT_CT_EVENTMASK, + __NFT_CT_MAX }; +#define NFT_CT_MAX (__NFT_CT_MAX - 1) /** * enum nft_ct_attributes - nf_tables ct expression netlink attributes From patchwork Tue May 8 11:08:33 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910133 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGwX2RmXz9s0W for ; Tue, 8 May 2018 21:09:48 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752796AbeEHLJr (ORCPT ); Tue, 8 May 2018 07:09:47 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36724 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752331AbeEHLJr (ORCPT ); Tue, 8 May 2018 07:09:47 -0400 Received: from localhost ([::1]:52000 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0V7-0003tZ-Vl; Tue, 08 May 2018 13:09:46 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 02/14] libnftables: Put bison parsing into dedicated functions Date: Tue, 8 May 2018 13:08:33 +0200 Message-Id: <20180508110845.26364-3-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org Preparing for an alternative JSON parser, put bison specific details into separate functions. Signed-off-by: Phil Sutter --- include/nftables.h | 2 + src/libnftables.c | 101 ++++++++++++++++++++++++++++----------------- 2 files changed, 64 insertions(+), 39 deletions(-) diff --git a/include/nftables.h b/include/nftables.h index f525ba6b8b19d..661c1e17392c8 100644 --- a/include/nftables.h +++ b/include/nftables.h @@ -38,6 +38,7 @@ struct nft_cache { }; struct mnl_socket; +struct parser_state; struct nft_ctx { struct mnl_socket *nf_sock; @@ -49,6 +50,7 @@ struct nft_ctx { bool check; struct nft_cache cache; uint32_t flags; + struct parser_state *state; }; enum nftables_exit_codes { diff --git a/src/libnftables.c b/src/libnftables.c index df4f0922b927a..ae61ce652941d 100644 --- a/src/libnftables.c +++ b/src/libnftables.c @@ -81,27 +81,6 @@ out: return ret; } -static int nft_run(struct nft_ctx *nft, struct mnl_socket *nf_sock, - void *scanner, struct parser_state *state, - struct list_head *msgs) -{ - struct cmd *cmd; - int ret; - - ret = nft_parse(nft, scanner, state); - if (ret != 0 || state->nerrs > 0) { - ret = -1; - goto err1; - } - - list_for_each_entry(cmd, state->cmds, list) - nft_cmd_expand(cmd); - - ret = nft_netlink(nft, state->cmds, msgs, nf_sock); -err1: - return ret; -} - static void nft_init(void) { mark_table_init(); @@ -162,6 +141,7 @@ struct nft_ctx *nft_ctx_new(uint32_t flags) nft_init(); ctx = xzalloc(sizeof(struct nft_ctx)); + ctx->state = xzalloc(sizeof(struct parser_state)); nft_ctx_add_include_path(ctx, DEFAULT_INCLUDE_PATH); ctx->parser_max_errors = 10; @@ -294,6 +274,7 @@ void nft_ctx_free(struct nft_ctx *ctx) iface_cache_release(); cache_release(&ctx->cache); nft_ctx_clear_include_paths(ctx); + xfree(ctx->state); xfree(ctx); nft_exit(); } @@ -397,34 +378,82 @@ static const struct input_descriptor indesc_cmdline = { .name = "", }; +static int nft_parse_bison_buffer(struct nft_ctx *nft, char *buf, size_t buflen, + struct list_head *msgs, struct list_head *cmds) +{ + struct cmd *cmd; + void *scanner; + int ret; + + parser_init(nft, nft->state, msgs, cmds); + scanner = scanner_init(nft->state); + scanner_push_buffer(scanner, &indesc_cmdline, buf); + + ret = nft_parse(nft, scanner, nft->state); + if (ret != 0 || nft->state->nerrs > 0) { + ret = -1; + goto err; + } + list_for_each_entry(cmd, cmds, list) + nft_cmd_expand(cmd); + +err: + scanner_destroy(scanner); + return ret; +} + +static int nft_parse_bison_filename(struct nft_ctx *nft, const char *filename, + struct list_head *msgs, struct list_head *cmds) +{ + struct cmd *cmd; + void *scanner; + int ret; + + parser_init(nft, nft->state, msgs, cmds); + scanner = scanner_init(nft->state); + if (scanner_read_file(scanner, filename, &internal_location) < 0) { + ret = -1; + goto err; + } + + ret = nft_parse(nft, scanner, nft->state); + if (ret != 0 || nft->state->nerrs > 0) { + ret = -1; + goto err; + } + list_for_each_entry(cmd, cmds, list) + nft_cmd_expand(cmd); + +err: + scanner_destroy(scanner); + return ret; +} + int nft_run_cmd_from_buffer(struct nft_ctx *nft, char *buf, size_t buflen) { - int rc = 0; - struct parser_state state; struct cmd *cmd, *next; LIST_HEAD(msgs); LIST_HEAD(cmds); size_t nlbuflen; - void *scanner; char *nlbuf; + int rc; nlbuflen = max(buflen + 1, strlen(buf) + 2); nlbuf = xzalloc(nlbuflen); snprintf(nlbuf, nlbuflen, "%s\n", buf); - parser_init(nft, &state, &msgs, &cmds); - scanner = scanner_init(&state); - scanner_push_buffer(scanner, &indesc_cmdline, nlbuf); + rc = nft_parse_bison_buffer(nft, nlbuf, nlbuflen, &msgs, &cmds); + if (rc) + goto err; - if (nft_run(nft, nft->nf_sock, scanner, &state, &msgs) != 0) + if (nft_netlink(nft, &cmds, &msgs, nft->nf_sock) != 0) rc = -1; - +err: list_for_each_entry_safe(cmd, next, &cmds, list) { list_del(&cmd->list); cmd_free(cmd); } erec_print_list(&nft->output, &msgs, nft->debug_mask); - scanner_destroy(scanner); iface_cache_release(); free(nlbuf); @@ -433,11 +462,9 @@ int nft_run_cmd_from_buffer(struct nft_ctx *nft, char *buf, size_t buflen) int nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename) { - struct parser_state state; struct cmd *cmd, *next; LIST_HEAD(msgs); LIST_HEAD(cmds); - void *scanner; int rc; rc = cache_update(nft->nf_sock, &nft->cache, CMD_INVALID, &msgs, @@ -448,14 +475,11 @@ int nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename) if (!strcmp(filename, "-")) filename = "/dev/stdin"; - parser_init(nft, &state, &msgs, &cmds); - scanner = scanner_init(&state); - if (scanner_read_file(scanner, filename, &internal_location) < 0) { - rc = -1; + rc = nft_parse_bison_filename(nft, filename, &msgs, &cmds); + if (rc) goto err; - } - if (nft_run(nft, nft->nf_sock, scanner, &state, &msgs) != 0) + if (nft_netlink(nft, &cmds, &msgs, nft->nf_sock) != 0) rc = -1; err: list_for_each_entry_safe(cmd, next, &cmds, list) { @@ -463,7 +487,6 @@ err: cmd_free(cmd); } erec_print_list(&nft->output, &msgs, nft->debug_mask); - scanner_destroy(scanner); iface_cache_release(); return rc; From patchwork Tue May 8 11:08:34 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910129 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGw60R9cz9rxs for ; Tue, 8 May 2018 21:09:26 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752520AbeEHLJZ (ORCPT ); Tue, 8 May 2018 07:09:25 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36698 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752331AbeEHLJY (ORCPT ); Tue, 8 May 2018 07:09:24 -0400 Received: from localhost ([::1]:51974 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0Ul-0003sA-KW; Tue, 08 May 2018 13:09:23 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 03/14] libnftables: Make some arrays globally accessible Date: Tue, 8 May 2018 13:08:34 +0200 Message-Id: <20180508110845.26364-4-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org This removes static flag and adds declarations in headers for the following arrays: * ct_templates from src/ct.c * mark_tbl from src/datatype.c * meta_templates and devgroup_tbl from src/meta.c * table_flags_name from src/rule.c * set_stmt_op_names from src/statement.c * tcpopthdr_protocols from src/tcpopt.c Signed-off-by: Phil Sutter --- include/ct.h | 2 ++ include/datatype.h | 1 + include/meta.h | 4 ++++ include/rt.h | 2 ++ include/rule.h | 3 +++ include/statement.h | 2 ++ include/tcpopt.h | 4 ++++ src/ct.c | 2 +- src/datatype.c | 3 ++- src/meta.c | 5 +++-- src/rt.c | 2 +- src/rule.c | 2 -- src/statement.c | 2 +- src/tcpopt.c | 2 +- 14 files changed, 27 insertions(+), 9 deletions(-) diff --git a/include/ct.h b/include/ct.h index 2c3392d36c94e..dadd820f88740 100644 --- a/include/ct.h +++ b/include/ct.h @@ -16,6 +16,8 @@ struct ct_template { unsigned int len; }; +extern const struct ct_template ct_templates[__NFT_CT_MAX]; + #define CT_TEMPLATE(__token, __dtype, __byteorder, __len) { \ .token = (__token), \ .dtype = (__dtype), \ diff --git a/include/datatype.h b/include/datatype.h index 3f612e52aec29..a1abe31f2b2bd 100644 --- a/include/datatype.h +++ b/include/datatype.h @@ -218,6 +218,7 @@ extern struct symbol_table *rt_symbol_table_init(const char *filename); extern void rt_symbol_table_free(struct symbol_table *tbl); extern const struct symbol_table inet_service_tbl; +extern struct symbol_table *mark_tbl; extern const struct datatype invalid_type; extern const struct datatype verdict_type; diff --git a/include/meta.h b/include/meta.h index 6086a71cc0115..a49b4ff549702 100644 --- a/include/meta.h +++ b/include/meta.h @@ -16,6 +16,8 @@ struct meta_template { unsigned int len; }; +extern const struct meta_template meta_templates[]; + #define META_TEMPLATE(__token, __dtype, __len, __byteorder) { \ .token = (__token), \ .dtype = (__dtype), \ @@ -40,4 +42,6 @@ extern const struct datatype devgroup_type; extern const struct datatype pkttype_type; extern const struct datatype ifname_type; +extern struct symbol_table *devgroup_tbl; + #endif /* NFTABLES_META_H */ diff --git a/include/rt.h b/include/rt.h index 9828e634660a1..195af94345cbc 100644 --- a/include/rt.h +++ b/include/rt.h @@ -18,6 +18,8 @@ struct rt_template { bool invalid; }; +extern const struct rt_template rt_templates[]; + #define RT_TEMPLATE(__token, __dtype, __len, __byteorder, __invalid) { \ .token = (__token), \ .dtype = (__dtype), \ diff --git a/include/rule.h b/include/rule.h index b265690d3c964..74903eca3608f 100644 --- a/include/rule.h +++ b/include/rule.h @@ -114,6 +114,9 @@ struct symbol *symbol_get(const struct scope *scope, const char *identifier); enum table_flags { TABLE_F_DORMANT = (1 << 0), }; +#define TABLE_FLAGS_MAX 1 + +extern const char *table_flags_name[TABLE_FLAGS_MAX]; /** * struct table - nftables table diff --git a/include/statement.h b/include/statement.h index 7315e7aeea8bc..5987b18170f94 100644 --- a/include/statement.h +++ b/include/statement.h @@ -163,6 +163,8 @@ struct set_stmt { enum nft_dynset_ops op; }; +extern const char * const set_stmt_op_names[]; + extern struct stmt *set_stmt_alloc(const struct location *loc); struct map_stmt { diff --git a/include/tcpopt.h b/include/tcpopt.h index 9be84817e6f20..ffdbcb028125c 100644 --- a/include/tcpopt.h +++ b/include/tcpopt.h @@ -29,6 +29,7 @@ enum tcpopt_hdr_types { TCPOPTHDR_TIMESTAMP, TCPOPTHDR_ECHO, TCPOPTHDR_ECHO_REPLY, + __TCPOPTHDR_MAX }; enum tcpopt_hdr_fields { @@ -42,4 +43,7 @@ enum tcpopt_hdr_fields { TCPOPTHDR_FIELD_TSVAL, TCPOPTHDR_FIELD_TSECR, }; + +extern const struct exthdr_desc *tcpopthdr_protocols[__TCPOPTHDR_MAX]; + #endif /* NFTABLES_TCPOPT_H */ diff --git a/src/ct.c b/src/ct.c index 6cb6bd5af927b..2abaa0d581443 100644 --- a/src/ct.c +++ b/src/ct.c @@ -220,7 +220,7 @@ void ct_label_table_exit(void) #define NF_CT_HELPER_NAME_LEN 16 #endif -static const struct ct_template ct_templates[] = { +const struct ct_template ct_templates[__NFT_CT_MAX] = { [NFT_CT_STATE] = CT_TEMPLATE("state", &ct_state_type, BYTEORDER_HOST_ENDIAN, 4 * BITS_PER_BYTE), diff --git a/src/datatype.c b/src/datatype.c index 446bde9f438d1..8b4579daa8444 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -721,7 +721,8 @@ void rt_symbol_table_free(struct symbol_table *tbl) xfree(tbl); } -static struct symbol_table *mark_tbl; +struct symbol_table *mark_tbl = NULL; + void mark_table_init(void) { mark_tbl = rt_symbol_table_init("/etc/iproute2/rt_marks"); diff --git a/src/meta.c b/src/meta.c index 3012efa94e242..fb94ed406d4a5 100644 --- a/src/meta.c +++ b/src/meta.c @@ -334,7 +334,8 @@ const struct datatype pkttype_type = { .sym_tbl = &pkttype_type_tbl, }; -static struct symbol_table *devgroup_tbl; +struct symbol_table *devgroup_tbl = NULL; + void devgroup_table_init(void) { devgroup_tbl = rt_symbol_table_init("/etc/iproute2/group"); @@ -378,7 +379,7 @@ const struct datatype ifname_type = { .basetype = &string_type, }; -static const struct meta_template meta_templates[] = { +const struct meta_template meta_templates[] = { [NFT_META_LEN] = META_TEMPLATE("length", &integer_type, 4 * 8, BYTEORDER_HOST_ENDIAN), [NFT_META_PROTOCOL] = META_TEMPLATE("protocol", ðertype_type, diff --git a/src/rt.c b/src/rt.c index 041dbc2f7d900..2530b663bb249 100644 --- a/src/rt.c +++ b/src/rt.c @@ -57,7 +57,7 @@ const struct datatype realm_type = { .flags = DTYPE_F_PREFIX, }; -static const struct rt_template rt_templates[] = { +const struct rt_template rt_templates[] = { [NFT_RT_CLASSID] = RT_TEMPLATE("classid", &realm_type, 4 * BITS_PER_BYTE, diff --git a/src/rule.c b/src/rule.c index 2f0123b7a4a58..700ddffbe6919 100644 --- a/src/rule.c +++ b/src/rule.c @@ -848,8 +848,6 @@ struct table *table_lookup(const struct handle *h, return NULL; } -#define TABLE_FLAGS_MAX 1 - const char *table_flags_name[TABLE_FLAGS_MAX] = { "dormant", }; diff --git a/src/statement.c b/src/statement.c index 19c30cf861c81..a5ef7d7dfd2eb 100644 --- a/src/statement.c +++ b/src/statement.c @@ -567,7 +567,7 @@ struct stmt *nat_stmt_alloc(const struct location *loc, return stmt; } -static const char * const set_stmt_op_names[] = { +const char * const set_stmt_op_names[] = { [NFT_DYNSET_OP_ADD] = "add", [NFT_DYNSET_OP_UPDATE] = "update", }; diff --git a/src/tcpopt.c b/src/tcpopt.c index 7c6c2557028ab..66f021f94d63f 100644 --- a/src/tcpopt.c +++ b/src/tcpopt.c @@ -136,7 +136,7 @@ static unsigned int calc_offset_reverse(const struct exthdr_desc *desc, } } -static const struct exthdr_desc *tcpopthdr_protocols[] = { +const struct exthdr_desc *tcpopthdr_protocols[__TCPOPTHDR_MAX] = { [TCPOPTHDR_EOL] = &tcpopt_eol, [TCPOPTHDR_NOOP] = &tcpopt_nop, [TCPOPTHDR_MAXSEG] = &tcptopt_maxseg, From patchwork Tue May 8 11:08:35 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910132 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGwQ52Nrz9rvt for ; Tue, 8 May 2018 21:09:42 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752769AbeEHLJm (ORCPT ); Tue, 8 May 2018 07:09:42 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36718 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752331AbeEHLJl (ORCPT ); Tue, 8 May 2018 07:09:41 -0400 Received: from localhost ([::1]:51994 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0V2-0003tF-E0; Tue, 08 May 2018 13:09:40 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 04/14] libnftables: Make some functions globally accessible Date: Tue, 8 May 2018 13:08:35 +0200 Message-Id: <20180508110845.26364-5-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org This removes static flag and adds header prototype for the following functions: * must_print_eq_op() from src/expression.c * fib_result_str() from src/fib.c * set_policy2str() and chain_policy2str from src/rule.c In fib.h, include linux/netfilter/nf_tables.h to make sure enum nft_fib_result is known when including this file. Signed-off-by: Phil Sutter --- include/expression.h | 2 ++ include/fib.h | 3 +++ include/rule.h | 2 ++ include/statement.h | 2 ++ src/expression.c | 2 +- src/fib.c | 2 +- src/rule.c | 4 ++-- src/statement.c | 4 ++-- 8 files changed, 15 insertions(+), 6 deletions(-) diff --git a/include/expression.h b/include/expression.h index f0ba6fc112601..23d6bd23cff29 100644 --- a/include/expression.h +++ b/include/expression.h @@ -366,6 +366,8 @@ extern struct expr *unary_expr_alloc(const struct location *loc, extern struct expr *binop_expr_alloc(const struct location *loc, enum ops op, struct expr *left, struct expr *right); +extern bool must_print_eq_op(const struct expr *expr); + extern struct expr *relational_expr_alloc(const struct location *loc, enum ops op, struct expr *left, struct expr *right); diff --git a/include/fib.h b/include/fib.h index 9ce681cc1ecf3..67edccfea0d57 100644 --- a/include/fib.h +++ b/include/fib.h @@ -1,6 +1,9 @@ #ifndef NFTABLES_FIB_H #define NFTABLES_FIB_H +#include + +extern const char *fib_result_str(enum nft_fib_result result); extern struct expr *fib_expr_alloc(const struct location *loc, unsigned int flags, unsigned int result); diff --git a/include/rule.h b/include/rule.h index 74903eca3608f..afc72f2216d69 100644 --- a/include/rule.h +++ b/include/rule.h @@ -203,6 +203,7 @@ extern struct chain *chain_lookup(const struct table *table, extern const char *family2str(unsigned int family); extern const char *hooknum2str(unsigned int family, unsigned int hooknum); +extern const char *chain_policy2str(uint32_t policy); extern void chain_print_plain(const struct chain *chain, struct output_ctx *octx); @@ -283,6 +284,7 @@ extern void set_add_hash(struct set *set, struct table *table); extern struct set *set_lookup(const struct table *table, const char *name); extern struct set *set_lookup_global(uint32_t family, const char *table, const char *name, struct nft_cache *cache); +extern const char *set_policy2str(uint32_t policy); extern void set_print(const struct set *set, struct output_ctx *octx); extern void set_print_plain(const struct set *s, struct output_ctx *octx); diff --git a/include/statement.h b/include/statement.h index 5987b18170f94..fc80dbd518b35 100644 --- a/include/statement.h +++ b/include/statement.h @@ -21,6 +21,7 @@ struct objref_stmt { struct expr *expr; }; +const char *objref_type_name(uint32_t type); struct stmt *objref_stmt_alloc(const struct location *loc); struct counter_stmt { @@ -75,6 +76,7 @@ struct log_stmt { uint32_t flags; }; +extern const char *log_level(uint32_t level); extern struct stmt *log_stmt_alloc(const struct location *loc); diff --git a/src/expression.c b/src/expression.c index 239cf8825c4f4..049c75a7787d1 100644 --- a/src/expression.c +++ b/src/expression.c @@ -558,7 +558,7 @@ static void binop_arg_print(const struct expr *op, const struct expr *arg, nft_print(octx, ")"); } -static bool must_print_eq_op(const struct expr *expr) +bool must_print_eq_op(const struct expr *expr) { return expr->left->ops->type == EXPR_BINOP; } diff --git a/src/fib.c b/src/fib.c index 21bc69a9f0a38..069411f0131f2 100644 --- a/src/fib.c +++ b/src/fib.c @@ -52,7 +52,7 @@ const struct datatype fib_addr_type = { .sym_tbl = &addrtype_tbl, }; -static const char *fib_result_str(enum nft_fib_result result) +const char *fib_result_str(enum nft_fib_result result) { if (result <= NFT_FIB_RESULT_MAX) return fib_result[result]; diff --git a/src/rule.c b/src/rule.c index 700ddffbe6919..7f00a24cda73d 100644 --- a/src/rule.c +++ b/src/rule.c @@ -287,7 +287,7 @@ struct print_fmt_options { const char *stmt_separator; }; -static const char *set_policy2str(uint32_t policy) +const char *set_policy2str(uint32_t policy) { switch (policy) { case NFT_SET_POL_PERFORMANCE: @@ -732,7 +732,7 @@ const char *hooknum2str(unsigned int family, unsigned int hooknum) return "unknown"; } -static const char *chain_policy2str(uint32_t policy) +const char *chain_policy2str(uint32_t policy) { switch (policy) { case NF_DROP: diff --git a/src/statement.c b/src/statement.c index a5ef7d7dfd2eb..6537bbbd9a20b 100644 --- a/src/statement.c +++ b/src/statement.c @@ -178,7 +178,7 @@ static const char *objref_type[NFT_OBJECT_MAX + 1] = { [NFT_OBJECT_LIMIT] = "limit", }; -static const char *objref_type_name(uint32_t type) +const char *objref_type_name(uint32_t type) { if (type > NFT_OBJECT_MAX) return "unknown"; @@ -225,7 +225,7 @@ static const char *syslog_level[LOG_DEBUG + 1] = { [LOG_DEBUG] = "debug", }; -static const char *log_level(uint32_t level) +const char *log_level(uint32_t level) { if (level > LOG_DEBUG) return "unknown"; From patchwork Tue May 8 11:08:36 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910135 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGws3lbfz9rvt for ; Tue, 8 May 2018 21:10:05 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752777AbeEHLKF (ORCPT ); Tue, 8 May 2018 07:10:05 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36750 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752174AbeEHLKE (ORCPT ); Tue, 8 May 2018 07:10:04 -0400 Received: from localhost ([::1]:52026 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0VP-0003vW-I7; Tue, 08 May 2018 13:10:03 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 05/14] libnftables: Introduce a few helper functions Date: Tue, 8 May 2018 13:08:36 +0200 Message-Id: <20180508110845.26364-6-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org This adds a bunch of functions for conversion of different values into string (and vice-versa). * log_level_parse(): A simple helper to turn log level string representation into log level value. * nat_etype2str(): Translate nat statement type into string representation. * ct_dir2str(): Convert IP_CT_DIR_* values into string representation. * ct_label2str(): Convert ct_label values into string representation. Signed-off-by: Phil Sutter --- include/ct.h | 2 ++ include/statement.h | 3 +++ src/ct.c | 44 +++++++++++++++++++++++++++++++------------- src/statement.c | 21 +++++++++++++++++++-- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/include/ct.h b/include/ct.h index dadd820f88740..4c5bd804dabfc 100644 --- a/include/ct.h +++ b/include/ct.h @@ -33,6 +33,8 @@ extern void ct_expr_update_type(struct proto_ctx *ctx, struct expr *expr); extern struct stmt *notrack_stmt_alloc(const struct location *loc); extern struct stmt *flow_offload_stmt_alloc(const struct location *loc, const char *table_name); +extern const char *ct_dir2str(int dir); +extern const char *ct_label2str(unsigned long value); extern const struct datatype ct_dir_type; extern const struct datatype ct_state_type; diff --git a/include/statement.h b/include/statement.h index fc80dbd518b35..2c6d0dfa2dd50 100644 --- a/include/statement.h +++ b/include/statement.h @@ -77,6 +77,7 @@ struct log_stmt { }; extern const char *log_level(uint32_t level); +extern int log_level_parse(const char *level); extern struct stmt *log_stmt_alloc(const struct location *loc); @@ -107,6 +108,8 @@ enum nft_nat_etypes { NFT_NAT_REDIR, }; +extern const char *nat_etype2str(enum nft_nat_etypes type); + struct nat_stmt { enum nft_nat_etypes type; struct expr *addr; diff --git a/src/ct.c b/src/ct.c index 2abaa0d581443..a1a91f3ae7644 100644 --- a/src/ct.c +++ b/src/ct.c @@ -64,6 +64,18 @@ static const struct symbol_table ct_dir_tbl = { } }; +const char *ct_dir2str(int dir) +{ + const struct symbolic_constant *s; + + for (s = ct_dir_tbl.symbols; s->identifier != NULL; s++) { + if (dir == (int)s->value) + return s->identifier; + } + + return NULL; +} + const struct datatype ct_dir_type = { .type = TYPE_CT_DIR, .name = "ct_dir", @@ -133,20 +145,30 @@ static struct symbol_table *ct_label_tbl; #define CT_LABEL_BIT_SIZE 128 +const char *ct_label2str(unsigned long value) +{ + const struct symbolic_constant *s; + + for (s = ct_label_tbl->symbols; s->identifier; s++) { + if (value == s->value) + return s->identifier; + } + + return NULL; +} + static void ct_label_type_print(const struct expr *expr, struct output_ctx *octx) { unsigned long bit = mpz_scan1(expr->value, 0); - const struct symbolic_constant *s; + const char *labelstr = ct_label2str(bit); - for (s = ct_label_tbl->symbols; s->identifier != NULL; s++) { - if (bit != s->value) - continue; - nft_print(octx, "\"%s\"", s->identifier); + if (labelstr) { + nft_print(octx, "\"%s\"", labelstr); return; } /* can happen when connlabel.conf is altered after rules were added */ - nft_print(octx, "%ld", (long)mpz_scan1(expr->value, 0)); + nft_print(octx, "%lu", bit); } static struct error_record *ct_label_type_parse(const struct expr *sym, @@ -273,19 +295,15 @@ const struct ct_template ct_templates[__NFT_CT_MAX] = { static void ct_print(enum nft_ct_keys key, int8_t dir, uint8_t nfproto, struct output_ctx *octx) { - const struct symbolic_constant *s; + const char *dirstr = ct_dir2str(dir); const struct proto_desc *desc; nft_print(octx, "ct "); if (dir < 0) goto done; - for (s = ct_dir_tbl.symbols; s->identifier != NULL; s++) { - if (dir == (int)s->value) { - nft_print(octx, "%s ", s->identifier); - break; - } - } + if (dirstr) + nft_print(octx, "%s ", dirstr); switch (key) { case NFT_CT_SRC: diff --git a/src/statement.c b/src/statement.c index 6537bbbd9a20b..8160e0adfce49 100644 --- a/src/statement.c +++ b/src/statement.c @@ -233,6 +233,18 @@ const char *log_level(uint32_t level) return syslog_level[level]; } +int log_level_parse(const char *level) +{ + int i; + + for (i = 0; i <= LOG_DEBUG; i++) { + if (syslog_level[i] && + !strcmp(level, syslog_level[i])) + return i; + } + return -1; +} + static void log_stmt_print(const struct stmt *stmt, struct output_ctx *octx) { nft_print(octx, "log"); @@ -499,7 +511,7 @@ static void print_nf_nat_flags(uint32_t flags, struct output_ctx *octx) nft_print(octx, "%spersistent", delim); } -static void nat_stmt_print(const struct stmt *stmt, struct output_ctx *octx) +const char *nat_etype2str(enum nft_nat_etypes type) { static const char * const nat_types[] = { [NFT_NAT_SNAT] = "snat", @@ -508,7 +520,12 @@ static void nat_stmt_print(const struct stmt *stmt, struct output_ctx *octx) [NFT_NAT_REDIR] = "redirect", }; - nft_print(octx, "%s", nat_types[stmt->nat.type]); + return nat_types[type]; +} + +static void nat_stmt_print(const struct stmt *stmt, struct output_ctx *octx) +{ + nft_print(octx, "%s", nat_etype2str(stmt->nat.type)); if (stmt->nat.addr || stmt->nat.proto) nft_print(octx, " to"); From patchwork Tue May 8 11:08:37 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910137 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGx46zZhz9rxs for ; Tue, 8 May 2018 21:10:16 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753152AbeEHLKQ (ORCPT ); Tue, 8 May 2018 07:10:16 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36762 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752463AbeEHLKP (ORCPT ); Tue, 8 May 2018 07:10:15 -0400 Received: from localhost ([::1]:52038 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0Va-0003wA-7c; Tue, 08 May 2018 13:10:14 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 06/14] libnftables: Implement JSON output support Date: Tue, 8 May 2018 13:08:37 +0200 Message-Id: <20180508110845.26364-7-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org Although technically there already is support for JSON output via 'nft export json' command, it is hardly useable since it exports all the gory details of nftables VM. Also, libnftables has no control over what is exported since the content comes directly from libnftnl. Instead, implement JSON format support for regular 'nft list' commands. Signed-off-by: Phil Sutter --- Changes since v1: - Fix numerical uid/gid output (use a JSON integer instead of string). Changes since v2: - Implement json_pack_assert() as a macro. This way triggered assert() prints a useful line number. - Fix for changes in struct handle. --- configure.ac | 14 +- include/datatype.h | 4 + include/expression.h | 3 + include/gmputil.h | 1 + include/json.h | 161 ++++ include/nftables.h | 1 + include/nftables/libnftables.h | 2 + include/statement.h | 3 + src/Makefile.am | 5 + src/ct.c | 4 + src/datatype.c | 8 + src/expression.c | 15 + src/exthdr.c | 2 + src/fib.c | 1 + src/hash.c | 1 + src/json.c | 1564 ++++++++++++++++++++++++++++++++ src/libnftables.c | 16 + src/main.c | 11 +- src/meta.c | 6 + src/numgen.c | 1 + src/payload.c | 3 + src/rt.c | 2 + src/rule.c | 4 + src/statement.c | 15 + 24 files changed, 1845 insertions(+), 2 deletions(-) create mode 100644 include/json.h create mode 100644 src/json.c diff --git a/configure.ac b/configure.ac index 249657793646a..65fe038a316fe 100644 --- a/configure.ac +++ b/configure.ac @@ -107,6 +107,17 @@ AC_DEFINE([HAVE_LIBXTABLES], [1], [0]) AC_SUBST(with_libxtables) AM_CONDITIONAL([BUILD_XTABLES], [test "x$with_libxtables" == xyes]) +AC_ARG_WITH([json], [AS_HELP_STRING([--with-json], + [Enable JSON output support)])], + [], [with_json=no]) +AS_IF([test "x$with_json" != xno], [ +AC_CHECK_LIB([jansson], [json_object], , + AC_MSG_ERROR([No suitable version of libjansson found])) +AC_DEFINE([HAVE_LIBJANSSON], [1], [Define if you have libjansson]) +]) +AC_SUBST(with_json) +AM_CONDITIONAL([BUILD_JSON], [test "x$with_json" != xno]) + AC_CONFIG_FILES([ \ Makefile \ libnftables.pc \ @@ -130,4 +141,5 @@ nft configuration: use mini-gmp: ${with_mini_gmp} enable man page: ${enable_man_doc} enable pdf documentation: ${enable_pdf_doc} - libxtables support: ${with_libxtables}" + libxtables support: ${with_libxtables} + json output support: ${with_json}" diff --git a/include/datatype.h b/include/datatype.h index a1abe31f2b2bd..44ac075c2c534 100644 --- a/include/datatype.h +++ b/include/datatype.h @@ -1,6 +1,8 @@ #ifndef NFTABLES_DATATYPE_H #define NFTABLES_DATATYPE_H +#include + /** * enum datatypes * @@ -149,6 +151,8 @@ struct datatype { const char *basefmt; void (*print)(const struct expr *expr, struct output_ctx *octx); + json_t *(*json)(const struct expr *expr, + struct output_ctx *octx); struct error_record *(*parse)(const struct expr *sym, struct expr **res); const struct symbol_table *sym_tbl; diff --git a/include/expression.h b/include/expression.h index 23d6bd23cff29..15af35e87a983 100644 --- a/include/expression.h +++ b/include/expression.h @@ -9,6 +9,7 @@ #include #include #include +#include /** * enum expr_types @@ -154,6 +155,8 @@ struct expr_ops { enum byteorder byteorder); void (*print)(const struct expr *expr, struct output_ctx *octx); + json_t *(*json)(const struct expr *expr, + struct output_ctx *octx); bool (*cmp)(const struct expr *e1, const struct expr *e2); void (*pctx_update)(struct proto_ctx *ctx, diff --git a/include/gmputil.h b/include/gmputil.h index 084aa62266879..73959c170c917 100644 --- a/include/gmputil.h +++ b/include/gmputil.h @@ -13,6 +13,7 @@ extern int mpz_vfprintf(FILE *fp, const char *format, va_list args); #define gmp_vfprintf mpz_vfprintf #endif +#include #include enum mpz_word_order { diff --git a/include/json.h b/include/json.h new file mode 100644 index 0000000000000..579bd5dfa98d3 --- /dev/null +++ b/include/json.h @@ -0,0 +1,161 @@ +#ifndef NFTABLES_JSON_H +#define NFTABLES_JSON_H + +struct chain; +struct cmd; +struct expr; +struct netlink_ctx; +struct rule; +struct set; +struct stmt; +struct symbol_table; +struct table; + +#ifdef HAVE_LIBJANSSON + +#include + +json_t *binop_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *relational_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *range_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *meta_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *payload_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *ct_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *concat_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *set_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *set_ref_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *set_elem_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *prefix_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *list_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *unary_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *mapping_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *map_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *exthdr_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *verdict_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *rt_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *numgen_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *hash_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *constant_expr_json(const struct expr *expr, struct output_ctx *octx); + +json_t *integer_type_json(const struct expr *expr, struct output_ctx *octx); +json_t *string_type_json(const struct expr *expr, struct output_ctx *octx); +json_t *boolean_type_json(const struct expr *expr, struct output_ctx *octx); +json_t *inet_protocol_type_json(const struct expr *expr, + struct output_ctx *octx); +json_t *inet_service_type_json(const struct expr *expr, + struct output_ctx *octx); +json_t *mark_type_json(const struct expr *expr, struct output_ctx *octx); +json_t *devgroup_type_json(const struct expr *expr, struct output_ctx *octx); +json_t *ct_label_type_json(const struct expr *expr, struct output_ctx *octx); +json_t *time_type_json(const struct expr *expr, struct output_ctx *octx); +json_t *uid_type_json(const struct expr *expr, struct output_ctx *octx); +json_t *gid_type_json(const struct expr *expr, struct output_ctx *octx); + +json_t *expr_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *payload_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *exthdr_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *quota_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *ct_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *limit_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *fwd_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *notrack_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *dup_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *meta_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *nat_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *reject_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *counter_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *set_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *log_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *objref_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *meter_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *queue_stmt_json(const struct stmt *stmt, struct output_ctx *octx); +json_t *verdict_stmt_json(const struct stmt *stmt, struct output_ctx *octx); + +int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd); + +#else /* ! HAVE_LIBJANSSON */ + +typedef void json_t; + +#define JSON_PRINT_STUB(name, arg1_t, arg2_t) \ +static inline json_t *name##_json(arg1_t arg1, arg2_t arg2) { return NULL; } + +#define EXPR_PRINT_STUB(name) \ + JSON_PRINT_STUB(name, const struct expr *, struct output_ctx *) +#define STMT_PRINT_STUB(name) \ + JSON_PRINT_STUB(name##_stmt, const struct stmt *, struct output_ctx *) + +EXPR_PRINT_STUB(binop_expr) +EXPR_PRINT_STUB(relational_expr) +EXPR_PRINT_STUB(range_expr) +EXPR_PRINT_STUB(meta_expr) +EXPR_PRINT_STUB(payload_expr) +EXPR_PRINT_STUB(ct_expr) +EXPR_PRINT_STUB(concat_expr) +EXPR_PRINT_STUB(set_expr) +EXPR_PRINT_STUB(set_ref_expr) +EXPR_PRINT_STUB(set_elem_expr) +EXPR_PRINT_STUB(prefix_expr) +EXPR_PRINT_STUB(list_expr) +EXPR_PRINT_STUB(unary_expr) +EXPR_PRINT_STUB(mapping_expr) +EXPR_PRINT_STUB(map_expr) +EXPR_PRINT_STUB(exthdr_expr) +EXPR_PRINT_STUB(verdict_expr) +EXPR_PRINT_STUB(rt_expr) +EXPR_PRINT_STUB(numgen_expr) +EXPR_PRINT_STUB(hash_expr) +EXPR_PRINT_STUB(fib_expr) +EXPR_PRINT_STUB(constant_expr) + +EXPR_PRINT_STUB(integer_type) +EXPR_PRINT_STUB(string_type) +EXPR_PRINT_STUB(boolean_type) +EXPR_PRINT_STUB(inet_protocol_type) +EXPR_PRINT_STUB(inet_service_type) +EXPR_PRINT_STUB(mark_type) +EXPR_PRINT_STUB(devgroup_type) +EXPR_PRINT_STUB(ct_label_type) +EXPR_PRINT_STUB(time_type) +EXPR_PRINT_STUB(uid_type) +EXPR_PRINT_STUB(gid_type) + +STMT_PRINT_STUB(expr) +STMT_PRINT_STUB(payload) +STMT_PRINT_STUB(exthdr) +STMT_PRINT_STUB(quota) +STMT_PRINT_STUB(ct) +STMT_PRINT_STUB(limit) +STMT_PRINT_STUB(fwd) +STMT_PRINT_STUB(notrack) +STMT_PRINT_STUB(dup) +STMT_PRINT_STUB(meta) +STMT_PRINT_STUB(nat) +STMT_PRINT_STUB(reject) +STMT_PRINT_STUB(counter) +STMT_PRINT_STUB(set) +STMT_PRINT_STUB(log) +STMT_PRINT_STUB(objref) +STMT_PRINT_STUB(meter) +STMT_PRINT_STUB(queue) +STMT_PRINT_STUB(verdict) + +#undef STMT_PRINT_STUB +#undef EXPR_PRINT_STUB +#undef JSON_PRINT_STUB + +static inline json_t *symbolic_constant_json(const struct symbol_table *tbl, + const struct expr *expr) +{ + return NULL; +} + +static inline int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) +{ + return -1; +} + +#endif /* HAVE_LIBJANSSON */ + +#endif /* NFTABLES_JSON_H */ diff --git a/include/nftables.h b/include/nftables.h index 661c1e17392c8..f88d0530a963e 100644 --- a/include/nftables.h +++ b/include/nftables.h @@ -21,6 +21,7 @@ struct output_ctx { unsigned int ip2name; unsigned int handle; unsigned int echo; + unsigned int json; union { FILE *output_fp; struct cookie output_cookie; diff --git a/include/nftables/libnftables.h b/include/nftables/libnftables.h index 652e0ca99182a..4bfdaf9a26885 100644 --- a/include/nftables/libnftables.h +++ b/include/nftables/libnftables.h @@ -55,6 +55,8 @@ bool nft_ctx_output_get_handle(struct nft_ctx *ctx); void nft_ctx_output_set_handle(struct nft_ctx *ctx, bool val); bool nft_ctx_output_get_echo(struct nft_ctx *ctx); void nft_ctx_output_set_echo(struct nft_ctx *ctx, bool val); +bool nft_ctx_output_get_json(struct nft_ctx *ctx); +void nft_ctx_output_set_json(struct nft_ctx *ctx, bool val); FILE *nft_ctx_set_output(struct nft_ctx *ctx, FILE *fp); int nft_ctx_buffer_output(struct nft_ctx *ctx); diff --git a/include/statement.h b/include/statement.h index 2c6d0dfa2dd50..de26549b32f4c 100644 --- a/include/statement.h +++ b/include/statement.h @@ -3,6 +3,7 @@ #include #include +#include extern struct stmt *expr_stmt_alloc(const struct location *loc, struct expr *expr); @@ -289,6 +290,8 @@ struct stmt_ops { void (*destroy)(struct stmt *stmt); void (*print)(const struct stmt *stmt, struct output_ctx *octx); + json_t *(*json)(const struct stmt *stmt, + struct output_ctx *octx); }; enum stmt_flags { diff --git a/src/Makefile.am b/src/Makefile.am index 92e6795f659aa..c5c3b0bca1f6b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -88,4 +88,9 @@ if BUILD_CLI nft_SOURCES += cli.c endif +if BUILD_JSON +libnftables_la_SOURCES += json.c +libnftables_la_LIBADD += ${JANSSON_LIBS} +endif + nft_LDADD = libnftables.la diff --git a/src/ct.c b/src/ct.c index a1a91f3ae7644..1d50382bab8b1 100644 --- a/src/ct.c +++ b/src/ct.c @@ -225,6 +225,7 @@ static const struct datatype ct_label_type = { .size = CT_LABEL_BIT_SIZE, .basetype = &bitmask_type, .print = ct_label_type_print, + .json = ct_label_type_json, .parse = ct_label_type_parse, }; @@ -360,6 +361,7 @@ static const struct expr_ops ct_expr_ops = { .type = EXPR_CT, .name = "ct", .print = ct_expr_print, + .json = ct_expr_json, .cmp = ct_expr_cmp, .clone = ct_expr_clone, .pctx_update = ct_expr_pctx_update, @@ -442,6 +444,7 @@ static const struct stmt_ops ct_stmt_ops = { .type = STMT_CT, .name = "ct", .print = ct_stmt_print, + .json = ct_stmt_json, }; struct stmt *ct_stmt_alloc(const struct location *loc, enum nft_ct_keys key, @@ -467,6 +470,7 @@ static const struct stmt_ops notrack_stmt_ops = { .type = STMT_NOTRACK, .name = "notrack", .print = notrack_stmt_print, + .json = notrack_stmt_json, }; struct stmt *notrack_stmt_alloc(const struct location *loc) diff --git a/src/datatype.c b/src/datatype.c index 8b4579daa8444..98b743feb1664 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -357,6 +358,7 @@ const struct datatype integer_type = { .name = "integer", .desc = "integer", .print = integer_type_print, + .json = integer_type_json, .parse = integer_type_parse, }; @@ -386,6 +388,7 @@ const struct datatype string_type = { .desc = "string", .byteorder = BYTEORDER_HOST_ENDIAN, .print = string_type_print, + .json = string_type_json, .parse = string_type_parse, }; @@ -603,6 +606,7 @@ const struct datatype inet_protocol_type = { .size = BITS_PER_BYTE, .basetype = &integer_type, .print = inet_protocol_type_print, + .json = inet_protocol_type_json, .parse = inet_protocol_type_parse, }; @@ -658,6 +662,7 @@ const struct datatype inet_service_type = { .size = 2 * BITS_PER_BYTE, .basetype = &integer_type, .print = inet_service_type_print, + .json = inet_service_type_json, .parse = inet_service_type_parse, .sym_tbl = &inet_service_tbl, }; @@ -753,6 +758,7 @@ const struct datatype mark_type = { .basetype = &integer_type, .basefmt = "0x%.8Zx", .print = mark_type_print, + .json = mark_type_json, .parse = mark_type_parse, .flags = DTYPE_F_PREFIX, }; @@ -967,6 +973,7 @@ const struct datatype time_type = { .size = 8 * BITS_PER_BYTE, .basetype = &integer_type, .print = time_type_print, + .json = time_type_json, .parse = time_type_parse, }; @@ -1143,4 +1150,5 @@ const struct datatype boolean_type = { .size = 1, .basetype = &integer_type, .sym_tbl = &boolean_tbl, + .json = boolean_type_json, }; diff --git a/src/expression.c b/src/expression.c index 049c75a7787d1..a8c0cc7a5ae3b 100644 --- a/src/expression.c +++ b/src/expression.c @@ -23,6 +23,7 @@ #include #include #include +#include struct expr *expr_alloc(const struct location *loc, const struct expr_ops *ops, const struct datatype *dtype, enum byteorder byteorder, @@ -195,6 +196,7 @@ static const struct expr_ops verdict_expr_ops = { .type = EXPR_VERDICT, .name = "verdict", .print = verdict_expr_print, + .json = verdict_expr_json, .cmp = verdict_expr_cmp, .clone = verdict_expr_clone, .destroy = verdict_expr_destroy, @@ -319,6 +321,7 @@ static const struct expr_ops constant_expr_ops = { .type = EXPR_VALUE, .name = "value", .print = constant_expr_print, + .json = constant_expr_json, .cmp = constant_expr_cmp, .clone = constant_expr_clone, .destroy = constant_expr_destroy, @@ -464,6 +467,7 @@ static const struct expr_ops prefix_expr_ops = { .type = EXPR_PREFIX, .name = "prefix", .print = prefix_expr_print, + .json = prefix_expr_json, .set_type = prefix_expr_set_type, .clone = prefix_expr_clone, .destroy = prefix_expr_destroy, @@ -517,6 +521,7 @@ static const struct expr_ops unary_expr_ops = { .type = EXPR_UNARY, .name = "unary", .print = unary_expr_print, + .json = unary_expr_json, .clone = unary_expr_clone, .destroy = unary_expr_destroy, }; @@ -592,6 +597,7 @@ static const struct expr_ops binop_expr_ops = { .type = EXPR_BINOP, .name = "binop", .print = binop_expr_print, + .json = binop_expr_json, .clone = binop_expr_clone, .destroy = binop_expr_destroy, }; @@ -613,6 +619,7 @@ static const struct expr_ops relational_expr_ops = { .type = EXPR_RELATIONAL, .name = "relational", .print = binop_expr_print, + .json = relational_expr_json, .destroy = binop_expr_destroy, }; @@ -679,6 +686,7 @@ static const struct expr_ops range_expr_ops = { .type = EXPR_RANGE, .name = "range", .print = range_expr_print, + .json = range_expr_json, .clone = range_expr_clone, .destroy = range_expr_destroy, .set_type = range_expr_set_type, @@ -763,6 +771,7 @@ static const struct expr_ops concat_expr_ops = { .type = EXPR_CONCAT, .name = "concat", .print = concat_expr_print, + .json = concat_expr_json, .clone = compound_expr_clone, .destroy = concat_expr_destroy, }; @@ -781,6 +790,7 @@ static const struct expr_ops list_expr_ops = { .type = EXPR_LIST, .name = "list", .print = list_expr_print, + .json = list_expr_json, .clone = compound_expr_clone, .destroy = compound_expr_destroy, }; @@ -867,6 +877,7 @@ static const struct expr_ops set_expr_ops = { .type = EXPR_SET, .name = "set", .print = set_expr_print, + .json = set_expr_json, .set_type = set_expr_set_type, .clone = compound_expr_clone, .destroy = compound_expr_destroy, @@ -915,6 +926,7 @@ static const struct expr_ops mapping_expr_ops = { .type = EXPR_MAPPING, .name = "mapping", .print = mapping_expr_print, + .json = mapping_expr_json, .set_type = mapping_expr_set_type, .clone = mapping_expr_clone, .destroy = mapping_expr_destroy, @@ -959,6 +971,7 @@ static const struct expr_ops map_expr_ops = { .type = EXPR_MAP, .name = "map", .print = map_expr_print, + .json = map_expr_json, .clone = map_expr_clone, .destroy = map_expr_destroy, }; @@ -1000,6 +1013,7 @@ static const struct expr_ops set_ref_expr_ops = { .type = EXPR_SET_REF, .name = "set reference", .print = set_ref_expr_print, + .json = set_ref_expr_json, .clone = set_ref_expr_clone, .destroy = set_ref_expr_destroy, }; @@ -1055,6 +1069,7 @@ static const struct expr_ops set_elem_expr_ops = { .name = "set element", .clone = set_elem_expr_clone, .print = set_elem_expr_print, + .json = set_elem_expr_json, .destroy = set_elem_expr_destroy, }; diff --git a/src/exthdr.c b/src/exthdr.c index 06a82070a6bb2..cb0a58e8526a5 100644 --- a/src/exthdr.c +++ b/src/exthdr.c @@ -70,6 +70,7 @@ const struct expr_ops exthdr_expr_ops = { .type = EXPR_EXTHDR, .name = "exthdr", .print = exthdr_expr_print, + .json = exthdr_expr_json, .cmp = exthdr_expr_cmp, .clone = exthdr_expr_clone, }; @@ -107,6 +108,7 @@ static const struct stmt_ops exthdr_stmt_ops = { .type = STMT_EXTHDR, .name = "exthdr", .print = exthdr_stmt_print, + .json = exthdr_stmt_json, }; struct stmt *exthdr_stmt_alloc(const struct location *loc, diff --git a/src/fib.c b/src/fib.c index 069411f0131f2..9a19cc348a3df 100644 --- a/src/fib.c +++ b/src/fib.c @@ -105,6 +105,7 @@ static const struct expr_ops fib_expr_ops = { .type = EXPR_FIB, .name = "fib", .print = fib_expr_print, + .json = fib_expr_json, .cmp = fib_expr_cmp, .clone = fib_expr_clone, }; diff --git a/src/hash.c b/src/hash.c index e6999637566c3..a2d2314baa544 100644 --- a/src/hash.c +++ b/src/hash.c @@ -60,6 +60,7 @@ static const struct expr_ops hash_expr_ops = { .type = EXPR_HASH, .name = "hash", .print = hash_expr_print, + .json = hash_expr_json, .cmp = hash_expr_cmp, .clone = hash_expr_clone, }; diff --git a/src/json.c b/src/json.c new file mode 100644 index 0000000000000..e458eb3e4bdd8 --- /dev/null +++ b/src/json.c @@ -0,0 +1,1564 @@ +#define _GNU_SOURCE +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG +#define __json_pack json_pack +#define json_pack(...) ({ \ + json_t *__out = __json_pack(__VA_ARGS__); \ + assert(__out); \ + __out; \ +}) +#endif + +static json_t *expr_print_json(const struct expr *expr, struct output_ctx *octx) +{ + char buf[1024]; + FILE *fp; + + if (expr->ops->json) + return expr->ops->json(expr, octx); + + printf("warning: expr ops %s have no json callback\n", + expr->ops->name); + + fp = octx->output_fp; + octx->output_fp = fmemopen(buf, 1024, "w"); + + expr->ops->print(expr, octx); + + fclose(octx->output_fp); + octx->output_fp = fp; + + return json_pack("s", buf); +} + +static json_t *set_dtype_json(const struct expr *key) +{ + char *namedup = xstrdup(key->dtype->name), *tok; + json_t *root = NULL; + + tok = strtok(namedup, " ."); + while (tok) { + json_t *jtok = json_string(xstrdup(tok)); + if (!root) + root = jtok; + else if (json_is_string(root)) + root = json_pack("[o, o]", root, jtok); + else + json_array_append_new(root, jtok); + tok = strtok(NULL, " ."); + } + xfree(namedup); + return root; +} + +static json_t *set_print_json(struct output_ctx *octx, const struct set *set) +{ + json_t *root, *tmp; + const char *type, *datatype_ext = NULL; + + if (set->flags & NFT_SET_MAP) { + type = "map"; + datatype_ext = set->datatype->name; + } else if (set->flags & NFT_SET_OBJECT) { + type = "map"; + datatype_ext = obj_type_name(set->objtype); + } else if (set->flags & NFT_SET_EVAL) { + type = "meter"; + } else { + type = "set"; + } + + root = json_pack("{s:s, s:s, s:s, s:o}", + "family", family2str(set->handle.family), + "name", set->handle.set.name, + "table", set->handle.table.name, + "type", set_dtype_json(set->key)); + if (octx->handle) + json_object_set_new(root, "handle", + json_integer(set->handle.handle.id)); + if (datatype_ext) + json_object_set_new(root, "map", json_string(datatype_ext)); + + if (!(set->flags & (NFT_SET_CONSTANT))) { + if (set->policy != NFT_SET_POL_PERFORMANCE) { + tmp = json_pack("s", set_policy2str(set->policy)); + json_object_set_new(root, "policy", tmp); + } + if (set->desc.size) { + tmp = json_pack("i", set->desc.size); + json_object_set_new(root, "size", tmp); + } + } + + tmp = json_array(); + if (set->flags & NFT_SET_CONSTANT) + json_array_append_new(tmp, json_pack("s", "constant")); + if (set->flags & NFT_SET_INTERVAL) + json_array_append_new(tmp, json_pack("s", "interval")); + if (set->flags & NFT_SET_TIMEOUT) + json_array_append_new(tmp, json_pack("s", "timeout")); + + if (json_array_size(tmp) > 0) { + json_object_set_new(root, "flags", tmp); + } else { + if (json_array_size(tmp)) + json_object_set(root, "flags", json_array_get(tmp, 0)); + json_decref(tmp); + } + + if (set->timeout) { + tmp = json_pack("i", set->timeout / 1000); + json_object_set_new(root, "timeout", tmp); + } + if (set->gc_int) { + tmp = json_pack("i", set->gc_int / 1000); + json_object_set_new(root, "gc-interval", tmp); + } + + if (set->init && set->init->size > 0) { + json_t *array = json_array(); + const struct expr *i; + + list_for_each_entry(i, &set->init->expressions, list) + json_array_append_new(array, expr_print_json(i, octx)); + + json_object_set_new(root, "elem", array); + } + + return json_pack("{s:o}", type, root); +} + +static json_t *stmt_print_json(const struct stmt *stmt, struct output_ctx *octx) +{ + char buf[1024]; + FILE *fp; + + if (stmt->ops->json) + return stmt->ops->json(stmt, octx); + + printf("warning: stmt ops %s have no json callback\n", + stmt->ops->name); + + fp = octx->output_fp; + octx->output_fp = fmemopen(buf, 1024, "w"); + + stmt->ops->print(stmt, octx); + + fclose(octx->output_fp); + octx->output_fp = fp; + + return json_pack("s", buf); +} + +static json_t *rule_print_json(struct output_ctx *octx, + const struct rule *rule) +{ + const struct stmt *stmt; + json_t *root, *tmp; + + root = json_pack("{s:s, s:s, s:s, s:I}", + "family", family2str(rule->handle.family), + "table", rule->handle.table.name, + "chain", rule->handle.chain.name, + "position", rule->handle.position.id); + if (octx->handle) + json_object_set_new(root, "handle", + json_integer(rule->handle.handle.id)); + if (rule->comment) + json_object_set_new(root, "comment", + json_string(rule->comment)); + + tmp = json_array(); + list_for_each_entry(stmt, &rule->stmts, list) + json_array_append_new(tmp, stmt_print_json(stmt, octx)); + + if (json_array_size(tmp)) + json_object_set_new(root, "expr", tmp); + else { + fprintf(stderr, "rule without statements?!\n"); + json_decref(tmp); + } + + return json_pack("{s:o}", "rule", root); +} + +static json_t *chain_print_json(const struct output_ctx *octx, + const struct chain *chain) +{ + json_t *root, *tmp; + + root = json_pack("{s:s, s:s, s:s}", + "family", family2str(chain->handle.family), + "table", chain->handle.table.name, + "name", chain->handle.chain.name); + if (octx->handle) + json_object_set_new(root, "handle", + json_integer(chain->handle.handle.id)); + + if (chain->flags & CHAIN_F_BASECHAIN) { + tmp = json_pack("{s:s, s:s, s:i, s:s}", + "type", chain->type, + "hook", hooknum2str(chain->handle.family, + chain->hooknum), + "prio", chain->priority, + "policy", chain_policy2str(chain->policy)); + if (chain->dev) + json_object_set_new(tmp, "dev", json_string(chain->dev)); + json_object_update(root, tmp); + json_decref(tmp); + } + + return json_pack("{s:o}", "chain", root); +} + +static json_t *proto_name_json(uint8_t proto) +{ + const struct protoent *p = getprotobynumber(proto); + + if (p) + return json_string(p->p_name); + return json_integer(proto); +} + +static json_t *obj_print_json(struct output_ctx *octx, const struct obj *obj) +{ + const char *type = obj_type_name(obj->type); + json_t *root, *tmp; + const char *unit; + uint64_t rate; + + root = json_pack("{s:s, s:s, s:s}", + "family", family2str(obj->handle.family), + "name", obj->handle.obj.name, + "table", obj->handle.table.name); + if (octx->handle) + json_object_set_new(root, "handle", + json_integer(obj->handle.handle.id)); + + switch (obj->type) { + case NFT_OBJECT_COUNTER: + tmp = json_pack("{s:I, s:I}", + "packets", obj->counter.packets, + "bytes", obj->counter.bytes); + json_object_update(root, tmp); + json_decref(tmp); + break; + case NFT_OBJECT_QUOTA: + tmp = json_pack("{s:I, s:I, s:b}", + "bytes", obj->quota.bytes, + "used", obj->quota.used, + "inv", obj->quota.flags & NFT_QUOTA_F_INV); + json_object_update(root, tmp); + json_decref(tmp); + break; + case NFT_OBJECT_CT_HELPER: + type = "ct helper"; + tmp = json_pack("{s:s, s:o, s:s}", + "helper", obj->ct_helper.name, "protocol", + proto_name_json(obj->ct_helper.l4proto), + "l3proto", family2str(obj->ct_helper.l3proto)); + json_object_update(root, tmp); + json_decref(tmp); + break; + case NFT_OBJECT_LIMIT: + tmp = json_pack("{s:b, s:s}", + "inv", obj->limit.flags & NFT_LIMIT_F_INV, + "per", get_unit(obj->limit.unit)); + switch (obj->limit.type) { + case NFT_LIMIT_PKTS: + json_object_set_new(tmp, "rate", + json_integer(obj->limit.rate)); + json_object_set_new(tmp, "burst", + json_integer(obj->limit.burst)); + break; + case NFT_LIMIT_PKT_BYTES: + unit = get_rate(obj->limit.rate, &rate); + json_object_set_new(tmp, "rate", json_integer(rate)); + json_object_set_new(tmp, "rate_unit", + json_string(unit)); + if (obj->limit.burst) { + unit = get_rate(obj->limit.burst, &rate); + json_object_set_new(tmp, "burst", + json_integer(rate)); + json_object_set_new(tmp, "burst_unit", + json_string(unit)); + } + break; + } + + json_object_update(root, tmp); + json_decref(tmp); + break; + } + + return json_pack("{s:o}", type, root); +} + +static json_t *flowtable_print_json(const struct flowtable *ftable) +{ + json_t *root, *devs = NULL; + int i; + + root = json_pack("{s:s, s:s, s:s, s:s, s:i}", + "family", family2str(ftable->handle.family), + "name", ftable->handle.flowtable, + "table", ftable->handle.table.name, + "hook", hooknum2str(NFPROTO_NETDEV, ftable->hooknum), + "prio", ftable->priority); + + for (i = 0; i < ftable->dev_array_len; i++) { + const char *dev = ftable->dev_array[i]; + if (!devs) + devs = json_string(dev); + else if (json_is_string(devs)) + devs = json_pack("[o, s]", devs, dev); + else + json_array_append_new(devs, json_string(dev)); + } + if (devs) + json_object_set_new(root, "dev", devs); + + return json_pack("{s:o}", "flowtable", root); +} + +static json_t *table_flags_json(const struct table *table) +{ + uint32_t flags = table->flags; + json_t *root = json_array(), *tmp; + int i = 0; + + while (flags) { + if (flags & 0x1) { + tmp = json_string(table_flags_name[i]); + json_array_append_new(root, tmp); + } + flags >>= 1; + i++; + } + switch (json_array_size(root)) { + case 0: + json_decref(root); + return NULL; + case 1: + json_unpack(root, "[o]", &tmp); + json_decref(root); + root = tmp; + break; + } + return root; +} + +static json_t *table_print_json(const struct output_ctx *octx, + const struct table *table) +{ + json_t *root, *tmp; + + root = json_pack("{s:s, s:s}", + "family", family2str(table->handle.family), + "name", table->handle.table.name); + if (octx->handle) + json_object_set_new(root, "handle", + json_integer(table->handle.handle.id)); + + tmp = table_flags_json(table); + if (tmp) + json_object_set_new(root, "flags", tmp); + + return json_pack("{s:o}", "table", root); +} + +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)); +} + +json_t *relational_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + json_t *tmp; + + tmp = json_pack("{s:o, s:o}", + "left", expr_print_json(expr->left, octx), + "right", expr_print_json(expr->right, octx)); + /* XXX: check taken from binop_expr_print() + * if right is range, op is OP_EQ which in turn crashes nft if fed with it */ + if (expr_op_symbols[expr->op] && + (expr->op != OP_EQ || must_print_eq_op(expr))) + json_object_set_new(tmp, "op", + json_string(expr_op_symbols[expr->op])); + + return json_pack("{s:o}", "match", tmp); +} + +json_t *range_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + return json_pack("{s:[o, o]}", "range", + expr_print_json(expr->left, octx), + expr_print_json(expr->right, octx)); +} + +json_t *meta_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + return json_pack("{s:s}", "meta", meta_templates[expr->meta.key].token); +} + +json_t *payload_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + const struct proto_hdr_template *tmpl; + const struct proto_desc *desc; + json_t *root; + + desc = expr->payload.desc; + tmpl = expr->payload.tmpl; + if (payload_is_known(expr)) + root = json_pack("{s:s, s:s}", + "name", desc->name, + "field", tmpl->token); + else + root = json_pack("{s:s, s:s, s:i, s:i}", + "name", "raw", + "base", proto_base_tokens[expr->payload.base], + "offset", expr->payload.offset, + "len", expr->len); + + return json_pack("{s:o}", "payload", root); +} + +json_t *ct_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + const char *dirstr = ct_dir2str(expr->ct.direction); + enum nft_ct_keys key = expr->ct.key; + const struct proto_desc *desc; + json_t *root; + + root = json_pack("{s:s}", "key", ct_templates[key].token); + + if (expr->ct.direction < 0) + goto out; + + if (dirstr) + json_object_set_new(root, "dir", json_string(dirstr)); + + switch (key) { + case NFT_CT_SRC: + case NFT_CT_DST: + desc = proto_find_upper(&proto_inet, expr->ct.nfproto); + if (desc) + json_object_set_new(root, "family", + json_string(desc->name)); + break; + default: + break; + } +out: + return json_pack("{s:o}", "ct", root); +} + +json_t *concat_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + json_t *array = json_array(); + const struct expr *i; + + list_for_each_entry(i, &expr->expressions, list) + json_array_append_new(array, expr_print_json(i, octx)); + + return json_pack("{s:o}", "concat", array); +} + +json_t *set_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + json_t *array = json_array(); + const struct expr *i; + + list_for_each_entry(i, &expr->expressions, list) + json_array_append_new(array, expr_print_json(i, octx)); + + return json_pack("{s:o}", "set", array); +} + +json_t *set_ref_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + if (expr->set->flags & NFT_SET_ANONYMOUS) { + return expr_print_json(expr->set->init, octx); + } else { + return json_pack("s+", "@", expr->set->handle.set.name); + } +} + +json_t *set_elem_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + json_t *root = expr_print_json(expr->key, octx); + + if (!root) + return NULL; + + /* these element attributes require formal set elem syntax */ + if (expr->timeout || expr->expiration || expr->comment) { + root = json_pack("{s:o}", "val", root); + + if (expr->timeout) + json_object_set_new(root, "elem_timeout", + json_integer(expr->timeout / 1000)); + if (expr->expiration) + json_object_set_new(root, "elem_expires", + json_integer(expr->expiration / 1000)); + if (expr->comment) + json_object_set_new(root, "elem_comment", + json_string(expr->comment)); + return json_pack("{s:o}", "elem", root); + } + + return root; +} + +json_t *prefix_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + json_t *root = expr_print_json(expr->prefix, octx); + + return json_pack("{s:{s:o, s:i}}", "prefix", + "addr", root, + "len", expr->prefix_len); +} + +json_t *list_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + json_t *array = json_array(); + const struct expr *i; + + list_for_each_entry(i, &expr->expressions, list) + json_array_append_new(array, expr_print_json(i, octx)); + + //return json_pack("{s:s, s:o}", "type", "list", "val", array); + return array; +} + +json_t *unary_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + return expr_print_json(expr->arg, octx); +} + +json_t *mapping_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + return json_pack("[o, o]", + expr_print_json(expr->left, octx), + expr_print_json(expr->right, octx)); +} + +json_t *map_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + return json_pack("{s:{s:o, s:o}}", "map", + "left", expr_print_json(expr->map, octx), + "right", expr_print_json(expr->mappings, octx)); +} + +json_t *exthdr_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + const char *desc = expr->exthdr.desc ? + expr->exthdr.desc->name : + "unknown-exthdr"; + const char *field = expr->exthdr.tmpl->token; + json_t *root; + bool is_exists = expr->exthdr.flags & NFT_EXTHDR_F_PRESENT; + + if (expr->exthdr.op == NFT_EXTHDR_OP_TCPOPT) { + unsigned int offset = expr->exthdr.offset / 64; + + if (offset) { + const char *offstrs[] = { "0", "1", "2", "3" }; + const char *offstr = ""; + + if (offset < 4) + offstr = offstrs[offset]; + + root = json_pack("{s:s+}", "name", desc, offstr); + } else { + root = json_pack("{s:s}", "name", desc); + } + + if (!is_exists) + json_object_set_new(root, "field", json_string(field)); + + return json_pack("{s:o}", "tcp option", root); + } + + root = json_pack("{s:s}", + "name", desc); + if (!is_exists) + json_object_set_new(root, "field", json_string(field)); + + return json_pack("{s:o}", "exthdr", root); +} + +json_t *verdict_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + const struct { + int verdict; + const char *name; + bool chain; + } verdict_tbl[] = { + { NFT_CONTINUE, "continue", false }, + { NFT_BREAK, "break", false }, + { NFT_JUMP, "jump", true }, + { NFT_GOTO, "goto", true }, + { NFT_RETURN, "return", false }, + { NF_ACCEPT, "accept", false }, + { NF_DROP, "drop", false }, + { NF_QUEUE, "queue", false }, + }; + const char *name = NULL; + const char *chain = NULL; + unsigned int i; + + for (i = 0; i < array_size(verdict_tbl); i++) { + if (expr->verdict == verdict_tbl[i].verdict) { + name = verdict_tbl[i].name; + if (verdict_tbl[i].chain && expr->chain) + chain = expr->chain; + break; + } + } + if (!name) { + BUG("Unknown verdict %d.", expr->verdict); + return NULL; + } + return json_pack("{s:o}", name, chain ? json_string(chain) : json_null()); +} + +json_t *rt_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + const char *key = rt_templates[expr->rt.key].token; + json_t *root = json_pack("{s:s}", "key", key); + const char *family = NULL; + + switch (expr->rt.key) { + case NFT_RT_NEXTHOP4: + family = "ip"; + break; + case NFT_RT_NEXTHOP6: + family = "ip6"; + break; + default: + break; + } + + if (family) + json_object_set_new(root, "family", json_string(family)); + + return json_pack("{s:o}", "rt", root); +} + +json_t *numgen_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + const char *mode; + + switch (expr->numgen.type) { + case NFT_NG_INCREMENTAL: + mode = "inc"; + break; + case NFT_NG_RANDOM: + mode = "random"; + break; + default: + mode = "unknown"; + break; + } + + return json_pack("{s:{s:s, s:i, s:i}}", "numgen", + "mode", mode, + "mod", expr->numgen.mod, + "offset", expr->numgen.offset); +} + +json_t *hash_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + const char *type; + json_t *root, *jexpr = NULL; + + switch (expr->hash.type) { + case NFT_HASH_SYM: + type = "symhash"; + break; + case NFT_HASH_JENKINS: + default: + type = "jhash"; + jexpr = expr_print_json(expr->hash.expr, octx); + break; + } + + root = json_pack("{s:i}", "mod", expr->hash.mod); + if (expr->hash.seed_set) + json_object_set_new(root, "seed", + json_integer(expr->hash.seed)); + if (expr->hash.offset) + json_object_set_new(root, "offset", + json_integer(expr->hash.offset)); + if (jexpr) + json_object_set_new(root, "expr", jexpr); + + return json_pack("{s:o}", type, root); +} + +json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + const char *fib_flags[] = { "saddr", "daddr", "mark", "iif", "oif" }; + unsigned int flags = expr->fib.flags & ~NFTA_FIB_F_PRESENT; + json_t *root; + + root = json_pack("{s:s}", "result", fib_result_str(expr->fib.result)); + + if (flags) { + json_t *tmp = json_array(); + unsigned int i; + + for (i = 0; i < array_size(fib_flags); i++) { + if (flags & (1 << i)) { + json_array_append_new(tmp, json_string(fib_flags[i])); + flags &= ~(1 << i); + } + } + if (flags) + json_array_append_new(tmp, json_integer(flags)); + json_object_set_new(root, "flags", tmp); + } + return json_pack("{s:o}", "fib", root); +} + +static json_t *symbolic_constant_json(const struct symbol_table *tbl, + const struct expr *expr, + struct output_ctx *octx) +{ + unsigned int len = div_round_up(expr->len, BITS_PER_BYTE); + const struct symbolic_constant *s; + uint64_t val = 0; + + /* Export the data in the correct byteorder for comparison */ + assert(expr->len / BITS_PER_BYTE <= sizeof(val)); + mpz_export_data(constant_data_ptr(val, expr->len), expr->value, + expr->byteorder, len); + + for (s = tbl->symbols; s->identifier != NULL; s++) { + if (val == s->value) + break; + } + if (!s->identifier) + return expr_basetype(expr)->json(expr, octx); + + if (octx->numeric > NFT_NUMERIC_ALL) + return json_integer(val); + else + return json_string(s->identifier); +} + +static json_t *datatype_json(const struct expr *expr, struct output_ctx *octx) +{ + const struct datatype *dtype = expr->dtype; + + do { + if (dtype->json) + return dtype->json(expr, octx); + if (dtype->sym_tbl) + return symbolic_constant_json(dtype->sym_tbl, + expr, octx); + if (dtype->print) { + struct output_ctx octx = { .numeric = 3 }; + char buf[1024]; + + octx.output_fp = fmemopen(buf, 1024, "w"); + dtype->print(expr, &octx); + fclose(octx.output_fp); + + if (buf[0] == '"') { + memmove(buf, buf + 1, strlen(buf)); + *strchrnul(buf, '"') = '\0'; + } + + return json_string(buf); + } + } while ((dtype = dtype->basetype)); + + BUG("datatype %s has no print method or symbol table\n", + expr->dtype->name); +} + +json_t *constant_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + return datatype_json(expr, octx); +} + +json_t *integer_type_json(const struct expr *expr, struct output_ctx *octx) +{ + char buf[1024] = "0x"; + + if (mpz_fits_ulong_p(expr->value)) + return json_integer(mpz_get_ui(expr->value)); + + mpz_get_str(buf + 2, 16, expr->value); + return json_string(buf); +} + +json_t *string_type_json(const struct expr *expr, struct output_ctx *octx) +{ + unsigned int len = div_round_up(expr->len, BITS_PER_BYTE); + char data[len+1]; + + mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len); + data[len] = '\0'; + + return json_string(data); +} + +json_t *boolean_type_json(const struct expr *expr, struct output_ctx *octx) +{ + unsigned int len = div_round_up(expr->len, BITS_PER_BYTE); + uint64_t val = 0; + + /* Export the data in the correct byteorder for comparison */ + assert(expr->len / BITS_PER_BYTE <= sizeof(val)); + mpz_export_data(constant_data_ptr(val, expr->len), expr->value, + expr->byteorder, len); + + return json_boolean((int)val); +} + +json_t *inet_protocol_type_json(const struct expr *expr, + struct output_ctx *octx) +{ + struct protoent *p; + + if (octx->numeric < NFT_NUMERIC_ALL) { + p = getprotobynumber(mpz_get_uint8(expr->value)); + if (p != NULL) + return json_string(p->p_name); + } + return integer_type_json(expr, octx); +} + +json_t *inet_service_type_json(const struct expr *expr, struct output_ctx *octx) +{ + if (octx->numeric >= NFT_NUMERIC_PORT) + return integer_type_json(expr, octx); + + return symbolic_constant_json(&inet_service_tbl, expr, octx); +} + +json_t *mark_type_json(const struct expr *expr, struct output_ctx *octx) +{ + return symbolic_constant_json(mark_tbl, expr, octx); +} + +json_t *devgroup_type_json(const struct expr *expr, struct output_ctx *octx) +{ + return symbolic_constant_json(devgroup_tbl, expr, octx); +} + +json_t *ct_label_type_json(const struct expr *expr, struct output_ctx *octx) +{ + unsigned long bit = mpz_scan1(expr->value, 0); + const char *labelstr = ct_label2str(bit); + + if (labelstr) + return json_string(labelstr); + + /* can happen when connlabel.conf is altered after rules were added */ + return json_integer(bit); +} + +json_t *time_type_json(const struct expr *expr, struct output_ctx *octx) +{ + return json_integer(mpz_get_uint64(expr->value) / MSEC_PER_SEC); +} + +json_t *uid_type_json(const struct expr *expr, struct output_ctx *octx) +{ + uint32_t uid = mpz_get_uint32(expr->value); + + if (octx->numeric < NFT_NUMERIC_ALL) { + struct passwd *pw = getpwuid(uid); + + if (pw) + return json_string(pw->pw_name); + } + return json_integer(uid); +} + +json_t *gid_type_json(const struct expr *expr, struct output_ctx *octx) +{ + uint32_t gid = mpz_get_uint32(expr->value); + + if (octx->numeric < NFT_NUMERIC_ALL) { + struct group *gr = getgrgid(gid); + + if (gr) + return json_string(gr->gr_name); + } + return json_integer(gid); +} + +json_t *expr_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + return expr_print_json(stmt->expr, octx); +} + +json_t *payload_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + return json_pack("{s: {s:o, s:o}}", "mangle", + "left", expr_print_json(stmt->payload.expr, octx), + "right", expr_print_json(stmt->payload.val, octx)); +} + +json_t *exthdr_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + return json_pack("{s: {s:o, s:o}}", "mangle", + "left", expr_print_json(stmt->exthdr.expr, octx), + "right", expr_print_json(stmt->exthdr.val, octx)); +} + +json_t *quota_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + const char *data_unit; + uint64_t bytes; + json_t *root; + + data_unit = get_rate(stmt->quota.bytes, &bytes); + root = json_pack("{s:I, s:s}", + "val", bytes, + "val_unit", data_unit); + + if (stmt->quota.flags & NFT_QUOTA_F_INV) + json_object_set_new(root, "inv", json_true()); + if (!octx->stateless && stmt->quota.used) { + data_unit = get_rate(stmt->quota.used, &bytes); + json_object_set_new(root, "used", json_integer((int)bytes)); + json_object_set_new(root, "used_unit", json_string(data_unit)); + } + + return json_pack("{s:o}", "quota", root); +} + +json_t *ct_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + struct expr expr = { + .ct = { + .key = stmt->ct.key, + .direction = stmt->ct.direction, + .nfproto = 0, + }, + }; + + return json_pack("{s:{s:o, s:o}}", "mangle", + "left", ct_expr_json(&expr, octx), + "right", expr_print_json(stmt->ct.expr, octx)); +} + +json_t *limit_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + const char *rate_unit = NULL, *burst_unit = NULL; + bool inv = stmt->limit.flags & NFT_LIMIT_F_INV; + uint64_t burst = stmt->limit.burst; + uint64_t rate = stmt->limit.rate; + json_t *root; + + if (stmt->limit.type == NFT_LIMIT_PKT_BYTES) { + rate_unit = get_rate(stmt->limit.rate, &rate); + burst_unit = get_rate(stmt->limit.burst, &burst); + } + + root = json_pack("{s:I, s:s}", + "rate", rate, + "per", get_unit(stmt->limit.unit)); + if (inv) + json_object_set_new(root, "inv", json_boolean(inv)); + if (rate_unit) + json_object_set_new(root, "rate_unit", json_string(rate_unit)); + if (burst) { + json_object_set_new(root, "burst", json_integer(burst)); + if (burst_unit) + json_object_set_new(root, "burst_unit", + json_string(burst_unit)); + } + + return json_pack("{s:o}", "limit", root); +} + +json_t *fwd_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + json_t *root; + + root = expr_print_json(stmt->fwd.to, octx); + return json_pack("{s:o}", "fwd", root); +} + +json_t *notrack_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + return json_pack("{s:n}", "notrack"); +} + +json_t *dup_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + json_t *root = json_object(); + + if (stmt->dup.to) { + root = json_pack("{s:o}", "addr", expr_print_json(stmt->dup.to, octx)); + if (stmt->dup.dev) + json_object_set_new(root, "dev", + expr_print_json(stmt->dup.dev, octx)); + } else { + root = json_null(); + } + return json_pack("{s:o}", "dup", root); +} + +json_t *meta_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + json_t *root; + + root = json_pack("{s:s}", "meta", meta_templates[stmt->meta.key].token); + root = json_pack("{s:o, s:o}", + "left", root, + "right", expr_print_json(stmt->meta.expr, octx)); + + return json_pack("{s:o}", "mangle", root); +} + +json_t *log_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + json_t *root = json_object(), *flags; + + if (stmt->log.flags & STMT_LOG_PREFIX) + json_object_set_new(root, "prefix", + json_string(stmt->log.prefix)); + if (stmt->log.flags & STMT_LOG_GROUP) + json_object_set_new(root, "group", + json_integer(stmt->log.group)); + if (stmt->log.flags & STMT_LOG_SNAPLEN) + json_object_set_new(root, "snaplen", + json_integer(stmt->log.snaplen)); + if (stmt->log.flags & STMT_LOG_QTHRESHOLD) + json_object_set_new(root, "queue-threshold", + json_integer(stmt->log.qthreshold)); + if ((stmt->log.flags & STMT_LOG_LEVEL) && + stmt->log.level != LOG_WARNING) + json_object_set_new(root, "level", + json_string(log_level(stmt->log.level))); + + flags = json_array(); + + if ((stmt->log.logflags & NF_LOG_MASK) == NF_LOG_MASK) { + json_array_append_new(flags, json_string("all")); + } else { + if (stmt->log.logflags & NF_LOG_TCPSEQ) + json_array_append_new(flags, + json_string("tcp sequence")); + if (stmt->log.logflags & NF_LOG_TCPOPT) + json_array_append_new(flags, + json_string("tcp options")); + if (stmt->log.logflags & NF_LOG_IPOPT) + json_array_append_new(flags, json_string("ip options")); + if (stmt->log.logflags & NF_LOG_UID) + json_array_append_new(flags, json_string("skuid")); + if (stmt->log.logflags & NF_LOG_MACDECODE) + json_array_append_new(flags, json_string("ether")); + } + if (json_array_size(flags) > 1) { + json_object_set_new(root, "flags", flags); + } else { + if (json_array_size(flags)) + json_object_set(root, "flags", + json_array_get(flags, 0)); + json_decref(flags); + } + + if (!json_object_size(root)) { + json_decref(root); + root = json_null(); + } + + return json_pack("{s:o}", "log", root); +} + +static json_t *nat_flags_json(int flags) +{ + json_t *array = json_array(); + + if (flags & NF_NAT_RANGE_PROTO_RANDOM) + json_array_append_new(array, json_string("random")); + if (flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) + json_array_append_new(array, json_string("fully-random")); + if (flags & NF_NAT_RANGE_PERSISTENT) + json_array_append_new(array, json_string("persistent")); + return array; +} + +json_t *nat_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + json_t *root = json_object(); + json_t *array = nat_flags_json(stmt->nat.flags); + + if (stmt->nat.addr) + json_object_set_new(root, "addr", + expr_print_json(stmt->nat.addr, octx)); + + if (stmt->nat.proto) + json_object_set_new(root, "port", + expr_print_json(stmt->nat.proto, octx)); + + if (json_array_size(array) > 1) { + json_object_set_new(root, "flags", array); + } else { + if (json_array_size(array)) + json_object_set(root, "flags", + json_array_get(array, 0)); + json_decref(array); + } + + if (!json_object_size(root)) { + json_decref(root); + root = json_null(); + } + + return json_pack("{s:o}", nat_etype2str(stmt->nat.type), root); +} + +json_t *reject_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + json_t *root, *jexpr = NULL; + const char *type = NULL; + + switch (stmt->reject.type) { + case NFT_REJECT_TCP_RST: + type = "tcp reset"; + break; + case NFT_REJECT_ICMPX_UNREACH: + if (stmt->reject.icmp_code == NFT_REJECT_ICMPX_PORT_UNREACH) + break; + type = "icmpx"; + jexpr = expr_print_json(stmt->reject.expr, octx); + break; + case NFT_REJECT_ICMP_UNREACH: + switch (stmt->reject.family) { + case NFPROTO_IPV4: + if (stmt->reject.icmp_code == ICMP_PORT_UNREACH) + break; + type = "icmp"; + jexpr = expr_print_json(stmt->reject.expr, octx); + break; + case NFPROTO_IPV6: + if (stmt->reject.icmp_code == ICMP6_DST_UNREACH_NOPORT) + break; + type = "icmpv6"; + jexpr = expr_print_json(stmt->reject.expr, octx); + break; + } + } + + if (!type && !jexpr) + return json_pack("{s:n}", "reject"); + + root = json_object(); + if (type) + json_object_set_new(root, "type", json_string(type)); + if (jexpr) + json_object_set_new(root, "expr", jexpr); + + return json_pack("{s:o}", "reject", root); +} + +json_t *counter_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + if (octx->stateless) + return json_pack("{s:n}", "counter"); + + return json_pack("{s:{s:I, s:I}}", "counter", + "packets", stmt->counter.packets, + "bytes", stmt->counter.bytes); +} + +json_t *set_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + return json_pack("{s:{s:s, s:o, s:s+}}", "set", + "op", set_stmt_op_names[stmt->set.op], + "elem", expr_print_json(stmt->set.key, octx), + "set", "@", stmt->set.set->set->handle.set.name); +} + +json_t *objref_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + const char *name; + + if (stmt->objref.type > NFT_OBJECT_MAX) + name = "unknown"; + else + name = objref_type_name(stmt->objref.type); + + return json_pack("{s:o}", name, expr_print_json(stmt->objref.expr, octx)); +} + +json_t *meter_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + json_t *root, *tmp; + + octx->stateless++; + tmp = stmt_print_json(stmt->meter.stmt, octx); + octx->stateless--; + + root = json_pack("{s:o, s:o}", + "key", expr_print_json(stmt->meter.key, octx), + "stmt", tmp); + if (stmt->meter.set) { + tmp = json_string(stmt->meter.set->set->handle.set.name); + json_object_set_new(root, "name", tmp); + } + + return json_pack("{s:o}", "meter", root); +} + +json_t *queue_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + json_t *root, *flags; + + root = json_object(); + + if (stmt->queue.queue) + json_object_set_new(root, "num", + expr_print_json(stmt->queue.queue, octx)); + + flags = json_array(); + if (stmt->queue.flags & NFT_QUEUE_FLAG_BYPASS) + json_array_append_new(flags, json_string("bypass")); + if (stmt->queue.flags & NFT_QUEUE_FLAG_CPU_FANOUT) + json_array_append_new(flags, json_string("fanout")); + if (json_array_size(flags) > 1) { + json_object_set_new(root, "flags", flags); + } else { + if (json_array_size(flags)) + json_object_set(root, "flags", + json_array_get(flags, 0)); + json_decref(flags); + } + + if (!json_object_size(root)) { + json_decref(root); + root = json_null(); + } + + return json_pack("{s:o}", "queue", root); +} + +json_t *verdict_stmt_json(const struct stmt *stmt, struct output_ctx *octx) +{ + return expr_print_json(stmt->expr, octx); +} + +static json_t *table_print_json_full(struct netlink_ctx *ctx, + struct table *table) +{ + json_t *root = json_array(), *tmp; + struct flowtable *flowtable; + struct chain *chain; + struct rule *rule; + struct obj *obj; + struct set *set; + + tmp = table_print_json(ctx->octx, table); + json_array_append_new(root, tmp); + + list_for_each_entry(obj, &table->objs, list) { + tmp = obj_print_json(ctx->octx, obj); + json_array_append_new(root, tmp); + } + list_for_each_entry(set, &table->sets, list) { + if (set->flags & NFT_SET_ANONYMOUS) + continue; + tmp = set_print_json(ctx->octx, set); + json_array_append_new(root, tmp); + } + list_for_each_entry(flowtable, &table->flowtables, list) { + tmp = flowtable_print_json(flowtable); + json_array_append_new(root, tmp); + } + list_for_each_entry(chain, &table->chains, list) { + tmp = chain_print_json(ctx->octx, chain); + json_array_append_new(root, tmp); + + list_for_each_entry(rule, &chain->rules, list) { + tmp = rule_print_json(ctx->octx, rule); + json_array_append_new(root, tmp); + } + } + + return root; +} + +static json_t *do_list_ruleset_json(struct netlink_ctx *ctx, struct cmd *cmd) +{ + unsigned int family = cmd->handle.family; + json_t *root = json_array(); + struct table *table; + + list_for_each_entry(table, &ctx->cache->list, list) { + if (family != NFPROTO_UNSPEC && + table->handle.family != family) + continue; + + json_array_extend(root, table_print_json_full(ctx, table)); + } + + return root; +} + +static json_t *do_list_tables_json(struct netlink_ctx *ctx, struct cmd *cmd) +{ + unsigned int family = cmd->handle.family; + json_t *root = json_array(); + struct table *table; + + list_for_each_entry(table, &ctx->cache->list, list) { + if (family != NFPROTO_UNSPEC && + table->handle.family != family) + continue; + + json_array_append_new(root, table_print_json(ctx->octx, table)); + } + + return root; +} + +static json_t *do_list_table_json(struct netlink_ctx *ctx, + struct cmd *cmd, struct table *table) +{ + return table_print_json_full(ctx, table); +} + +static json_t *do_list_chain_json(struct netlink_ctx *ctx, + struct cmd *cmd, struct table *table) +{ + json_t *root = json_array(); + struct chain *chain; + struct rule *rule; + + list_for_each_entry(chain, &table->chains, list) { + if (chain->handle.family != cmd->handle.family || + strcmp(cmd->handle.chain.name, chain->handle.chain.name)) + continue; + + json_array_append_new(root, chain_print_json(ctx->octx, chain)); + + list_for_each_entry(rule, &chain->rules, list) { + json_t *tmp = rule_print_json(ctx->octx, rule); + + json_array_append_new(root, tmp); + } + } + + return root; +} + +static json_t *do_list_chains_json(struct netlink_ctx *ctx, struct cmd *cmd) +{ + json_t *root = json_array(); + struct table *table; + struct chain *chain; + + list_for_each_entry(table, &ctx->cache->list, list) { + if (cmd->handle.family != NFPROTO_UNSPEC && + cmd->handle.family != table->handle.family) + continue; + + list_for_each_entry(chain, &table->chains, list) { + json_t *tmp = chain_print_json(ctx->octx, chain); + + json_array_append_new(root, tmp); + } + } + + return root; +} + +static json_t *do_list_set_json(struct netlink_ctx *ctx, + struct cmd *cmd, struct table *table) +{ + struct set *set = set_lookup(table, cmd->handle.set.name); + + if (set == NULL) + return json_null(); + + return json_pack("[o]", set_print_json(ctx->octx, set)); +} + +static json_t *do_list_sets_json(struct netlink_ctx *ctx, struct cmd *cmd) +{ + struct output_ctx *octx = ctx->octx; + json_t *root = json_array(); + struct table *table; + struct set *set; + + list_for_each_entry(table, &ctx->cache->list, list) { + if (cmd->handle.family != NFPROTO_UNSPEC && + cmd->handle.family != table->handle.family) + continue; + + list_for_each_entry(set, &table->sets, list) { + if (cmd->obj == CMD_OBJ_SETS && + (set->flags & NFT_SET_ANONYMOUS || + set->flags & NFT_SET_MAP)) + continue; + if (cmd->obj == CMD_OBJ_METERS && + !(set->flags & NFT_SET_EVAL)) + continue; + if (cmd->obj == CMD_OBJ_MAPS && + !(set->flags & NFT_SET_MAP)) + continue; + json_array_append_new(root, set_print_json(octx, set)); + } + } + + return root; +} + +static json_t *do_list_obj_json(struct netlink_ctx *ctx, + struct cmd *cmd, uint32_t type) +{ + json_t *root = json_array(); + struct table *table; + struct obj *obj; + + list_for_each_entry(table, &ctx->cache->list, list) { + if (cmd->handle.family != NFPROTO_UNSPEC && + cmd->handle.family != table->handle.family) + continue; + + if (cmd->handle.table.name && + strcmp(cmd->handle.table.name, table->handle.table.name)) + continue; + + list_for_each_entry(obj, &table->objs, list) { + if (obj->type != type || + (cmd->handle.obj.name && + strcmp(cmd->handle.obj.name, obj->handle.obj.name))) + continue; + + json_array_append_new(root, + obj_print_json(ctx->octx, obj)); + } + } + + return root; +} + +static json_t *do_list_flowtables_json(struct netlink_ctx *ctx, struct cmd *cmd) +{ + json_t *root = json_array(), *tmp; + struct flowtable *flowtable; + struct table *table; + + list_for_each_entry(table, &ctx->cache->list, list) { + if (cmd->handle.family != NFPROTO_UNSPEC && + cmd->handle.family != table->handle.family) + continue; + + list_for_each_entry(flowtable, &table->flowtables, list) { + tmp = flowtable_print_json(flowtable); + json_array_append_new(root, tmp); + } + } + + return root; +} + +int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) +{ + struct table *table = NULL; + json_t *root; + + if (cmd->handle.table.name) + table = table_lookup(&cmd->handle, ctx->cache); + + switch (cmd->obj) { + case CMD_OBJ_TABLE: + if (!cmd->handle.table.name) { + root = do_list_tables_json(ctx, cmd); + break; + } + root = do_list_table_json(ctx, cmd, table); + break; + case CMD_OBJ_CHAIN: + root = do_list_chain_json(ctx, cmd, table); + break; + case CMD_OBJ_CHAINS: + root = do_list_chains_json(ctx, cmd); + break; + case CMD_OBJ_SETS: + root = do_list_sets_json(ctx, cmd); + break; + case CMD_OBJ_SET: + root = do_list_set_json(ctx, cmd, table); + break; + case CMD_OBJ_RULESET: + root = do_list_ruleset_json(ctx, cmd); + break; + case CMD_OBJ_METERS: + root = do_list_sets_json(ctx, cmd); + break; + case CMD_OBJ_METER: + root = do_list_set_json(ctx, cmd, table); + break; + case CMD_OBJ_MAPS: + root = do_list_sets_json(ctx, cmd); + break; + case CMD_OBJ_MAP: + root = do_list_set_json(ctx, cmd, table); + break; + case CMD_OBJ_COUNTER: + case CMD_OBJ_COUNTERS: + root = do_list_obj_json(ctx, cmd, NFT_OBJECT_COUNTER); + break; + case CMD_OBJ_QUOTA: + case CMD_OBJ_QUOTAS: + root = do_list_obj_json(ctx, cmd, NFT_OBJECT_QUOTA); + break; + case CMD_OBJ_CT_HELPER: + case CMD_OBJ_CT_HELPERS: + root = do_list_obj_json(ctx, cmd, NFT_OBJECT_CT_HELPER); + break; + case CMD_OBJ_LIMIT: + case CMD_OBJ_LIMITS: + root = do_list_obj_json(ctx, cmd, NFT_OBJECT_LIMIT); + break; + case CMD_OBJ_FLOWTABLES: + root = do_list_flowtables_json(ctx, cmd); + break; + default: + BUG("invalid command object type %u\n", cmd->obj); + } + + if (json_is_array(root) && !json_array_size(root)) { + json_decref(root); + root = json_null(); + } + root = json_pack("{s:o}", "nftables", root); + json_dumpf(root, ctx->octx->output_fp, 0); + json_decref(root); + return 0; +} diff --git a/src/libnftables.c b/src/libnftables.c index ae61ce652941d..68e53f70cae8f 100644 --- a/src/libnftables.c +++ b/src/libnftables.c @@ -373,6 +373,22 @@ void nft_ctx_output_set_echo(struct nft_ctx *ctx, bool val) ctx->output.echo = val; } +bool nft_ctx_output_get_json(struct nft_ctx *ctx) +{ +#ifdef HAVE_LIBJANSSON + return ctx->output.json; +#else + return false; +#endif +} + +void nft_ctx_output_set_json(struct nft_ctx *ctx, bool val) +{ +#ifdef HAVE_LIBJANSSON + ctx->output.json = val; +#endif +} + static const struct input_descriptor indesc_cmdline = { .type = INDESC_BUFFER, .name = "", diff --git a/src/main.c b/src/main.c index d26ea01889a08..f36159744744a 100644 --- a/src/main.c +++ b/src/main.c @@ -31,6 +31,7 @@ enum opt_vals { OPT_FILE = 'f', OPT_INTERACTIVE = 'i', OPT_INCLUDEPATH = 'I', + OPT_JSON = 'j', OPT_NUMERIC = 'n', OPT_STATELESS = 's', OPT_IP2NAME = 'N', @@ -40,7 +41,7 @@ enum opt_vals { OPT_INVALID = '?', }; -#define OPTSTRING "hvcf:iI:vnsNae" +#define OPTSTRING "hvcf:iI:jvnsNae" static const struct option options[] = { { @@ -94,6 +95,10 @@ static const struct option options[] = { .name = "echo", .val = OPT_ECHO, }, + { + .name = "json", + .val = OPT_JSON, + }, { .name = NULL } @@ -112,6 +117,7 @@ static void show_help(const char *name) " -f, --file Read input from \n" " -i, --interactive Read input from interactive CLI\n" "\n" +" -j, --json Format output in JSON\n" " -n, --numeric When specified once, show network addresses numerically (default behaviour).\n" " Specify twice to also show Internet services (port numbers) numerically.\n" " Specify three times to also show protocols, user IDs, and group IDs numerically.\n" @@ -255,6 +261,9 @@ int main(int argc, char * const *argv) case OPT_ECHO: nft_ctx_output_set_echo(nft, true); break; + case OPT_JSON: + nft_ctx_output_set_json(nft, true); + break; case OPT_INVALID: exit(EXIT_FAILURE); } diff --git a/src/meta.c b/src/meta.c index fb94ed406d4a5..ff0cb122d7dfa 100644 --- a/src/meta.c +++ b/src/meta.c @@ -35,6 +35,7 @@ #include #include #include +#include static struct symbol_table *realm_tbl; void realm_table_meta_init(void) @@ -251,6 +252,7 @@ const struct datatype uid_type = { .size = sizeof(uid_t) * BITS_PER_BYTE, .basetype = &integer_type, .print = uid_type_print, + .json = uid_type_json, .parse = uid_type_parse, }; @@ -303,6 +305,7 @@ const struct datatype gid_type = { .size = sizeof(gid_t) * BITS_PER_BYTE, .basetype = &integer_type, .print = gid_type_print, + .json = gid_type_json, .parse = gid_type_parse, }; @@ -366,6 +369,7 @@ const struct datatype devgroup_type = { .size = 4 * BITS_PER_BYTE, .basetype = &integer_type, .print = devgroup_type_print, + .json = devgroup_type_json, .parse = devgroup_type_parse, .flags = DTYPE_F_PREFIX, }; @@ -550,6 +554,7 @@ static const struct expr_ops meta_expr_ops = { .type = EXPR_META, .name = "meta", .print = meta_expr_print, + .json = meta_expr_json, .cmp = meta_expr_cmp, .clone = meta_expr_clone, .pctx_update = meta_expr_pctx_update, @@ -603,6 +608,7 @@ static const struct stmt_ops meta_stmt_ops = { .type = STMT_META, .name = "meta", .print = meta_stmt_print, + .json = meta_stmt_json, }; struct stmt *meta_stmt_alloc(const struct location *loc, enum nft_meta_keys key, diff --git a/src/numgen.c b/src/numgen.c index aa6da490d5d99..b7751b0727c2c 100644 --- a/src/numgen.c +++ b/src/numgen.c @@ -55,6 +55,7 @@ static const struct expr_ops numgen_expr_ops = { .type = EXPR_NUMGEN, .name = "numgen", .print = numgen_expr_print, + .json = numgen_expr_json, .cmp = numgen_expr_cmp, .clone = numgen_expr_clone, }; diff --git a/src/payload.c b/src/payload.c index 09665a0e81568..6517686cbfba5 100644 --- a/src/payload.c +++ b/src/payload.c @@ -25,6 +25,7 @@ #include #include #include +#include bool payload_is_known(const struct expr *expr) { @@ -107,6 +108,7 @@ static const struct expr_ops payload_expr_ops = { .type = EXPR_PAYLOAD, .name = "payload", .print = payload_expr_print, + .json = payload_expr_json, .cmp = payload_expr_cmp, .clone = payload_expr_clone, .pctx_update = payload_expr_pctx_update, @@ -191,6 +193,7 @@ static const struct stmt_ops payload_stmt_ops = { .type = STMT_PAYLOAD, .name = "payload", .print = payload_stmt_print, + .json = payload_stmt_json, }; struct stmt *payload_stmt_alloc(const struct location *loc, diff --git a/src/rt.c b/src/rt.c index 2530b663bb249..caa4947d048a5 100644 --- a/src/rt.c +++ b/src/rt.c @@ -22,6 +22,7 @@ #include #include #include +#include static struct symbol_table *realm_tbl; void realm_table_rt_init(void) @@ -112,6 +113,7 @@ static const struct expr_ops rt_expr_ops = { .type = EXPR_RT, .name = "rt", .print = rt_expr_print, + .json = rt_expr_json, .cmp = rt_expr_cmp, .clone = rt_expr_clone, }; diff --git a/src/rule.c b/src/rule.c index 7f00a24cda73d..5515f001bfe39 100644 --- a/src/rule.c +++ b/src/rule.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -1811,6 +1812,9 @@ static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd) { struct table *table = NULL; + if (ctx->octx->json) + return do_command_list_json(ctx, cmd); + if (cmd->handle.table.name != NULL) table = table_lookup(&cmd->handle, ctx->cache); diff --git a/src/statement.c b/src/statement.c index 8160e0adfce49..d2910018b47eb 100644 --- a/src/statement.c +++ b/src/statement.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -79,6 +80,7 @@ static const struct stmt_ops expr_stmt_ops = { .type = STMT_EXPRESSION, .name = "expression", .print = expr_stmt_print, + .json = expr_stmt_json, .destroy = expr_stmt_destroy, }; @@ -95,6 +97,7 @@ static const struct stmt_ops verdict_stmt_ops = { .type = STMT_VERDICT, .name = "verdict", .print = expr_stmt_print, + .json = verdict_stmt_json, .destroy = expr_stmt_destroy, }; @@ -137,6 +140,7 @@ static const struct stmt_ops meter_stmt_ops = { .type = STMT_METER, .name = "meter", .print = meter_stmt_print, + .json = meter_stmt_json, .destroy = meter_stmt_destroy, }; @@ -160,6 +164,7 @@ static const struct stmt_ops counter_stmt_ops = { .type = STMT_COUNTER, .name = "counter", .print = counter_stmt_print, + .json = counter_stmt_json, }; struct stmt *counter_stmt_alloc(const struct location *loc) @@ -204,6 +209,7 @@ static const struct stmt_ops objref_stmt_ops = { .type = STMT_OBJREF, .name = "objref", .print = objref_stmt_print, + .json = objref_stmt_json, }; struct stmt *objref_stmt_alloc(const struct location *loc) @@ -293,6 +299,7 @@ static const struct stmt_ops log_stmt_ops = { .type = STMT_LOG, .name = "log", .print = log_stmt_print, + .json = log_stmt_json, .destroy = log_stmt_destroy, }; @@ -376,6 +383,7 @@ static const struct stmt_ops limit_stmt_ops = { .type = STMT_LIMIT, .name = "limit", .print = limit_stmt_print, + .json = limit_stmt_json, }; struct stmt *limit_stmt_alloc(const struct location *loc) @@ -409,6 +417,7 @@ static const struct stmt_ops queue_stmt_ops = { .type = STMT_QUEUE, .name = "queue", .print = queue_stmt_print, + .json = queue_stmt_json, }; struct stmt *queue_stmt_alloc(const struct location *loc) @@ -436,6 +445,7 @@ static const struct stmt_ops quota_stmt_ops = { .type = STMT_QUOTA, .name = "quota", .print = quota_stmt_print, + .json = quota_stmt_json, }; struct stmt *quota_stmt_alloc(const struct location *loc) @@ -483,6 +493,7 @@ static const struct stmt_ops reject_stmt_ops = { .type = STMT_REJECT, .name = "reject", .print = reject_stmt_print, + .json = reject_stmt_json, }; struct stmt *reject_stmt_alloc(const struct location *loc) @@ -572,6 +583,7 @@ static const struct stmt_ops nat_stmt_ops = { .type = STMT_NAT, .name = "nat", .print = nat_stmt_print, + .json = nat_stmt_json, .destroy = nat_stmt_destroy, }; @@ -608,6 +620,7 @@ static const struct stmt_ops set_stmt_ops = { .type = STMT_SET, .name = "set", .print = set_stmt_print, + .json = set_stmt_json, .destroy = set_stmt_destroy, }; @@ -669,6 +682,7 @@ static const struct stmt_ops dup_stmt_ops = { .type = STMT_DUP, .name = "dup", .print = dup_stmt_print, + .json = dup_stmt_json, .destroy = dup_stmt_destroy, }; @@ -692,6 +706,7 @@ static const struct stmt_ops fwd_stmt_ops = { .type = STMT_FWD, .name = "fwd", .print = fwd_stmt_print, + .json = fwd_stmt_json, .destroy = fwd_stmt_destroy, }; From patchwork Tue May 8 11:08:38 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910124 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGvd6vMKz9rxs for ; Tue, 8 May 2018 21:09:01 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752174AbeEHLJA (ORCPT ); Tue, 8 May 2018 07:09:00 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36666 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750973AbeEHLI6 (ORCPT ); Tue, 8 May 2018 07:08:58 -0400 Received: from localhost ([::1]:51942 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0UJ-0003q8-LP; Tue, 08 May 2018 13:08:55 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 07/14] libnftables: Implement JSON parser Date: Tue, 8 May 2018 13:08:38 +0200 Message-Id: <20180508110845.26364-8-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org If JSON output setting is active in current context, try parsing any input as JSON. If the initial loading of the buffer or filename by libjansson fails, fall back to regular syntax parser. Signed-off-by: Phil Sutter --- Changes since v1: - Fix typo in 'icmpv6' constant. Changes since v2: - Fix for changes in struct handle. --- include/json.h | 20 + src/Makefile.am | 2 +- src/libnftables.c | 13 +- src/parser_json.c | 3141 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 3172 insertions(+), 4 deletions(-) create mode 100644 src/parser_json.c diff --git a/include/json.h b/include/json.h index 579bd5dfa98d3..ae3938142aeac 100644 --- a/include/json.h +++ b/include/json.h @@ -1,6 +1,8 @@ #ifndef NFTABLES_JSON_H #define NFTABLES_JSON_H +#include + struct chain; struct cmd; struct expr; @@ -74,6 +76,11 @@ json_t *verdict_stmt_json(const struct stmt *stmt, struct output_ctx *octx); int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd); +int nft_parse_json_buffer(struct nft_ctx *nft, char *buf, size_t buflen, + struct list_head *msgs, struct list_head *cmds); +int nft_parse_json_filename(struct nft_ctx *nft, const char *filename, + struct list_head *msgs, struct list_head *cmds); + #else /* ! HAVE_LIBJANSSON */ typedef void json_t; @@ -156,6 +163,19 @@ static inline int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) return -1; } +static inline int +nft_parse_json_buffer(struct nft_ctx *nft, char *buf, size_t buflen, + struct list_head *msgs, struct list_head *cmds) +{ + return -EINVAL; +} +static inline int +nft_parse_json_filename(struct nft_ctx *nft, const char *filename, + struct list_head *msgs, struct list_head *cmds) +{ + return -EINVAL; +} + #endif /* HAVE_LIBJANSSON */ #endif /* NFTABLES_JSON_H */ diff --git a/src/Makefile.am b/src/Makefile.am index c5c3b0bca1f6b..6db31c813ab97 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -89,7 +89,7 @@ nft_SOURCES += cli.c endif if BUILD_JSON -libnftables_la_SOURCES += json.c +libnftables_la_SOURCES += json.c parser_json.c libnftables_la_LIBADD += ${JANSSON_LIBS} endif diff --git a/src/libnftables.c b/src/libnftables.c index 68e53f70cae8f..d9b2c0810988e 100644 --- a/src/libnftables.c +++ b/src/libnftables.c @@ -452,13 +452,16 @@ int nft_run_cmd_from_buffer(struct nft_ctx *nft, char *buf, size_t buflen) LIST_HEAD(cmds); size_t nlbuflen; char *nlbuf; - int rc; + int rc = -EINVAL; nlbuflen = max(buflen + 1, strlen(buf) + 2); nlbuf = xzalloc(nlbuflen); snprintf(nlbuf, nlbuflen, "%s\n", buf); - rc = nft_parse_bison_buffer(nft, nlbuf, nlbuflen, &msgs, &cmds); + if (nft->output.json) + rc = nft_parse_json_buffer(nft, nlbuf, nlbuflen, &msgs, &cmds); + if (rc == -EINVAL) + rc = nft_parse_bison_buffer(nft, nlbuf, nlbuflen, &msgs, &cmds); if (rc) goto err; @@ -491,7 +494,11 @@ int nft_run_cmd_from_filename(struct nft_ctx *nft, const char *filename) if (!strcmp(filename, "-")) filename = "/dev/stdin"; - rc = nft_parse_bison_filename(nft, filename, &msgs, &cmds); + rc = -EINVAL; + if (nft->output.json) + rc = nft_parse_json_filename(nft, filename, &msgs, &cmds); + if (rc == -EINVAL) + rc = nft_parse_bison_filename(nft, filename, &msgs, &cmds); if (rc) goto err; diff --git a/src/parser_json.c b/src/parser_json.c new file mode 100644 index 0000000000000..88a69493ab7d3 --- /dev/null +++ b/src/parser_json.c @@ -0,0 +1,3141 @@ +#include +#include /* needed by gmputil.h */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CTX_F_RHS (1 << 0) +#define CTX_F_STMT (1 << 1) +#define CTX_F_PRIMARY (1 << 2) +#define CTX_F_DTYPE (1 << 3) +#define CTX_F_SET_RHS (1 << 4) +#define CTX_F_MANGLE (1 << 5) +#define CTX_F_SES (1 << 6) /* set_elem_expr_stmt */ +#define CTX_F_MAP (1 << 7) /* LHS of map_expr */ + +struct json_ctx { + struct input_descriptor indesc; + struct nft_ctx *nft; + struct list_head *msgs; + struct list_head *cmds; + uint32_t flags; +}; + +#define is_RHS(ctx) (ctx->flags & CTX_F_RHS) +#define is_STMT(ctx) (ctx->flags & CTX_F_STMT) +#define is_PRIMARY(ctx) (ctx->flags & CTX_F_PRIMARY) +#define is_DTYPE(ctx) (ctx->flags & CTX_F_DTYPE) +#define is_SET_RHS(ctx) (ctx->flags & CTX_F_SET_RHS) + +static char *ctx_flags_to_string(struct json_ctx *ctx) +{ + static char buf[1024]; + const char *sep = ""; + + buf[0] = '\0'; + + if (is_RHS(ctx)) { + strcat(buf, sep); + strcat(buf, "RHS"); + sep = ", "; + } + if (is_STMT(ctx)) { + strcat(buf, sep); + strcat(buf, "STMT"); + sep = ", "; + } + if (is_PRIMARY(ctx)) { + strcat(buf, sep); + strcat(buf, "PRIMARY"); + sep = ", "; + } + if (is_DTYPE(ctx)) { + strcat(buf, sep); + strcat(buf, "DTYPE"); + sep = ", "; + } + if (is_SET_RHS(ctx)) { + strcat(buf, sep); + strcat(buf, "SET_RHS"); + sep = ", "; + } + return buf; +} + +/* common parser entry points */ + +static struct expr *json_parse_expr(struct json_ctx *ctx, json_t *root); +static struct expr *json_parse_rhs_expr(struct json_ctx *ctx, json_t *root); +static struct expr *json_parse_stmt_expr(struct json_ctx *ctx, json_t *root); +static struct expr *json_parse_primary_expr(struct json_ctx *ctx, json_t *root); +static struct expr *json_parse_set_rhs_expr(struct json_ctx *ctx, json_t *root); +static struct expr *json_parse_set_elem_expr_stmt(struct json_ctx *ctx, json_t *root); +static struct expr *json_parse_map_lhs_expr(struct json_ctx *ctx, json_t *root); +static struct stmt *json_parse_stmt(struct json_ctx *ctx, json_t *root); + +/* parsing helpers */ + +const struct location *int_loc = &internal_location; + +static void json_lib_error(struct json_ctx *ctx, json_error_t *err) +{ + struct location loc = { + .indesc = &ctx->indesc, + .line_offset = err->position - err->column, + .first_line = err->line, + .last_line = err->line, + .first_column = err->column, + /* no information where problematic part ends :( */ + .last_column = err->column, + }; + + erec_queue(error(&loc, err->text), ctx->msgs); +} + +__attribute__((format(printf, 2, 3))) +static void json_error(struct json_ctx *ctx, const char *fmt, ...) +{ + struct error_record *erec; + va_list ap; + + va_start(ap, fmt); + erec = erec_vcreate(EREC_ERROR, int_loc, fmt, ap); + va_end(ap); + erec_queue(erec, ctx->msgs); +} + +static const char *json_typename(const json_t *val) +{ + const char *type_name[] = { + [JSON_OBJECT] = "object", + [JSON_ARRAY] = "array", + [JSON_STRING] = "string", + [JSON_INTEGER] = "integer", + [JSON_REAL] = "real", + [JSON_TRUE] = "true", + [JSON_FALSE] = "false", + [JSON_NULL] = "null" + }; + + return type_name[json_typeof(val)]; +} + +static int json_unpack_err(struct json_ctx *ctx, + json_t *root, const char *fmt, ...) +{ + json_error_t err; + va_list ap; + int rc; + + va_start(ap, fmt); + rc = json_vunpack_ex(root, &err, 0, fmt, ap); + va_end(ap); + + if (rc) + json_lib_error(ctx, &err); + return rc; +} + +static int json_unpack_stmt(struct json_ctx *ctx, json_t *root, + const char **key, json_t **value) +{ + assert(key); + assert(value); + + if (json_object_size(root) != 1) { + json_error(ctx, "Malformed object (too many properties): '%s'.", + json_dumps(root, 0)); + return 1; + } + + json_object_foreach(root, *key, *value) + return 0; + + /* not reached */ + return 1; +} + +static int parse_family(const char *name) +{ + unsigned int i; + struct { + const char *name; + int val; + } family_tbl[] = { + { "ip", NFPROTO_IPV4 }, + { "ip6", NFPROTO_IPV6 }, + { "inet", NFPROTO_INET }, + { "arp", NFPROTO_ARP }, + { "bridge", NFPROTO_BRIDGE }, + { "netdev", NFPROTO_NETDEV } + }; + + for (i = 0; i < array_size(family_tbl); i++) { + if (!strcmp(name, family_tbl[i].name)) + return family_tbl[i].val; + } + return -1; +} + +static bool is_keyword(const char *keyword) +{ + const char *keywords[] = { + "ether", + "ip", + "ip6", + "vlan", + "arp", + "dnat", + "snat", + "ecn", + "reset", + "original", + "reply", + "label", + }; + unsigned int i; + + for (i = 0; i < array_size(keywords); i++) { + if (!strcmp(keyword, keywords[i])) + return true; + } + return false; +} + +static bool is_constant(const char *keyword) +{ + const char *constants[] = { + "tcp", + "udp", + "udplite", + "esp", + "ah", + "icmp", + "icmpv6", + "comp", + "dccp", + "sctp", + "redirect", + }; + unsigned int i; + + for (i = 0; i < array_size(constants); i++) { + if (!strcmp(keyword, constants[i])) + return true; + } + return false; +} + +static struct expr *json_parse_constant(struct json_ctx *ctx, const char *name) +{ + const struct { + const char *name; + uint8_t data; + const struct datatype *dtype; + } constant_tbl[] = { + { "tcp", IPPROTO_TCP, &inet_protocol_type }, + { "udp", IPPROTO_UDP, &inet_protocol_type }, + { "udplite", IPPROTO_UDPLITE, &inet_protocol_type }, + { "esp", IPPROTO_ESP, &inet_protocol_type }, + { "ah", IPPROTO_AH, &inet_protocol_type }, + { "icmp", IPPROTO_ICMP, &inet_protocol_type }, + { "icmpv6", IPPROTO_ICMPV6, &inet_protocol_type }, + { "comp", IPPROTO_COMP, &inet_protocol_type }, + { "dccp", IPPROTO_DCCP, &inet_protocol_type }, + { "sctp", IPPROTO_SCTP, &inet_protocol_type }, + { "redirect", ICMP_REDIRECT, &icmp_type_type }, + }; + unsigned int i; + + for (i = 0; i < array_size(constant_tbl); i++) { + if (strcmp(name, constant_tbl[i].name)) + continue; + return constant_expr_alloc(int_loc, + constant_tbl[i].dtype, + BYTEORDER_HOST_ENDIAN, + 8 * BITS_PER_BYTE, + &constant_tbl[i].data); + } + json_error(ctx, "Unknown constant '%s'.", name); + return NULL; +} + +/* this is a combination of symbol_expr, integer_expr, boolean_expr ... */ +static struct expr *json_parse_immediate_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + enum symbol_types symtype = SYMBOL_VALUE; + const char *str; + char buf[64] = {}; + struct expr; + + switch (json_typeof(root)) { + case JSON_STRING: + str = json_string_value(root); + if (str[0] == '@') { + symtype = SYMBOL_SET; + str++; + } + if (is_RHS(ctx) && is_keyword(str)) + return symbol_expr_alloc(int_loc, + SYMBOL_VALUE, NULL, str); + if (is_RHS(ctx) && is_constant(str)) + return json_parse_constant(ctx, str); + break; + case JSON_INTEGER: + snprintf(buf, sizeof(buf), + "%" JSON_INTEGER_FORMAT, json_integer_value(root)); + str = buf; + break; + case JSON_TRUE: + case JSON_FALSE: + if (is_RHS(ctx)) { + buf[0] = json_is_true(root); + return constant_expr_alloc(int_loc, &boolean_type, + BYTEORDER_HOST_ENDIAN, + 1, buf); + } + /* fall through */ + default: + json_error(ctx, "Invalid immediate value type '%d'.", + json_typeof(root)); + return NULL; + } + + return symbol_expr_alloc(int_loc, symtype, NULL, str); +} + +static struct expr *json_parse_meta_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + struct error_record *erec; + unsigned int key; + const char *name; + + if (json_unpack_err(ctx, root, "s", &name)) + return NULL; + erec = meta_key_parse(int_loc, name, &key); + if (erec) { + erec_queue(erec, ctx->msgs); + return NULL; + } + return meta_expr_alloc(int_loc, key); +} + +static int json_parse_payload_field(const struct proto_desc *desc, + const char *name, int *field) +{ + unsigned int i; + + for (i = 0; i < PROTO_HDRS_MAX; i++) { + if (desc->templates[i].token && + !strcmp(desc->templates[i].token, name)) { + if (field) + *field = i; + return 0; + } + } + return 1; +} + +static int json_parse_tcp_option_type(const char *name, int *val) +{ + unsigned int i; + + for (i = 0; i < array_size(tcpopthdr_protocols); i++) { + if (tcpopthdr_protocols[i] && + !strcmp(tcpopthdr_protocols[i]->name, name)) { + if (val) + *val = i; + return 0; + } + } + /* special case for sack0 - sack3 */ + if (sscanf(name, "sack%u", &i) == 1 && i < 4) { + if (val) + *val = TCPOPTHDR_SACK0 + i; + return 0; + } + return 1; +} + +static int json_parse_tcp_option_field(int type, const char *name, int *val) +{ + unsigned int i; + const struct exthdr_desc *desc = tcpopthdr_protocols[type]; + + for (i = 0; i < array_size(desc->templates); i++) { + if (desc->templates[i].token && + !strcmp(desc->templates[i].token, name)) { + if (val) + *val = i; + return 0; + } + } + return 1; +} + +static const struct proto_desc *proto_lookup_byname(const char *name) +{ + const struct proto_desc *proto_tbl[] = { + &proto_eth, + &proto_vlan, + &proto_arp, + &proto_ip, + &proto_icmp, + &proto_ip6, + &proto_icmp6, + &proto_ah, + &proto_esp, + &proto_comp, + &proto_udp, + &proto_udplite, + &proto_tcp, + &proto_dccp, + &proto_sctp + }; + unsigned int i; + + for (i = 0; i < array_size(proto_tbl); i++) { + if (!strcmp(proto_tbl[i]->name, name)) + return proto_tbl[i]; + } + return NULL; +} + +static struct expr *json_parse_payload_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + const char *name; + const char *field; + int val; + const struct proto_desc *proto; + + if (json_unpack_err(ctx, root, "{s:s}", "name", &name)) + return NULL; + + /* special treatment for raw */ + + if (!strcmp(name, "raw")) { + int offset, len, baseval; + struct expr *expr; + const char *base; + + if (json_unpack_err(ctx, root, "{s:s, s:i, s:i}", + "base", &base, + "offset", &offset, + "len", &len)) + return NULL; + if (!strcmp(base, "ll")) { + baseval = PROTO_BASE_LL_HDR; + } else if (!strcmp(base, "nh")) { + baseval = PROTO_BASE_NETWORK_HDR; + } else if (!strcmp(base, "th")) { + baseval = PROTO_BASE_TRANSPORT_HDR; + } else { + json_error(ctx, "Invalid payload base '%s'.", base); + return NULL; + } + expr = payload_expr_alloc(int_loc, NULL, 0); + payload_init_raw(expr, baseval, offset, len); + expr->byteorder = BYTEORDER_BIG_ENDIAN; + expr->payload.is_raw = true; + + return expr; + } + + proto = proto_lookup_byname(name); + if (!proto) { + json_error(ctx, "Unknown payload expr name '%s'.", name); + return NULL; + } + if (json_unpack_err(ctx, root, "{s:s}", "field", &field)) + return NULL; + if (json_parse_payload_field(proto, field, &val)) { + json_error(ctx, "Unknown %s field '%s'.", name, field); + return NULL; + } + return payload_expr_alloc(int_loc, proto, val); +} + +static struct expr *json_parse_tcp_option_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + const char *desc, *field = NULL; + int descval, fieldval; + struct expr *expr; + + if (json_unpack_err(ctx, root, "{s:s}", "name", &desc)) + return NULL; + json_unpack(root, "{s:s}", "field", &field); + + if (json_parse_tcp_option_type(desc, &descval)) { + json_error(ctx, "Unknown tcp option name '%s'.", desc); + return NULL; + } + + if (!field) { + expr = tcpopt_expr_alloc(int_loc, descval, + TCPOPTHDR_FIELD_KIND); + expr->exthdr.flags = NFT_EXTHDR_F_PRESENT; + + return expr; + } + if (json_parse_tcp_option_field(descval, field, &fieldval)) { + json_error(ctx, "Unknown tcp option field '%s'.", field); + return NULL; + } + return tcpopt_expr_alloc(int_loc, descval, fieldval); +} + +static const struct exthdr_desc *exthdr_lookup_byname(const char *name) +{ + const struct exthdr_desc *exthdr_tbl[] = { + &exthdr_hbh, + &exthdr_rt, + &exthdr_rt0, + &exthdr_rt2, + &exthdr_rt4, + &exthdr_frag, + &exthdr_dst, + &exthdr_mh, + }; + unsigned int i; + + for (i = 0; i < array_size(exthdr_tbl); i++) { + if (!strcmp(exthdr_tbl[i]->name, name)) + return exthdr_tbl[i]; + } + return NULL; +} + +static int json_parse_exthdr_field(const struct exthdr_desc *desc, + const char *name, int *field) +{ + unsigned int i; + + for (i = 0; i < array_size(desc->templates); i++) { + if (desc->templates[i].token && + !strcmp(desc->templates[i].token, name)) { + if (field) + *field = i; + return 0; + } + } + return 1; +} + +static struct expr *json_parse_exthdr_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + const char *name, *field = NULL; + struct expr *expr; + int offset = 0, fieldval; + const struct exthdr_desc *desc; + + if (json_unpack_err(ctx, root, "{s:s}", "name", &name)) + return NULL; + + desc = exthdr_lookup_byname(name); + if (!desc) { + json_error(ctx, "Invalid exthdr protocol '%s'.", name); + return NULL; + } + + if (json_unpack(root, "{s:s}", "field", &field)) { + expr = exthdr_expr_alloc(int_loc, desc, 1); + expr->exthdr.flags = NFT_EXTHDR_F_PRESENT; + return expr; + } + + if (json_parse_exthdr_field(desc, field, &fieldval)) { + json_error(ctx, "Unknown %s field %s.", desc->name, field); + return NULL; + } + + /* special treatment for rt0 */ + if (desc == &exthdr_rt0 && + json_unpack_err(ctx, root, "{s:i}", "offset", &offset)) + return NULL; + + return exthdr_expr_alloc(int_loc, desc, fieldval + offset); +} + +static struct expr *json_parse_rt_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + const struct { + const char *name; + int val; + } rt_key_tbl[] = { + { "classid", NFT_RT_CLASSID }, + { "nexthop", NFT_RT_NEXTHOP4 }, + { "mtu", NFT_RT_TCPMSS }, + }; + unsigned int i, familyval = NFPROTO_UNSPEC; + const char *key, *family = NULL; + + if (json_unpack_err(ctx, root, "{s:s}", "key", &key)) + return NULL; + if (!json_unpack(root, "{s:s}", "family", &family)) { + familyval = parse_family(family); + if (familyval != NFPROTO_IPV4 && + familyval != NFPROTO_IPV6) { + json_error(ctx, "Invalid RT family '%s'.", family); + return NULL; + } + } + + for (i = 0; i < array_size(rt_key_tbl); i++) { + int val = rt_key_tbl[i].val; + bool invalid = true; + + if (strcmp(key, rt_key_tbl[i].name)) + continue; + + if (familyval) { + if (familyval == NFPROTO_IPV6 && + val == NFT_RT_NEXTHOP4) + val = NFT_RT_NEXTHOP6; + invalid = false; + } + return rt_expr_alloc(int_loc, val, invalid); + } + json_error(ctx, "Unknown rt key '%s'.", key); + return NULL; +} + +static bool ct_key_is_dir(enum nft_ct_keys key) +{ + const enum nft_ct_keys ct_dir_keys[] = { + NFT_CT_L3PROTOCOL, + NFT_CT_SRC, + NFT_CT_DST, + NFT_CT_PROTOCOL, + NFT_CT_PROTO_SRC, + NFT_CT_PROTO_DST, + NFT_CT_PKTS, + NFT_CT_BYTES, + NFT_CT_AVGPKT, + NFT_CT_ZONE, + }; + unsigned int i; + + for (i = 0; i < array_size(ct_dir_keys); i++) { + if (key == ct_dir_keys[i]) + return true; + } + return false; +} + +static struct expr *json_parse_ct_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + const char *key, *dir, *family; + unsigned int i; + int dirval = -1, familyval = NFPROTO_UNSPEC, keyval = -1; + + if (json_unpack_err(ctx, root, "{s:s}", "key", &key)) + return NULL; + + for (i = 0; i < array_size(ct_templates); i++) { + if (ct_templates[i].token && + !strcmp(key, ct_templates[i].token)) { + keyval = i; + break; + } + } + if (keyval == -1) { + json_error(ctx, "Unknown ct key '%s'.", key); + return NULL; + } + + if (!json_unpack(root, "{s:s}", "family", &family)) { + familyval = parse_family(family); + if (familyval != NFPROTO_IPV4 && + familyval != NFPROTO_IPV6) { + json_error(ctx, "Invalid CT family '%s'.", family); + return NULL; + } + } + + if (!json_unpack(root, "{s:s}", "dir", &dir)) { + if (!strcmp(dir, "original")) { + dirval = IP_CT_DIR_ORIGINAL; + } else if (!strcmp(dir, "reply")) { + dirval = IP_CT_DIR_REPLY; + } else { + json_error(ctx, "Invalid ct direction '%s'.", dir); + return NULL; + } + + if (!ct_key_is_dir(keyval)) { + json_error(ctx, "Direction not supported by CT key '%s'.", key); + return NULL; + } + } + + return ct_expr_alloc(int_loc, keyval, dirval, familyval); +} + +static struct expr *json_parse_numgen_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + int modeval, mod, offset = 0; + const char *mode; + + if (json_unpack_err(ctx, root, "{s:s, s:i}", + "mode", &mode, "mod", &mod)) + return NULL; + json_unpack(root, "{s:i}", "offset", &offset); + + if (!strcmp(mode, "inc")) { + modeval = NFT_NG_INCREMENTAL; + } else if (!strcmp(mode, "random")) { + modeval = NFT_NG_RANDOM; + } else { + json_error(ctx, "Unknown numgen mode '%s'.", mode); + return NULL; + } + + return numgen_expr_alloc(int_loc, modeval, mod, offset); +} + +static struct expr *json_parse_hash_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + int mod, offset = 0, seed = 0; + struct expr *expr, *hash_expr; + bool have_seed; + json_t *jexpr; + + + if (json_unpack_err(ctx, root, "{s:i}", "mod", &mod)) + return NULL; + json_unpack(root, "{s:i}", "offset", &offset); + + if (!strcmp(type, "symhash")) { + return hash_expr_alloc(int_loc, mod, false, 0, + offset, NFT_HASH_SYM); + } else if (strcmp(type, "jhash")) { + json_error(ctx, "Unknown hash type '%s'.", type); + return NULL; + } + + if (json_unpack_err(ctx, root, "{s:o}", "expr", &jexpr)) + return NULL; + expr = json_parse_expr(ctx, jexpr); + if (!expr) { + json_error(ctx, "Invalid jhash expression."); + return NULL; + } + have_seed = !json_unpack(root, "{s:i}", "seed", &seed); + + hash_expr = hash_expr_alloc(int_loc, mod, have_seed, + seed, offset, NFT_HASH_JENKINS); + hash_expr->hash.expr = expr; + return hash_expr; +} + +static int fib_flag_parse(const char *name, int *flags) +{ + const char *fib_flags[] = { + "saddr", + "daddr", + "mark", + "iif", + "oif", + }; + unsigned int i; + + for (i = 0; i < array_size(fib_flags); i++) { + if (!strcmp(name, fib_flags[i])) { + *flags |= (1 << i); + return 0; + } + } + return 1; +} + +static struct expr *json_parse_fib_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + const char *fib_result_tbl[] = { + [NFT_FIB_RESULT_UNSPEC] = NULL, + [NFT_FIB_RESULT_OIF] = "oif", + [NFT_FIB_RESULT_OIFNAME] = "oifname", + [NFT_FIB_RESULT_ADDRTYPE] = "type", + }; + enum nft_fib_result resultval = NFT_FIB_RESULT_UNSPEC; + json_t *flags, *value; + const char *result; + unsigned int i; + size_t index; + int flagval = 0; + + if (json_unpack_err(ctx, root, "{s:s}", "result", &result)) + return NULL; + + for (i = 1; i < array_size(fib_result_tbl); i++) { + if (!strcmp(result, fib_result_tbl[i])) { + resultval = i; + break; + } + } + if (resultval == NFT_FIB_RESULT_UNSPEC) { + json_error(ctx, "Invalid fib result '%s'.", result); + return NULL; + } + + if (!json_unpack(root, "{s:o}", "flags", &flags)) { + const char *flag; + + if (json_is_string(flags)) { + flag = json_string_value(flags); + + if (fib_flag_parse(flag, &flagval)) { + json_error(ctx, "Invalid fib flag '%s'.", flag); + return NULL; + } + } else if (!json_is_array(flags)) { + json_error(ctx, "Unexpected object type in fib tuple."); + return NULL; + } + + json_array_foreach(flags, index, value) { + if (!json_is_string(value)) { + json_error(ctx, "Unexpected object type in fib flags array at index %zd.", index); + return NULL; + } + flag = json_string_value(value); + + if (fib_flag_parse(flag, &flagval)) { + json_error(ctx, "Invalid fib flag '%s'.", flag); + return NULL; + } + } + } + + /* sanity checks from fib_expr in parser_bison.y */ + + if ((flagval & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) == 0) { + json_error(ctx, "fib: need either saddr or daddr"); + return NULL; + } + + if ((flagval & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) == + (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) { + json_error(ctx, "fib: saddr and daddr are mutually exclusive"); + return NULL; + } + + if ((flagval & (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) == + (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) { + json_error(ctx, "fib: iif and oif are mutually exclusive"); + return NULL; + } + + return fib_expr_alloc(int_loc, flagval, resultval); +} + +static struct expr *json_parse_binop_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + const struct { + const char *type; + enum ops op; + } op_tbl[] = { + { "|", OP_OR }, + { "^", OP_XOR }, + { "&", OP_AND }, + { ">>", OP_RSHIFT }, + { "<<", OP_LSHIFT }, + }; + enum ops thisop = OP_INVALID; + struct expr *left, *right; + json_t *jleft, *jright; + unsigned int i; + + for (i = 0; i < array_size(op_tbl); i++) { + if (strcmp(type, op_tbl[i].type)) + continue; + + thisop = op_tbl[i].op; + break; + } + if (thisop == OP_INVALID) { + json_error(ctx, "Invalid binop type '%s'.", type); + return NULL; + } + + if (json_unpack_err(ctx, root, "[o, o!]", &jleft, &jright)) + return NULL; + + left = json_parse_primary_expr(ctx, jleft); + if (!left) { + json_error(ctx, "Failed to parse LHS of binop expression."); + return NULL; + } + right = json_parse_primary_expr(ctx, jright); + if (!right) { + json_error(ctx, "Failed to parse RHS of binop expression."); + expr_free(left); + return NULL; + } + return binop_expr_alloc(int_loc, thisop, left, right); +} + +static struct expr *json_parse_concat_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + struct expr *expr = NULL, *tmp; + json_t *value; + size_t index; + + if (json_is_object(root)) + return json_parse_primary_expr(ctx, root); + else if (!json_is_array(root)) { + json_error(ctx, "Unexpected concat object type %s.", + json_typename(root)); + return NULL; + } + + json_array_foreach(root, index, value) { + tmp = json_parse_primary_expr(ctx, value); + if (!tmp) { + json_error(ctx, "Parsing expr at index %zd failed.", index); + expr_free(expr); + return NULL; + } + if (!expr) { + expr = tmp; + continue; + } + if (expr->ops->type != EXPR_CONCAT) { + struct expr *concat; + + concat = concat_expr_alloc(int_loc); + compound_expr_add(concat, expr); + expr = concat; + } + compound_expr_add(expr, tmp); + } + return expr; +} + +static struct expr *json_parse_prefix_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + struct expr *expr; + json_t *addr; + int len; + + if (json_unpack_err(ctx, root, "{s:o, s:i}", + "addr", &addr, "len", &len)) + return NULL; + + expr = json_parse_primary_expr(ctx, addr); + if (!expr) { + json_error(ctx, "Invalid prefix in prefix expr."); + return NULL; + } + return prefix_expr_alloc(int_loc, expr, len); +} + +static struct expr *json_parse_range_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + struct expr *expr_low, *expr_high; + json_t *low, *high; + + if (json_unpack_err(ctx, root, "[o, o!]", &low, &high)) + return NULL; + + expr_low = json_parse_primary_expr(ctx, low); + if (!expr_low) { + json_error(ctx, "Invalid low value in range expression."); + return NULL; + } + expr_high = json_parse_primary_expr(ctx, high); + if (!expr_high) { + json_error(ctx, "Invalid high value in range expression."); + return NULL; + } + return range_expr_alloc(int_loc, expr_low, expr_high); +} + +static struct expr *json_parse_wildcard_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + struct expr *expr; + + expr = constant_expr_alloc(int_loc, &integer_type, + BYTEORDER_HOST_ENDIAN, 0, NULL); + return prefix_expr_alloc(int_loc, expr, 0); +} + +static struct expr *json_parse_verdict_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + const struct { + int verdict; + const char *name; + bool chain; + } verdict_tbl[] = { + { NFT_CONTINUE, "continue", false }, + { NFT_BREAK, "break", false }, + { NFT_JUMP, "jump", true }, + { NFT_GOTO, "goto", true }, + { NFT_RETURN, "return", false }, + { NF_ACCEPT, "accept", false }, + { NF_DROP, "drop", false }, + { NF_QUEUE, "queue", false }, + }; + const char *chain = NULL; + unsigned int i; + + json_unpack(root, "s", &chain); + + for (i = 0; i < array_size(verdict_tbl); i++) { + if (strcmp(type, verdict_tbl[i].name)) + continue; + + if (verdict_tbl[i].chain && !chain) { + json_error(ctx, "Verdict %s needs chain argument.", type); + return NULL; + } + return verdict_expr_alloc(int_loc, + verdict_tbl[i].verdict, chain); + } + json_error(ctx, "Unknown verdict '%s'.", type); + return NULL; +} + +static struct expr *json_parse_set_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + struct expr *expr, *set_expr = NULL; + json_t *value; + size_t index; + + switch (json_typeof(root)) { + case JSON_OBJECT: + case JSON_ARRAY: + break; + default: + expr = json_parse_immediate_expr(ctx, type, root); + if (expr->ops->type == EXPR_SYMBOL && + expr->symtype == SYMBOL_SET) + return expr; + + expr = set_elem_expr_alloc(int_loc, expr); + set_expr = set_expr_alloc(int_loc, NULL); + compound_expr_add(set_expr, expr); + return set_expr; + } + + json_array_foreach(root, index, value) { + struct expr *expr; + json_t *jleft, *jright; + + if (!json_unpack(value, "[o, o!]", &jleft, &jright)) { + struct expr *expr2; + + expr = json_parse_rhs_expr(ctx, jleft); + if (!expr) { + json_error(ctx, "Invalid set elem at index %zu.", index); + expr_free(set_expr); + return NULL; + } + if (expr->ops->type != EXPR_SET_ELEM) + expr = set_elem_expr_alloc(int_loc, expr); + + expr2 = json_parse_set_rhs_expr(ctx, jright); + if (!expr2) { + json_error(ctx, "Invalid set elem at index %zu.", index); + expr_free(expr); + expr_free(set_expr); + return NULL; + } + expr2 = mapping_expr_alloc(int_loc, expr, expr2); + expr = expr2; + } else if (json_is_object(value)) { + expr = json_parse_rhs_expr(ctx, value); + + if (!expr) { + json_error(ctx, "Invalid set elem at index %zu.", index); + expr_free(set_expr); + return NULL; + } + + if (expr->ops->type != EXPR_SET_ELEM) + expr = set_elem_expr_alloc(int_loc, expr); + } else { + expr = json_parse_immediate_expr(ctx, "elem", value); + expr = set_elem_expr_alloc(int_loc, expr); + } + + if (!set_expr) + set_expr = set_expr_alloc(int_loc, NULL); + compound_expr_add(set_expr, expr); + } + return set_expr; +} + +static struct expr *json_parse_map_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + json_t *jleft, *jright; + struct expr *left, *right; + + if (json_unpack_err(ctx, root, "{s:o, s:o}", + "left", &jleft, "right", &jright)) + return NULL; + + left = json_parse_map_lhs_expr(ctx, jleft); + if (!left) { + json_error(ctx, "Illegal LHS of map expression."); + return NULL; + } + + right = json_parse_rhs_expr(ctx, jright); + if (!right) { + json_error(ctx, "Illegal RHS of map expression."); + expr_free(left); + return NULL; + } + + return map_expr_alloc(int_loc, left, right); +} + +static struct expr *json_parse_set_elem_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + struct expr *expr; + json_t *tmp; + int i; + + if (json_unpack_err(ctx, root, "{s:o}", "val", &tmp)) + return NULL; + + expr = json_parse_expr(ctx, tmp); + if (!expr) + return NULL; + + expr = set_elem_expr_alloc(int_loc, expr); + + if (!json_unpack(root, "{s:i}", "elem_timeout", &i)) + expr->timeout = i * 1000; + if (!json_unpack(root, "{s:i}", "elem_expires", &i)) + expr->expiration = i * 1000; + if (!json_unpack(root, "{s:s}", "elem_comment", &expr->comment)) + expr->comment = xstrdup(expr->comment); + + return expr; +} + +static struct expr *json_parse_expr(struct json_ctx *ctx, json_t *root) +{ + const struct { + const char *name; + struct expr *(*cb)(struct json_ctx *, const char *, json_t *); + uint32_t flags; + } cb_tbl[] = { + { "concat", json_parse_concat_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_DTYPE | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { "set", json_parse_set_expr, CTX_F_RHS | CTX_F_STMT }, /* allow this as stmt expr because that allows set references */ + { "map", json_parse_map_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS }, + /* below three are multiton_rhs_expr */ + { "prefix", json_parse_prefix_expr, CTX_F_RHS | CTX_F_STMT }, + { "range", json_parse_range_expr, CTX_F_RHS | CTX_F_STMT }, + { "*", json_parse_wildcard_expr, CTX_F_RHS | CTX_F_STMT }, + { "immediate", json_parse_immediate_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, /* symbol, boolean or integer expr */ + { "payload", json_parse_payload_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP }, + { "exthdr", json_parse_exthdr_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { "tcp option", json_parse_tcp_option_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES }, + { "meta", json_parse_meta_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP }, + { "rt", json_parse_rt_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { "ct", json_parse_ct_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP }, + { "numgen", json_parse_numgen_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + /* below two are hash expr */ + { "jhash", json_parse_hash_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { "symhash", json_parse_hash_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { "fib", json_parse_fib_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { "|", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { "^", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { "&", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { ">>", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { "<<", json_parse_binop_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, + { "accept", json_parse_verdict_expr, CTX_F_RHS | CTX_F_SET_RHS }, + { "drop", json_parse_verdict_expr, CTX_F_RHS | CTX_F_SET_RHS }, + { "continue", json_parse_verdict_expr, CTX_F_RHS | CTX_F_SET_RHS }, + { "jump", json_parse_verdict_expr, CTX_F_RHS | CTX_F_SET_RHS }, + { "goto", json_parse_verdict_expr, CTX_F_RHS | CTX_F_SET_RHS }, + { "return", json_parse_verdict_expr, CTX_F_RHS | CTX_F_SET_RHS }, + { "elem", json_parse_set_elem_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY }, + }; + const char *type; + unsigned int i; + json_t *value; + + if (json_is_array(root)) { + struct expr *list; + size_t index; + + if (!(ctx->flags & (CTX_F_RHS | CTX_F_STMT))) { + json_error(ctx, "List expression only allowed on RHS or in statement expression."); + return NULL; + } + + if (is_PRIMARY(ctx)) { + json_error(ctx, "List expression not allowed as primary expression."); + return NULL; + } + + list = list_expr_alloc(int_loc); + json_array_foreach(root, index, value) { + struct expr *expr = json_parse_expr(ctx, value); + if (!expr) { + json_error(ctx, "Parsing list expression item at index %zu failed.", index); + expr_free(list); + return NULL; + } + compound_expr_add(list, expr); + } + return list; + } else if (json_is_string(root)) { + const struct datatype *dtype; + + if (is_DTYPE(ctx)) { + dtype = datatype_lookup_byname(json_string_value(root)); + if (!dtype) { + json_error(ctx, "Unknown datatype '%s'.", json_string_value(root)); + return NULL; + } + return constant_expr_alloc(int_loc, dtype, + dtype->byteorder, dtype->size, NULL); + } else { + return json_parse_immediate_expr(ctx, "immediate", root); + } + } else if ((is_RHS(ctx) || is_STMT(ctx) || is_PRIMARY(ctx)) && (json_is_integer(root) || json_is_boolean(root))) { + /* is_STMT for mangle statement */ + return json_parse_immediate_expr(ctx, "immediate", root); + } + + if (json_unpack_stmt(ctx, root, &type, &value)) + return NULL; + + for (i = 0; i < array_size(cb_tbl); i++) { + if (strcmp(type, cb_tbl[i].name)) + continue; + + if ((cb_tbl[i].flags & ctx->flags) != ctx->flags) { + json_error(ctx, "Expression type %s not allowed in context (%s).", + type, ctx_flags_to_string(ctx)); + return NULL; + } + + return cb_tbl[i].cb(ctx, type, value); + } + json_error(ctx, "Unknown expression type '%s'.", type); + return NULL; +} + +static struct expr *json_parse_flagged_expr(struct json_ctx *ctx, + uint32_t flags, json_t *root) +{ + uint32_t old_flags = ctx->flags; + struct expr *expr; + + ctx->flags |= flags; + expr = json_parse_expr(ctx, root); + ctx->flags = old_flags; + + return expr; +} + +static struct expr *json_parse_rhs_expr(struct json_ctx *ctx, json_t *root) +{ + return json_parse_flagged_expr(ctx, CTX_F_RHS, root); +} + +static struct expr *json_parse_stmt_expr(struct json_ctx *ctx, json_t *root) +{ + return json_parse_flagged_expr(ctx, CTX_F_STMT, root); +} + +static struct expr *json_parse_primary_expr(struct json_ctx *ctx, json_t *root) +{ + return json_parse_flagged_expr(ctx, CTX_F_PRIMARY, root); +} + +static struct expr *json_parse_set_rhs_expr(struct json_ctx *ctx, json_t *root) +{ + return json_parse_flagged_expr(ctx, CTX_F_SET_RHS, root); +} + +static struct expr *json_parse_mangle_lhs_expr(struct json_ctx *ctx, json_t *root) +{ + return json_parse_flagged_expr(ctx, CTX_F_MANGLE, root); +} + +static struct expr *json_parse_set_elem_expr_stmt(struct json_ctx *ctx, json_t *root) +{ + struct expr *expr = json_parse_flagged_expr(ctx, CTX_F_SES, root); + + if (expr->ops->type != EXPR_SET_ELEM) + expr = set_elem_expr_alloc(int_loc, expr); + + return expr; +} + +static struct expr *json_parse_map_lhs_expr(struct json_ctx *ctx, json_t *root) +{ + return json_parse_flagged_expr(ctx, CTX_F_MAP, root); +} + +static struct expr *json_parse_dtype_expr(struct json_ctx *ctx, json_t *root) +{ + if (json_is_string(root)) { + const struct datatype *dtype; + + dtype = datatype_lookup_byname(json_string_value(root)); + if (!dtype) { + json_error(ctx, "Invalid datatype '%s'.", + json_string_value(root)); + return NULL; + } + return constant_expr_alloc(int_loc, dtype, + dtype->byteorder, dtype->size, NULL); + } else if (json_is_array(root)) { + json_t *value; + size_t index; + struct expr *expr = concat_expr_alloc(int_loc); + + json_array_foreach(root, index, value) { + struct expr *i = json_parse_dtype_expr(ctx, value); + + if (!i) { + json_error(ctx, "Invalid datatype at index %zu.", index); + expr_free(expr); + return NULL; + } + compound_expr_add(expr, i); + } + return expr; + } + json_error(ctx, "Invalid set datatype."); + return NULL; +} + +static struct stmt *json_parse_match_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct expr *left, *right, *rel_expr; + json_t *jleft, *jright; + const char *opstr = NULL; + enum ops op; + + if (json_unpack_err(ctx, value, "{s:o, s:o}", + "left", &jleft, + "right", &jright)) + return NULL; + + json_unpack(value, "{s:s}", "op", &opstr); + if (opstr) { + for (op = OP_INVALID; op < __OP_MAX; op++) { + if (expr_op_symbols[op] && + !strcmp(opstr, expr_op_symbols[op])) + break; + } + if (op == __OP_MAX) { + json_error(ctx, "Unknown relational op '%s'.", opstr); + return NULL; + } + } else { + op = OP_IMPLICIT; + } + + left = json_parse_expr(ctx, jleft); + if (!left) { + json_error(ctx, "Invalid LHS of relational."); + return NULL; + } + right = json_parse_rhs_expr(ctx, jright); + if (!right) { + expr_free(left); + json_error(ctx, "Invalid RHS of relational."); + return NULL; + } + + rel_expr = relational_expr_alloc(int_loc, op, left, right); + return expr_stmt_alloc(int_loc, rel_expr); +} + +static struct stmt *json_parse_counter_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + int packets, bytes; + struct stmt *stmt; + + if (json_is_null(value)) + return counter_stmt_alloc(int_loc); + + if (!json_unpack(value, "{s:i, s:i}", + "packets", &packets, + "bytes", &bytes)) { + stmt = counter_stmt_alloc(int_loc); + stmt->counter.packets = packets; + stmt->counter.bytes = bytes; + return stmt; + } + + stmt = objref_stmt_alloc(int_loc); + stmt->objref.type = NFT_OBJECT_COUNTER; + stmt->objref.expr = json_parse_stmt_expr(ctx, value); + if (!stmt->objref.expr) { + json_error(ctx, "Invalid counter reference."); + stmt_free(stmt); + return NULL; + } + return stmt; +} + +static struct stmt *json_parse_verdict_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct { + const char *name; + int val; + } verdict_type_tbl[] = { + { "accept", NF_ACCEPT }, + { "drop", NF_DROP }, + { "continue", NFT_CONTINUE }, + { "jump", NFT_JUMP }, + { "goto", NFT_GOTO }, + { "return", NFT_RETURN }, + }; + const char *identifier = NULL; + struct expr *expr; + unsigned int i; + int type = 255; /* NFT_* are negative, NF_* are max 5 (NF_STOP) */ + + for (i = 0; i < array_size(verdict_type_tbl); i++) { + if (!strcmp(verdict_type_tbl[i].name, key)) { + type = verdict_type_tbl[i].val; + break; + } + } + switch(type) { + case NFT_JUMP: + case NFT_GOTO: + if (!json_is_string(value)) { + json_error(ctx, "Verdict '%s' requires destination.", key); + return NULL; + } + identifier = xstrdup(json_string_value(value)); + /* fall through */ + case NF_ACCEPT: + case NF_DROP: + case NFT_CONTINUE: + case NFT_RETURN: + expr = verdict_expr_alloc(int_loc, type, identifier); + return verdict_stmt_alloc(int_loc, expr); + } + json_error(ctx, "Unknown verdict '%s'.", key); + return NULL; +} + +static struct stmt *json_parse_mangle_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + json_t *jleft, *jright; + struct expr *left, *right; + struct stmt *stmt; + + if (json_unpack_err(ctx, value, "{s:o, s:o}", + "left", &jleft, "right", &jright)) + return NULL; + + left = json_parse_mangle_lhs_expr(ctx, jleft); + if (!left) { + json_error(ctx, "Invalid LHS of mangle statement"); + return NULL; + } + right = json_parse_stmt_expr(ctx, jright); + if (!right) { + json_error(ctx, "Invalid RHS of mangle statement"); + expr_free(left); + return NULL; + } + + switch (left->ops->type) { + case EXPR_EXTHDR: + return exthdr_stmt_alloc(int_loc, left, right); + case EXPR_PAYLOAD: + return payload_stmt_alloc(int_loc, left, right); + case EXPR_META: + stmt = meta_stmt_alloc(int_loc, left->meta.key, right); + expr_free(left); + return stmt; + case EXPR_CT: + if (left->ct.key == NFT_CT_HELPER) { + stmt = objref_stmt_alloc(int_loc); + stmt->objref.type = NFT_OBJECT_CT_HELPER; + stmt->objref.expr = right; + } else { + stmt = ct_stmt_alloc(int_loc, left->ct.key, + left->ct.direction, right); + } + expr_free(left); + return stmt; + default: + json_error(ctx, "Invalid LHS expression type for mangle statement."); + return NULL; + } +} + +static uint64_t rate_to_bytes(int val, const char *unit) +{ + uint64_t bytes = val; + + if (!strcmp(unit, "kbytes")) + return bytes * 1024; + if (!strcmp(unit, "mbytes")) + return bytes * 1024 * 1024; + return bytes; +} + +static struct stmt *json_parse_quota_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct stmt *stmt; + int inv = 0; + const char *val_unit = "bytes", *used_unit = "bytes"; + int val, used = 0; + + if (!json_unpack(value, "{s:i}", "val", &val)) { + json_unpack(value, "{s:b}", "inv", &inv); + json_unpack(value, "{s:s}", "val_unit", &val_unit); + json_unpack(value, "{s:i}", "used", &used); + json_unpack(value, "{s:s}", "used_unit", &used_unit); + stmt = quota_stmt_alloc(int_loc); + stmt->quota.bytes = rate_to_bytes(val, val_unit); + if (used) + stmt->quota.used = rate_to_bytes(used, used_unit); + stmt->quota.flags = (inv ? NFT_QUOTA_F_INV : 0); + return stmt; + } + stmt = objref_stmt_alloc(int_loc); + stmt->objref.type = NFT_OBJECT_QUOTA; + stmt->objref.expr = json_parse_stmt_expr(ctx, value); + if (!stmt->objref.expr) { + json_error(ctx, "Invalid quota reference."); + stmt_free(stmt); + return NULL; + } + return stmt; +} + +static uint64_t seconds_from_unit(const char *unit) +{ + if (!strcmp(unit, "week")) + return 60 * 60 * 24 * 7; + if (!strcmp(unit, "day")) + return 60 * 60 * 24; + if (!strcmp(unit, "hour")) + return 60 * 60; + if (!strcmp(unit, "minute")) + return 60; + return 1; +} + +static struct stmt *json_parse_limit_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct stmt *stmt; + int rate, burst = 0; + const char *rate_unit = "packets", *time, *burst_unit = "bytes"; + int inv = 0; + + if (!json_unpack(value, "{s:i, s:s}", + "rate", &rate, "per", &time)) { + json_unpack(value, "{s:s}", "rate_unit", &rate_unit); + json_unpack(value, "{s:b}", "inv", &inv); + json_unpack(value, "{s:i}", "burst", &burst); + json_unpack(value, "{s:s}", "burst_unit", &burst_unit); + + stmt = limit_stmt_alloc(int_loc); + + if (!strcmp(rate_unit, "packets")) { + stmt->limit.type = NFT_LIMIT_PKTS; + stmt->limit.rate = rate; + stmt->limit.burst = burst; + } else { + stmt->limit.type = NFT_LIMIT_PKT_BYTES; + stmt->limit.rate = rate_to_bytes(rate, rate_unit); + stmt->limit.burst = rate_to_bytes(burst, burst_unit); + } + stmt->limit.unit = seconds_from_unit(time); + stmt->limit.flags = inv ? NFT_LIMIT_F_INV : 0; + return stmt; + } + + stmt = objref_stmt_alloc(int_loc); + stmt->objref.type = NFT_OBJECT_LIMIT; + stmt->objref.expr = json_parse_stmt_expr(ctx, value); + if (!stmt->objref.expr) { + json_error(ctx, "Invalid limit reference."); + stmt_free(stmt); + return NULL; + } + return stmt; +} + +static struct stmt *json_parse_fwd_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct stmt *stmt = fwd_stmt_alloc(int_loc); + + stmt->fwd.to = json_parse_expr(ctx, value); + + return stmt; +} + +static struct stmt *json_parse_notrack_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + return notrack_stmt_alloc(int_loc); +} + +static struct stmt *json_parse_dup_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct stmt *stmt; + struct expr *expr; + json_t *tmp; + + if (json_unpack_err(ctx, value, "{s:o}", "addr", &tmp)) + return NULL; + + expr = json_parse_stmt_expr(ctx, tmp); + if (!expr) { + json_error(ctx, "Illegal dup addr arg."); + return NULL; + } + + stmt = dup_stmt_alloc(int_loc); + stmt->dup.to = expr; + + if (json_unpack(value, "{s:o}", "dev", &tmp)) + return stmt; + + expr = json_parse_stmt_expr(ctx, tmp); + if (!expr) { + json_error(ctx, "Illegal dup dev."); + stmt_free(stmt); + return NULL; + } + stmt->dup.dev = expr; + return stmt; +} + +static int json_parse_nat_flag(struct json_ctx *ctx, + json_t *root, int *flags) +{ + const struct { + const char *flag; + int val; + } flag_tbl[] = { + { "random", NF_NAT_RANGE_PROTO_RANDOM }, + { "fully-random", NF_NAT_RANGE_PROTO_RANDOM_FULLY }, + { "persistent", NF_NAT_RANGE_PERSISTENT }, + }; + const char *flag; + unsigned int i; + + assert(flags); + + if (!json_is_string(root)) { + json_error(ctx, "Invalid nat flag type %s, expected string.", + json_typename(root)); + return 1; + } + flag = json_string_value(root); + for (i = 0; i < array_size(flag_tbl); i++) { + if (!strcmp(flag, flag_tbl[i].flag)) { + *flags |= flag_tbl[i].val; + return 0; + } + } + json_error(ctx, "Unknown nat flag '%s'.", flag); + return 1; +} + +static int json_parse_nat_flags(struct json_ctx *ctx, json_t *root) +{ + int flags = 0; + json_t *value; + size_t index; + + if (json_is_string(root)) { + json_parse_nat_flag(ctx, root, &flags); + return flags; + } else if (!json_is_array(root)) { + json_error(ctx, "Invalid nat flags type %s.", + json_typename(root)); + return -1; + } + json_array_foreach(root, index, value) { + if (json_parse_nat_flag(ctx, value, &flags)) + json_error(ctx, "Parsing nat flag at index %zu failed.", + index); + } + return flags; +} + +static int nat_type_parse(const char *type) +{ + const char * const nat_etypes[] = { + [NFT_NAT_SNAT] = "snat", + [NFT_NAT_DNAT] = "dnat", + [NFT_NAT_MASQ] = "masquerade", + [NFT_NAT_REDIR] = "redirect", + }; + size_t i; + + for (i = 0; i < array_size(nat_etypes); i++) { + if (!strcmp(type, nat_etypes[i])) + return i; + } + return -1; +} + +static struct stmt *json_parse_nat_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct stmt *stmt; + json_t *tmp; + int type; + + type = nat_type_parse(key); + if (type < 0) { + json_error(ctx, "Unknown nat type '%s'.", key); + return NULL; + } + + stmt = nat_stmt_alloc(int_loc, type); + + if (!json_unpack(value, "{s:o}", "addr", &tmp)) { + stmt->nat.addr = json_parse_stmt_expr(ctx, tmp); + if (!stmt->nat.addr) { + json_error(ctx, "Invalid nat addr."); + stmt_free(stmt); + return NULL; + } + } + if (!json_unpack(value, "{s:o}", "port", &tmp)) { + stmt->nat.proto = json_parse_stmt_expr(ctx, tmp); + if (!stmt->nat.proto) { + json_error(ctx, "Invalid nat port."); + stmt_free(stmt); + return NULL; + } + } + if (!json_unpack(value, "{s:o}", "flags", &tmp)) { + int flags = json_parse_nat_flags(ctx, tmp); + + if (flags < 0) { + stmt_free(stmt); + return NULL; + } + stmt->nat.flags = flags; + } + return stmt; +} + +static struct stmt *json_parse_reject_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct stmt *stmt = reject_stmt_alloc(int_loc); + const struct datatype *dtype = NULL; + const char *type; + json_t *tmp; + + stmt->reject.type = -1; + stmt->reject.icmp_code = -1; + + if (!json_unpack(value, "{s:s}", "type", &type)) { + if (!strcmp(type, "tcp reset")) { + stmt->reject.type = NFT_REJECT_TCP_RST; + stmt->reject.icmp_code = 0; + } else if (!strcmp(type, "icmpx")) { + stmt->reject.type = NFT_REJECT_ICMPX_UNREACH; + dtype = &icmpx_code_type; + stmt->reject.icmp_code = 0; + } else if (!strcmp(type, "icmp")) { + stmt->reject.type = NFT_REJECT_ICMP_UNREACH; + stmt->reject.family = NFPROTO_IPV4; + dtype = &icmp_code_type; + stmt->reject.icmp_code = 0; + } else if (!strcmp(type, "icmpv6")) { + stmt->reject.type = NFT_REJECT_ICMP_UNREACH; + stmt->reject.family = NFPROTO_IPV6; + dtype = &icmpv6_code_type; + stmt->reject.icmp_code = 0; + } + } + if (!json_unpack(value, "{s:o}", "expr", &tmp)) { + stmt->reject.expr = json_parse_immediate_expr(ctx, "immediate", tmp); + if (!stmt->reject.expr) { + json_error(ctx, "Illegal reject expr."); + stmt_free(stmt); + return NULL; + } + if (dtype) + stmt->reject.expr->dtype = dtype; + } + return stmt; +} + +static struct stmt *json_parse_set_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + const char *opstr, *set; + struct expr *expr, *expr2; + struct stmt *stmt; + json_t *elem; + uint64_t tmp; + int op; + + if (json_unpack_err(ctx, value, "{s:s, s:o, s:s}", + "op", &opstr, "elem", &elem, "set", &set)) + return NULL; + + if (!strcmp(opstr, "add")) { + op = NFT_DYNSET_OP_ADD; + } else if (!strcmp(opstr, "update")) { + op = NFT_DYNSET_OP_UPDATE; + } else { + json_error(ctx, "Unknown set statement op '%s'.", opstr); + return NULL; + } + + expr = json_parse_set_elem_expr_stmt(ctx, elem); + if (!expr) { + json_error(ctx, "Illegal set statement element."); + return NULL; + } + + if (!json_unpack(elem, "{s:I}", "elem_timeout", &tmp)) + expr->timeout = tmp * 1000; + if (!json_unpack(elem, "{s:I}", "elem_expires", &tmp)) + expr->expiration = tmp * 1000; + json_unpack(elem, "{s:s}", "elem_comment", &expr->comment); + + if (set[0] != '@') { + json_error(ctx, "Illegal set reference in set statement."); + expr_free(expr); + return NULL; + } + expr2 = symbol_expr_alloc(int_loc, SYMBOL_SET, NULL, set + 1); + + stmt = set_stmt_alloc(int_loc); + stmt->set.op = op; + stmt->set.key = expr; + stmt->set.set = expr2; + return stmt; +} + +static int json_parse_log_flag(struct json_ctx *ctx, + json_t *root, int *flags) +{ + const struct { + const char *flag; + int val; + } flag_tbl[] = { + { "tcp sequence", NF_LOG_TCPSEQ }, + { "tcp options", NF_LOG_TCPOPT }, + { "ip options", NF_LOG_IPOPT }, + { "skuid", NF_LOG_UID }, + { "ether", NF_LOG_MACDECODE }, + { "all", NF_LOG_MASK }, + }; + const char *flag; + unsigned int i; + + assert(flags); + + if (!json_is_string(root)) { + json_error(ctx, "Invalid log flag type %s, expected string.", + json_typename(root)); + return 1; + } + flag = json_string_value(root); + for (i = 0; i < array_size(flag_tbl); i++) { + if (!strcmp(flag, flag_tbl[i].flag)) { + *flags |= flag_tbl[i].val; + return 0; + } + } + json_error(ctx, "Unknown log flag '%s'.", flag); + return 1; +} + +static int json_parse_log_flags(struct json_ctx *ctx, json_t *root) +{ + int flags = 0; + json_t *value; + size_t index; + + if (json_is_string(root)) { + json_parse_log_flag(ctx, root, &flags); + return flags; + } else if (!json_is_array(root)) { + json_error(ctx, "Invalid log flags type %s.", + json_typename(root)); + return -1; + } + json_array_foreach(root, index, value) { + if (json_parse_log_flag(ctx, value, &flags)) + json_error(ctx, "Parsing log flag at index %zu failed.", + index); + } + return flags; +} + +static struct stmt *json_parse_log_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + const char *tmpstr; + struct stmt *stmt; + json_t *jflags; + int tmp; + + stmt = log_stmt_alloc(int_loc); + + if (!json_unpack(value, "{s:s}", "prefix", &tmpstr)) { + stmt->log.prefix = xstrdup(tmpstr); + stmt->log.flags |= STMT_LOG_PREFIX; + } + if (!json_unpack(value, "{s:i}", "group", &tmp)) { + stmt->log.group = tmp; + stmt->log.flags |= STMT_LOG_GROUP; + } + if (!json_unpack(value, "{s:i}", "snaplen", &tmp)) { + stmt->log.snaplen = tmp; + stmt->log.flags |= STMT_LOG_SNAPLEN; + } + if (!json_unpack(value, "{s:i}", "queue-threshold", &tmp)) { + stmt->log.qthreshold = tmp; + stmt->log.flags |= STMT_LOG_QTHRESHOLD; + } + if (!json_unpack(value, "{s:s}", "level", &tmpstr)) { + int level = log_level_parse(tmpstr); + + if (level < 0) { + json_error(ctx, "Invalid log level '%s'.", tmpstr); + stmt_free(stmt); + return NULL; + } + stmt->log.level = level; + stmt->log.flags |= STMT_LOG_LEVEL; + } + if (!json_unpack(value, "{s:o}", "flags", &jflags)) { + int flags = json_parse_log_flags(ctx, jflags); + + if (flags < 0) { + stmt_free(stmt); + return NULL; + } + stmt->log.logflags = flags; + } + return stmt; +} + +static struct stmt *json_parse_cthelper_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct stmt *stmt = objref_stmt_alloc(int_loc); + + stmt->objref.type = NFT_OBJECT_CT_HELPER; + stmt->objref.expr = json_parse_stmt_expr(ctx, value); + if (!stmt->objref.expr) { + json_error(ctx, "Invalid cthelper reference."); + stmt_free(stmt); + return NULL; + } + return stmt; +} + +static struct stmt *json_parse_meter_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + json_t *jkey, *jstmt; + struct stmt *stmt; + const char *name; + + if (json_unpack_err(ctx, value, "{s:o, s:o}", + "key", &jkey, "stmt", &jstmt)) + return NULL; + + stmt = meter_stmt_alloc(int_loc); + + if (!json_unpack(value, "{s:s}", "name", &name)) + stmt->meter.name = xstrdup(name); + + stmt->meter.key = json_parse_expr(ctx, jkey); + if (!stmt->meter.key) { + json_error(ctx, "Invalid meter key."); + stmt_free(stmt); + return NULL; + } + + stmt->meter.stmt = json_parse_stmt(ctx, jstmt); + if (!stmt->meter.stmt) { + json_error(ctx, "Invalid meter statement."); + stmt_free(stmt); + return NULL; + } + return stmt; +} + +static int queue_flag_parse(const char *name, uint16_t *flags) +{ + if (!strcmp(name, "bypass")) + *flags |= NFT_QUEUE_FLAG_BYPASS; + else if (!strcmp(name, "fanout")) + *flags |= NFT_QUEUE_FLAG_CPU_FANOUT; + else + return 1; + return 0; +} + +static struct stmt *json_parse_queue_stmt(struct json_ctx *ctx, + const char *key, json_t *value) +{ + struct stmt *stmt = queue_stmt_alloc(int_loc); + json_t *tmp; + + if (!json_unpack(value, "{s:o}", "num", &tmp)) { + stmt->queue.queue = json_parse_stmt_expr(ctx, tmp); + if (!stmt->queue.queue) { + json_error(ctx, "Invalid queue num."); + stmt_free(stmt); + return NULL; + } + } + if (!json_unpack(value, "{s:o}", "flags", &tmp)) { + const char *flag; + size_t index; + json_t *val; + + if (json_is_string(tmp)) { + flag = json_string_value(tmp); + + if (queue_flag_parse(flag, &stmt->queue.flags)) { + json_error(ctx, "Invalid queue flag '%s'.", + flag); + stmt_free(stmt); + return NULL; + } + } else if (!json_is_array(tmp)) { + json_error(ctx, "Unexpected object type in queue flags."); + stmt_free(stmt); + return NULL; + } + + json_array_foreach(tmp, index, val) { + if (!json_is_string(val)) { + json_error(ctx, "Invalid object in queue flag array at index %zu.", + index); + stmt_free(stmt); + return NULL; + } + flag = json_string_value(val); + + if (queue_flag_parse(flag, &stmt->queue.flags)) { + json_error(ctx, "Invalid queue flag '%s'.", + flag); + stmt_free(stmt); + return NULL; + } + } + } + return stmt; +} + +static struct stmt *json_parse_stmt(struct json_ctx *ctx, json_t *root) +{ + struct { + const char *key; + struct stmt *(*cb)(struct json_ctx *, const char *, json_t *); + } stmt_parser_tbl[] = { + { "accept", json_parse_verdict_stmt }, + { "drop", json_parse_verdict_stmt }, + { "continue", json_parse_verdict_stmt }, + { "jump", json_parse_verdict_stmt }, + { "goto", json_parse_verdict_stmt }, + { "return", json_parse_verdict_stmt }, + { "match", json_parse_match_stmt }, + { "counter", json_parse_counter_stmt }, + { "mangle", json_parse_mangle_stmt }, + { "quota", json_parse_quota_stmt }, + { "limit", json_parse_limit_stmt }, + { "fwd", json_parse_fwd_stmt }, + { "notrack", json_parse_notrack_stmt }, + { "dup", json_parse_dup_stmt }, + { "snat", json_parse_nat_stmt }, + { "dnat", json_parse_nat_stmt }, + { "masquerade", json_parse_nat_stmt }, + { "redirect", json_parse_nat_stmt }, + { "reject", json_parse_reject_stmt }, + { "set", json_parse_set_stmt }, + { "log", json_parse_log_stmt }, + { "cthelper", json_parse_cthelper_stmt }, + { "meter", json_parse_meter_stmt }, + { "queue", json_parse_queue_stmt }, + }; + const char *type; + unsigned int i; + json_t *tmp; + + if (json_unpack_stmt(ctx, root, &type, &tmp)) + return NULL; + + /* Yes, verdict_map_stmt is actually an expression */ + if (!strcmp(type, "map")) { + struct expr *expr = json_parse_map_expr(ctx, type, tmp); + + if (!expr) { + json_error(ctx, "Illegal vmap statement."); + return NULL; + } + return verdict_stmt_alloc(int_loc, expr); + } + + for (i = 0; i < array_size(stmt_parser_tbl); i++) { + if (!strcmp(type, stmt_parser_tbl[i].key)) + return stmt_parser_tbl[i].cb(ctx, stmt_parser_tbl[i].key, tmp); + } + + json_error(ctx, "Unknown statement object '%s'.", type); + return NULL; +} + +static struct cmd *json_parse_cmd_add_table(struct json_ctx *ctx, json_t *root, + enum cmd_ops op, enum cmd_obj obj) +{ + struct handle h = { 0 }; + const char *family = ""; + + if (json_unpack_err(ctx, root, "{s:s}", + "family", &family)) + return NULL; + if (op != CMD_DELETE && + json_unpack_err(ctx, root, "{s:s}", "name", &h.table.name)) { + return NULL; + } else if (op == CMD_DELETE && + json_unpack(root, "{s:s}", "name", &h.table.name) && + json_unpack(root, "{s:I}", "handle", &h.handle.id)) { + json_error(ctx, "Either name or handle required to delete a table."); + return NULL; + } + h.family = parse_family(family); + if (h.family < 0) { + json_error(ctx, "Unknown family '%s'.", family); + return NULL; + } + if (h.table.name) + h.table.name = xstrdup(h.table.name); + + return cmd_alloc(op, obj, &h, int_loc, NULL); +} + +static int parse_policy(const char *policy) +{ + if (!strcmp(policy, "accept")) + return NF_ACCEPT; + if (!strcmp(policy, "drop")) + return NF_DROP; + return -1; +} + +static struct cmd *json_parse_cmd_add_chain(struct json_ctx *ctx, json_t *root, + enum cmd_ops op, enum cmd_obj obj) +{ + struct handle h = { 0 }; + const char *family = "", *policy = "", *type, *hookstr; + int prio; + struct chain *chain; + + if (json_unpack_err(ctx, root, "{s:s, s:s}", + "family", &family, + "table", &h.table.name)) + return NULL; + if (op != CMD_DELETE && + json_unpack_err(ctx, root, "{s:s}", "name", &h.chain.name)) { + return NULL; + } else if (op == CMD_DELETE && + json_unpack(root, "{s:s}", "name", &h.chain.name) && + json_unpack(root, "{s:I}", "handle", &h.handle.id)) { + json_error(ctx, "Either name or handle required to delete a chain."); + return NULL; + } + h.family = parse_family(family); + if (h.family < 0) { + json_error(ctx, "Unknown family '%s'.", family); + return NULL; + } + h.table.name = xstrdup(h.table.name); + if (h.chain.name) + h.chain.name = xstrdup(h.chain.name); + + if (op == CMD_DELETE || + op == CMD_LIST || + op == CMD_FLUSH || + json_unpack(root, "{s:s, s:s, s:i}", + "type", &type, "hook", &hookstr, "prio", &prio)) + return cmd_alloc(op, obj, &h, int_loc, NULL); + + chain = chain_alloc(NULL); + chain->flags |= CHAIN_F_BASECHAIN; + chain->type = xstrdup(type); + chain->hookstr = chain_hookname_lookup(hookstr); + if (!chain->hookstr) { + json_error(ctx, "Invalid chain hook '%s'.", hookstr); + chain_free(chain); + return NULL; + } + + if (!json_unpack(root, "{s:s}", "dev", &chain->dev)) + chain->dev = xstrdup(chain->dev); + if (!json_unpack(root, "{s:s}", "policy", &policy)) { + chain->policy = parse_policy(policy); + if (chain->policy < 0) { + json_error(ctx, "Unknown policy '%s'.", policy); + chain_free(chain); + return NULL; + } + } + + handle_merge(&chain->handle, &h); + return cmd_alloc(op, obj, &h, int_loc, chain); +} + +static struct cmd *json_parse_cmd_add_rule(struct json_ctx *ctx, json_t *root, + enum cmd_ops op, enum cmd_obj obj) +{ + struct handle h = { 0 }; + const char *family = "", *comment = NULL; + struct rule *rule; + size_t index; + json_t *tmp, *value; + + if (json_unpack_err(ctx, root, "{s:s, s:s, s:s}", + "family", &family, + "table", &h.table.name, + "chain", &h.chain.name)) + return NULL; + if (op != CMD_DELETE && + json_unpack_err(ctx, root, "{s:o}", "expr", &tmp)) + return NULL; + else if (op == CMD_DELETE && + json_unpack_err(ctx, root, "{s:I}", "handle", &h.handle.id)) + return NULL; + + h.family = parse_family(family); + if (h.family < 0) { + json_error(ctx, "Unknown family '%s'.", family); + return NULL; + } + h.table.name = xstrdup(h.table.name); + h.chain.name = xstrdup(h.chain.name); + + if (op == CMD_DELETE) + return cmd_alloc(op, obj, &h, int_loc, NULL); + + if (!json_is_array(tmp)) { + json_error(ctx, "Value of property \"expr\" must be an array."); + return NULL; + } + + json_unpack(root, "{s:i}", "pos", &h.position.id); + + rule = rule_alloc(int_loc, NULL); + + json_unpack(root, "{s:s}", "comment", &comment); + if (comment) + rule->comment = strdup(comment); + + json_array_foreach(tmp, index, value) { + struct stmt *stmt; + + if (!json_is_object(value)) { + json_error(ctx, "Unexpected expr array element of type %s, expected object.", + json_typename(value)); + rule_free(rule); + return NULL; + } + + stmt = json_parse_stmt(ctx, value); + + if (!stmt) { + json_error(ctx, "Parsing expr array at index %zd failed.", index); + rule_free(rule); + return NULL; + } + + rule->num_stmts++; + list_add_tail(&stmt->list, &rule->stmts); + } + + return cmd_alloc(op, obj, &h, int_loc, rule); +} + +static int string_to_nft_object(const char *str) +{ + const char *obj_tbl[] = { + [NFT_OBJECT_COUNTER] = "counter", + [NFT_OBJECT_QUOTA] = "quota", + [NFT_OBJECT_CT_HELPER] = "ct helper", + [NFT_OBJECT_LIMIT] = "limit", + }; + unsigned int i; + + for (i = 1; i < array_size(obj_tbl); i++) { + if (!strcmp(str, obj_tbl[i])) + return i; + } + return 0; +} + +static int string_to_set_flag(const char *str) +{ + const struct { + enum nft_set_flags val; + const char *name; + } flag_tbl[] = { + { NFT_SET_CONSTANT, "constant" }, + { NFT_SET_INTERVAL, "interval" }, + { NFT_SET_TIMEOUT, "timeout" }, + }; + unsigned int i; + + for (i = 0; i < array_size(flag_tbl); i++) { + if (!strcmp(str, flag_tbl[i].name)) + return flag_tbl[i].val; + } + return 0; +} + +static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, + enum cmd_ops op, enum cmd_obj obj) +{ + struct handle h = { 0 }; + const char *family = "", *policy, *dtype_ext = NULL; + struct set *set; + json_t *tmp; + + if (json_unpack_err(ctx, root, "{s:s, s:s}", + "family", &family, + "table", &h.table.name)) + return NULL; + if (op != CMD_DELETE && + json_unpack_err(ctx, root, "{s:s}", "name", &h.set.name)) { + return NULL; + } else if (op == CMD_DELETE && + json_unpack(root, "{s:s}", "name", &h.set.name) && + json_unpack(root, "{s:I}", "handle", &h.handle.id)) { + json_error(ctx, "Either name or handle required to delete a set."); + return NULL; + } + + h.family = parse_family(family); + if (h.family < 0) { + json_error(ctx, "Unknown family '%s'.", family); + return NULL; + } + h.table.name = xstrdup(h.table.name); + if (h.set.name) + h.set.name = xstrdup(h.set.name); + + switch (op) { + case CMD_DELETE: + case CMD_LIST: + case CMD_FLUSH: + return cmd_alloc(op, obj, &h, int_loc, NULL); + default: + break; + } + + set = set_alloc(NULL); + + if (json_unpack(root, "{s:o}", "type", &tmp)) { + json_error(ctx, "Invalid set type."); + set_free(set); + handle_free(&h); + return NULL; + } + set->key = json_parse_dtype_expr(ctx, tmp); + if (!set->key) { + json_error(ctx, "Invalid set type."); + set_free(set); + handle_free(&h); + return NULL; + } + + if (!json_unpack(root, "{s:s}", "map", &dtype_ext)) { + set->objtype = string_to_nft_object(dtype_ext); + if (set->objtype) { + set->flags |= NFT_SET_OBJECT; + } else if (datatype_lookup_byname(dtype_ext)) { + set->datatype = datatype_lookup_byname(dtype_ext); + set->flags |= NFT_SET_MAP; + } else { + json_error(ctx, "Invalid map type '%s'.", dtype_ext); + set_free(set); + handle_free(&h); + return NULL; + } + } + if (!json_unpack(root, "{s:s}", "policy", &policy)) { + if (!strcmp(policy, "performance")) + set->policy = NFT_SET_POL_PERFORMANCE; + else if (!strcmp(policy, "memory")) { + set->policy = NFT_SET_POL_MEMORY; + } else { + json_error(ctx, "Unknown set policy '%s'.", policy); + set_free(set); + handle_free(&h); + return NULL; + } + } + if (!json_unpack(root, "{s:o}", "flags", &tmp)) { + json_t *value; + size_t index; + + json_array_foreach(tmp, index, value) { + int flag; + + if (!json_is_string(value) || + !(flag = string_to_set_flag(json_string_value(value)))) { + json_error(ctx, "Invalid set flag at index %zu.", index); + set_free(set); + handle_free(&h); + return NULL; + } + set->flags |= flag; + } + } + if (!json_unpack(root, "{s:o}", "elem", &tmp)) { + set->init = json_parse_set_expr(ctx, "elem", tmp); + if (!set->init) { + json_error(ctx, "Invalid set elem expression."); + set_free(set); + handle_free(&h); + return NULL; + } + } + if (!json_unpack(root, "{s:i}", "timeout", &set->timeout)) + set->timeout *= 1000; + if (!json_unpack(root, "{s:i}", "gc-interval", &set->gc_int)) + set->gc_int *= 1000; + json_unpack(root, "{s:i}", "size", &set->desc.size); + + handle_merge(&set->handle, &h); + return cmd_alloc(op, obj, &h, int_loc, set); +} + +static struct cmd *json_parse_cmd_add_element(struct json_ctx *ctx, + json_t *root, enum cmd_ops op, + enum cmd_obj cmd_obj) +{ + struct handle h = { 0 }; + const char *family; + struct expr *expr; + json_t *tmp; + + if (json_unpack_err(ctx, root, "{s:s, s:s, s:s, s:o}", + "family", &family, + "table", &h.table.name, + "name", &h.set.name, + "elem", &tmp)) + return NULL; + + h.family = parse_family(family); + if (h.family < 0) { + json_error(ctx, "Unknown family '%s'.", family); + return NULL; + } + h.table.name = xstrdup(h.table.name); + h.set.name = xstrdup(h.set.name); + + expr = json_parse_set_expr(ctx, "elem", tmp); + if (!expr) { + json_error(ctx, "Invalid set."); + handle_free(&h); + return NULL; + } + return cmd_alloc(op, cmd_obj, &h, int_loc, expr); +} + +static struct expr *json_parse_flowtable_devs(struct json_ctx *ctx, + json_t *root) +{ + struct expr *tmp, *expr = compound_expr_alloc(int_loc, NULL); + const char *dev; + json_t *value; + size_t index; + + if (!json_unpack(root, "s", &dev)) { + tmp = symbol_expr_alloc(int_loc, SYMBOL_VALUE, NULL, dev); + compound_expr_add(expr, tmp); + return expr; + } + if (!json_is_array(root)) { + expr_free(expr); + return NULL; + } + + json_array_foreach(root, index, value) { + if (json_unpack(value, "s", &dev)) { + json_error(ctx, "Invalid flowtable dev at index %zu.", + index); + expr_free(expr); + return NULL; + } + tmp = symbol_expr_alloc(int_loc, SYMBOL_VALUE, NULL, dev); + compound_expr_add(expr, tmp); + } + return expr; +} + +static struct cmd *json_parse_cmd_add_flowtable(struct json_ctx *ctx, + json_t *root, enum cmd_ops op, + enum cmd_obj cmd_obj) +{ + const char *family, *hook, *hookstr; + struct flowtable *flowtable; + struct handle h = { 0 }; + json_t *devs; + int prio; + + if (json_unpack_err(ctx, root, "{s:s, s:s, s:s}", + "family", &family, + "table", &h.table.name, + "name", &h.flowtable)) + return NULL; + + h.family = parse_family(family); + if (h.family < 0) { + json_error(ctx, "Unknown family '%s'.", family); + return NULL; + } + h.table.name = xstrdup(h.table.name); + h.flowtable = xstrdup(h.flowtable); + + if (op == CMD_DELETE) + return cmd_alloc(op, cmd_obj, &h, int_loc, NULL); + + if (json_unpack_err(ctx, root, "{s:s, s:I, s:o}", + "hook", &hook, + "prio", &prio, + "dev", &devs)) { + handle_free(&h); + return NULL; + } + + hookstr = chain_hookname_lookup(hook); + if (!hookstr) { + json_error(ctx, "Invalid flowtable hook '%s'.", hook); + handle_free(&h); + return NULL; + } + + flowtable = flowtable_alloc(int_loc); + flowtable->hookstr = hookstr; + flowtable->priority = prio; + + flowtable->dev_expr = json_parse_flowtable_devs(ctx, devs); + if (!flowtable->dev_expr) { + json_error(ctx, "Invalid flowtable dev."); + flowtable_free(flowtable); + handle_free(&h); + return NULL; + } + return cmd_alloc(op, cmd_obj, &h, int_loc, flowtable); +} + +static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, + json_t *root, enum cmd_ops op, + enum cmd_obj cmd_obj) +{ + const char *family, *tmp; + struct handle h = { 0 }; + struct obj *obj; + + if (json_unpack_err(ctx, root, "{s:s, s:s}", + "family", &family, + "table", &h.table.name)) + return NULL; + if ((op != CMD_DELETE || + cmd_obj == NFT_OBJECT_CT_HELPER) && + json_unpack_err(ctx, root, "{s:s}", "name", &h.obj.name)) { + return NULL; + } else if (op == CMD_DELETE && + cmd_obj != NFT_OBJECT_CT_HELPER && + json_unpack(root, "{s:s}", "name", &h.obj.name) && + json_unpack(root, "{s:I}", "handle", &h.handle.id)) { + json_error(ctx, "Either name or handle required to delete an object."); + return NULL; + } + + h.family = parse_family(family); + if (h.family < 0) { + json_error(ctx, "Unknown family '%s'.", family); + return NULL; + } + h.table.name = xstrdup(h.table.name); + if (h.obj.name) + h.obj.name = xstrdup(h.obj.name); + + if (op == CMD_DELETE || op == CMD_LIST) { + if (cmd_obj == NFT_OBJECT_CT_HELPER) + return cmd_alloc_obj_ct(op, NFT_OBJECT_CT_HELPER, + &h, int_loc, obj_alloc(int_loc)); + return cmd_alloc(op, cmd_obj, &h, int_loc, NULL); + } + + obj = obj_alloc(int_loc); + + switch (cmd_obj) { + case CMD_OBJ_COUNTER: + obj->type = NFT_OBJECT_COUNTER; + json_unpack(root, "{s:i}", "packets", &obj->counter.packets); + json_unpack(root, "{s:i}", "bytes", &obj->counter.bytes); + break; + case CMD_OBJ_QUOTA: + obj->type = NFT_OBJECT_QUOTA; + json_unpack(root, "{s:i}", "bytes", &obj->quota.bytes); + json_unpack(root, "{s:i}", "used", &obj->quota.used); + json_unpack(root, "{s:b}", "inv", &obj->quota.flags); + if (obj->quota.flags) + obj->quota.flags = NFT_QUOTA_F_INV; + break; + case NFT_OBJECT_CT_HELPER: + cmd_obj = CMD_OBJ_CT_HELPER; + obj->type = NFT_OBJECT_CT_HELPER; + if (!json_unpack(root, "{s:s}", "helper", &tmp)) { + int ret; + + ret = snprintf(obj->ct_helper.name, + sizeof(obj->ct_helper.name), "%s", tmp); + if (ret < 0 || + ret >= (int)sizeof(obj->ct_helper.name)) { + json_error(ctx, "Invalid CT helper name '%s', max length is %zu.", + tmp, sizeof(obj->ct_helper.name)); + obj_free(obj); + return NULL; + } + } + if (!json_unpack(root, "{s:s}", "protocol", &tmp)) { + if (!strcmp(tmp, "tcp")) { + obj->ct_helper.l4proto = IPPROTO_TCP; + } else if (!strcmp(tmp, "udp")) { + obj->ct_helper.l4proto = IPPROTO_UDP; + } else { + json_error(ctx, "Invalid ct helper protocol '%s'.", tmp); + obj_free(obj); + return NULL; + } + } + if (!json_unpack(root, "{s:s}", "l3proto", &tmp)) { + int family = parse_family(tmp); + + if (family < 0) { + json_error(ctx, "Invalid ct helper l3proto '%s'.", tmp); + obj_free(obj); + return NULL; + } + obj->ct_helper.l3proto = family; + } else { + obj->ct_helper.l3proto = NFPROTO_IPV4; + } + break; + case CMD_OBJ_LIMIT: + obj->type = NFT_OBJECT_LIMIT; + json_unpack(root, "{s:i}", "rate", &obj->limit.rate); + if (!json_unpack(root, "{s:s}", "per", &tmp)) + obj->limit.unit = seconds_from_unit(tmp); + json_unpack(root, "{s:i}", "burst", &obj->limit.burst); + if (!json_unpack(root, "{s:s}", "unit", &tmp)) { + if (!strcmp(tmp, "packets")) { + obj->limit.type = NFT_LIMIT_PKTS; + } else if (!strcmp(tmp, "bytes")) { + obj->limit.type = NFT_LIMIT_PKT_BYTES; + } else { + json_error(ctx, "Invalid limit unit '%s'.", tmp); + obj_free(obj); + return NULL; + } + } + json_unpack(root, "{s:b}", "inv", &obj->limit.flags); + if (obj->limit.flags) + obj->limit.flags = NFT_LIMIT_F_INV; + break; + default: + BUG("Invalid CMD '%d'", cmd_obj); + } + + return cmd_alloc(op, cmd_obj, &h, int_loc, obj); +} + +static struct cmd *json_parse_cmd_add(struct json_ctx *ctx, + json_t *root, enum cmd_ops op) +{ + struct { + const char *key; + enum cmd_obj obj; + struct cmd *(*cb)(struct json_ctx *, json_t *, + enum cmd_ops, enum cmd_obj); + } cmd_obj_table[] = { + { "table", CMD_OBJ_TABLE, json_parse_cmd_add_table }, + { "chain", CMD_OBJ_CHAIN, json_parse_cmd_add_chain }, + { "rule", CMD_OBJ_RULE, json_parse_cmd_add_rule }, + { "set", CMD_OBJ_SET, json_parse_cmd_add_set }, + { "map", CMD_OBJ_SET, json_parse_cmd_add_set }, + { "element", CMD_OBJ_SETELEM, json_parse_cmd_add_element }, + { "flowtable", CMD_OBJ_FLOWTABLE, json_parse_cmd_add_flowtable }, + { "counter", CMD_OBJ_COUNTER, json_parse_cmd_add_object }, + { "quota", CMD_OBJ_QUOTA, json_parse_cmd_add_object }, + { "ct helper", NFT_OBJECT_CT_HELPER, json_parse_cmd_add_object }, + { "limit", CMD_OBJ_LIMIT, json_parse_cmd_add_object } + }; + unsigned int i; + json_t *tmp; + + if (!json_is_object(root)) { + json_error(ctx, "Value of add command must be object (got %s instead).", + json_typename(root)); + return NULL; + } + + for (i = 0; i < array_size(cmd_obj_table); i++) { + tmp = json_object_get(root, cmd_obj_table[i].key); + if (!tmp) + continue; + + if (op == CMD_CREATE && cmd_obj_table[i].obj == CMD_OBJ_RULE) { + json_error(ctx, "Create command not available for rules."); + return NULL; + } + + return cmd_obj_table[i].cb(ctx, tmp, op, cmd_obj_table[i].obj); + } + json_error(ctx, "Unknown object passed to add command."); + return NULL; +} + +static struct cmd *json_parse_cmd_replace(struct json_ctx *ctx, + json_t *root, enum cmd_ops op) +{ + struct handle h = { 0 }; + json_t *tmp, *value; + const char *family; + struct rule *rule; + size_t index; + + if (json_unpack_err(ctx, root, "{s:o}", "rule", &rule)) + return NULL; + + if (json_unpack_err(ctx, root, "{s:s, s:s, s:s, s:o}", + "family", &family, + "table", &h.table.name, + "chain", &h.chain.name, + "expr", &tmp)) + return NULL; + + if (op == CMD_REPLACE && + json_unpack_err(ctx, root, "{s:I}", "handle", &h.handle.id)) + return NULL; + + if (op == CMD_INSERT && + json_unpack_err(ctx, root, "{s:i}", "pos", &h.position.id)) + return NULL; + + h.family = parse_family(family); + if (h.family < 0) { + json_error(ctx, "Unknown family '%s'.", family); + return NULL; + } + + if (!json_is_array(tmp)) { + json_error(ctx, "Value of property \"expr\" must be an array."); + return NULL; + } + + h.table.name = xstrdup(h.table.name); + h.chain.name = xstrdup(h.chain.name); + + rule = rule_alloc(int_loc, NULL); + + if (!json_unpack(root, "{s:s}", "comment", &rule->comment)) + rule->comment = xstrdup(rule->comment); + + json_array_foreach(tmp, index, value) { + struct stmt *stmt; + + if (!json_is_object(value)) { + json_error(ctx, "Unexpected expr array element of type %s, expected object.", + json_typename(value)); + rule_free(rule); + return NULL; + } + + stmt = json_parse_stmt(ctx, value); + + if (!stmt) { + json_error(ctx, "Parsing expr array at index %zd failed.", + index); + rule_free(rule); + return NULL; + } + + rule->num_stmts++; + list_add_tail(&stmt->list, &rule->stmts); + } + + return cmd_alloc(op, CMD_OBJ_RULE, &h, int_loc, rule); +} + +static struct cmd *json_parse_cmd_list_multiple(struct json_ctx *ctx, + json_t *root, enum cmd_ops op, + enum cmd_obj obj) +{ + struct handle h = { + .family = NFPROTO_UNSPEC, + }; + const char *tmp; + + if (!json_unpack(root, "{s:s}", "family", &tmp)) { + h.family = parse_family(tmp); + if (h.family < 0) { + json_error(ctx, "Unknown family '%s'.", tmp); + return NULL; + } + } + switch (obj) { + case CMD_OBJ_SETS: + case CMD_OBJ_COUNTERS: + case CMD_OBJ_CT_HELPERS: + if (!json_unpack(root, "{s:s}", "table", &tmp)) + h.table.name = xstrdup(tmp); + break; + default: + break; + } + if (obj == CMD_OBJ_CT_HELPERS && !h.table.name) { + json_error(ctx, "Listing ct helpers requires table reference."); + return NULL; + } + return cmd_alloc(op, obj, &h, int_loc, NULL); +} + +static struct cmd *json_parse_cmd_list(struct json_ctx *ctx, + json_t *root, enum cmd_ops op) +{ + struct { + const char *key; + enum cmd_obj obj; + struct cmd *(*cb)(struct json_ctx *, json_t *, + enum cmd_ops, enum cmd_obj); + } cmd_obj_table[] = { + { "table", CMD_OBJ_TABLE, json_parse_cmd_add_table }, + { "tables", CMD_OBJ_TABLE, json_parse_cmd_list_multiple }, + { "chain", CMD_OBJ_CHAIN, json_parse_cmd_add_chain }, + { "chains", CMD_OBJ_CHAINS, json_parse_cmd_list_multiple }, + { "set", CMD_OBJ_SET, json_parse_cmd_add_set }, + { "sets", CMD_OBJ_SETS, json_parse_cmd_list_multiple }, + { "map", CMD_OBJ_MAP, json_parse_cmd_add_set }, + { "maps", CMD_OBJ_MAPS, json_parse_cmd_add_set }, + { "counter", CMD_OBJ_COUNTER, json_parse_cmd_add_object }, + { "counters", CMD_OBJ_COUNTERS, json_parse_cmd_list_multiple }, + { "quota", CMD_OBJ_QUOTA, json_parse_cmd_add_object }, + { "quotas", CMD_OBJ_QUOTAS, json_parse_cmd_list_multiple }, + { "ct helper", NFT_OBJECT_CT_HELPER, json_parse_cmd_add_object }, + { "ct helpers", CMD_OBJ_CT_HELPERS, json_parse_cmd_list_multiple }, + { "limit", CMD_OBJ_LIMIT, json_parse_cmd_add_object }, + { "limits", CMD_OBJ_LIMIT, json_parse_cmd_list_multiple }, + { "ruleset", CMD_OBJ_RULESET, json_parse_cmd_list_multiple }, + { "meter", CMD_OBJ_METER, json_parse_cmd_add_set }, + { "meters", CMD_OBJ_METERS, json_parse_cmd_list_multiple }, + { "flowtables", CMD_OBJ_FLOWTABLES, json_parse_cmd_list_multiple }, + }; + unsigned int i; + json_t *tmp; + + if (!json_is_object(root)) { + json_error(ctx, "Value of list command must be object (got %s instead).", + json_typename(root)); + return NULL; + } + + for (i = 0; i < array_size(cmd_obj_table); i++) { + tmp = json_object_get(root, cmd_obj_table[i].key); + if (!tmp) + continue; + + return cmd_obj_table[i].cb(ctx, tmp, op, cmd_obj_table[i].obj); + } + json_error(ctx, "Unknown object passed to list command."); + return NULL; +} + +static struct cmd *json_parse_cmd_reset(struct json_ctx *ctx, + json_t *root, enum cmd_ops op) +{ + struct { + const char *key; + enum cmd_obj obj; + struct cmd *(*cb)(struct json_ctx *, json_t *, + enum cmd_ops, enum cmd_obj); + } cmd_obj_table[] = { + { "counter", CMD_OBJ_COUNTER, json_parse_cmd_add_object }, + { "counters", CMD_OBJ_COUNTERS, json_parse_cmd_list_multiple }, + { "quota", CMD_OBJ_QUOTA, json_parse_cmd_add_object }, + { "quotas", CMD_OBJ_QUOTAS, json_parse_cmd_list_multiple }, + }; + unsigned int i; + json_t *tmp; + + if (!json_is_object(root)) { + json_error(ctx, "Value of reset command must be object (got %s instead).", + json_typename(root)); + return NULL; + } + + for (i = 0; i < array_size(cmd_obj_table); i++) { + tmp = json_object_get(root, cmd_obj_table[i].key); + if (!tmp) + continue; + + return cmd_obj_table[i].cb(ctx, tmp, op, cmd_obj_table[i].obj); + } + json_error(ctx, "Unknown object passed to reset command."); + return NULL; +} + +static struct cmd *json_parse_cmd_flush(struct json_ctx *ctx, + json_t *root, enum cmd_ops op) +{ + struct { + const char *key; + enum cmd_obj obj; + struct cmd *(*cb)(struct json_ctx *, json_t *, + enum cmd_ops, enum cmd_obj); + } cmd_obj_table[] = { + { "table", CMD_OBJ_TABLE, json_parse_cmd_add_table }, + { "chain", CMD_OBJ_CHAIN, json_parse_cmd_add_chain }, + { "set", CMD_OBJ_SET, json_parse_cmd_add_set }, + { "map", CMD_OBJ_MAP, json_parse_cmd_add_set }, + { "meter", CMD_OBJ_METER, json_parse_cmd_add_set }, + { "ruleset", CMD_OBJ_RULESET, json_parse_cmd_list_multiple }, + }; + unsigned int i; + json_t *tmp; + + if (!json_is_object(root)) { + json_error(ctx, "Value of flush command must be object (got %s instead).", + json_typename(root)); + return NULL; + } + + for (i = 0; i < array_size(cmd_obj_table); i++) { + tmp = json_object_get(root, cmd_obj_table[i].key); + if (!tmp) + continue; + + return cmd_obj_table[i].cb(ctx, tmp, op, cmd_obj_table[i].obj); + } + json_error(ctx, "Unknown object passed to flush command."); + return NULL; +} + +static struct cmd *json_parse_cmd_rename(struct json_ctx *ctx, + json_t *root, enum cmd_ops op) +{ + const char *family, *newname; + struct handle h = { 0 }; + struct cmd *cmd; + + if (json_unpack_err(ctx, root, "{s:{s:s, s:s, s:s, s:s}}", "chain", + "family", &family, + "table", &h.table.name, + "name", &h.chain.name, + "newname", &newname)) + return NULL; + h.family = parse_family(family); + if (h.family < 0) { + json_error(ctx, "Unknown family '%s'.", family); + return NULL; + } + h.table.name = xstrdup(h.table.name); + h.chain.name = xstrdup(h.chain.name); + + cmd = cmd_alloc(op, CMD_OBJ_CHAIN, &h, int_loc, NULL); + cmd->arg = xstrdup(newname); + return cmd; +} + +static struct cmd *json_parse_cmd(struct json_ctx *ctx, json_t *root) +{ + struct { + const char *key; + enum cmd_ops op; + struct cmd *(*cb)(struct json_ctx *ctx, json_t *, enum cmd_ops); + } parse_cb_table[] = { + { "add", CMD_ADD, json_parse_cmd_add }, + { "replace", CMD_REPLACE, json_parse_cmd_replace }, + { "create", CMD_CREATE, json_parse_cmd_add }, + { "insert", CMD_INSERT, json_parse_cmd_replace }, + { "delete", CMD_DELETE, json_parse_cmd_add }, + { "list", CMD_LIST, json_parse_cmd_list }, + { "reset", CMD_RESET, json_parse_cmd_reset }, + { "flush", CMD_FLUSH, json_parse_cmd_flush }, + { "rename", CMD_RENAME, json_parse_cmd_rename }, + //{ "export", CMD_EXPORT, json_parse_cmd_export }, + //{ "monitor", CMD_MONITOR, json_parse_cmd_monitor }, + //{ "describe", CMD_DESCRIBE, json_parse_cmd_describe } + }; + unsigned int i; + json_t *tmp; + + for (i = 0; i < array_size(parse_cb_table); i++) { + tmp = json_object_get(root, parse_cb_table[i].key); + if (!tmp) + continue; + + return parse_cb_table[i].cb(ctx, tmp, parse_cb_table[i].op); + } + json_error(ctx, "Unknown command object."); + return NULL; +} + +static int __json_parse(struct json_ctx *ctx, json_t *root) +{ + struct eval_ctx ectx = { + .nf_sock = ctx->nft->nf_sock, + .msgs = ctx->msgs, + .cache = &ctx->nft->cache, + .octx = &ctx->nft->output, + .debug_mask = ctx->nft->debug_mask, + }; + json_t *tmp, *value; + size_t index; + + if (json_unpack_err(ctx, root, "{s:o}", "nftables", &tmp)) + return -1; + + if (!json_is_array(tmp)) { + json_error(ctx, "Value of property \"nftables\" must be an array."); + return -1; + } + + json_array_foreach(tmp, index, value) { + /* this is more or less from parser_bison.y:716 */ + LIST_HEAD(list); + struct cmd *cmd; + + if (!json_is_object(value)) { + json_error(ctx, "Unexpected command array element of type %s, expected object.", json_typename(value)); + return -1; + } + cmd = json_parse_cmd(ctx, value); + + if (!cmd) { + json_error(ctx, "Parsing command array at index %zd failed.", index); + return -1; + } + + list_add_tail(&cmd->list, &list); + + if (cmd_evaluate(&ectx, cmd) < 0) { + cmd_free(cmd); + json_error(ctx, "Evaluating command at index %zd failed.", index); + return -1; + } + list_splice_tail(&list, ctx->cmds); + } + + return 0; +} + + +int nft_parse_json_buffer(struct nft_ctx *nft, char *buf, size_t buflen, + struct list_head *msgs, struct list_head *cmds) +{ + struct json_ctx ctx = { + .indesc = { + .type = INDESC_BUFFER, + .data = buf, + }, + .nft = nft, + .msgs = msgs, + .cmds = cmds, + }; + json_t *root; + int ret; + + root = json_loads(buf, 0, NULL); + if (!root) + return -EINVAL; + + ret = __json_parse(&ctx, root); + + json_decref(root); + return ret; +} + +int nft_parse_json_filename(struct nft_ctx *nft, const char *filename, + struct list_head *msgs, struct list_head *cmds) +{ + struct json_ctx ctx = { + .indesc = { + .type = INDESC_FILE, + .name = filename, + }, + .nft = nft, + .msgs = msgs, + .cmds = cmds, + }; + json_error_t err; + json_t *root; + int ret; + + root = json_load_file(filename, 0, &err); + if (!root) + return -EINVAL; + + ret = __json_parse(&ctx, root); + + json_decref(root); + return ret; +} From patchwork Tue May 8 11:08:39 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910125 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGvj1Jbdz9s0W for ; Tue, 8 May 2018 21:09:05 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752246AbeEHLJD (ORCPT ); Tue, 8 May 2018 07:09:03 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36672 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750973AbeEHLJC (ORCPT ); Tue, 8 May 2018 07:09:02 -0400 Received: from localhost ([::1]:51948 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0UP-0003qF-6p; Tue, 08 May 2018 13:09:01 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 08/14] py: Add getter/setter for echo output option Date: Tue, 8 May 2018 13:08:39 +0200 Message-Id: <20180508110845.26364-9-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org Signed-off-by: Phil Sutter --- py/nftables.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/py/nftables.py b/py/nftables.py index c175975076982..eb81f5b2fdb9c 100644 --- a/py/nftables.py +++ b/py/nftables.py @@ -47,6 +47,13 @@ class Nftables: self.nft_ctx_output_set_handle = lib.nft_ctx_output_set_handle self.nft_ctx_output_set_handle.argtypes = [c_void_p, c_bool] + self.nft_ctx_output_get_echo = lib.nft_ctx_output_get_echo + self.nft_ctx_output_get_echo.restype = c_bool + self.nft_ctx_output_get_echo.argtypes = [c_void_p] + + self.nft_ctx_output_set_echo = lib.nft_ctx_output_set_echo + self.nft_ctx_output_set_echo.argtypes = [c_void_p, c_bool] + self.nft_ctx_output_get_numeric = lib.nft_ctx_output_get_numeric self.nft_ctx_output_get_numeric.restype = c_int self.nft_ctx_output_get_numeric.argtypes = [c_void_p] @@ -114,6 +121,24 @@ class Nftables: self.nft_ctx_output_set_handle(self.__ctx, val) return old + def get_echo_output(self): + """Get the current state of echo output. + + Returns a boolean indicating whether echo output is active or not. + """ + return self.nft_ctx_output_get_echo(self.__ctx) + + def set_echo_output(self, val): + """Enable or disable echo output. + + Accepts a boolean turning echo output on or off. + + Returns the previous value. + """ + old = self.get_echo_output() + self.nft_ctx_output_set_echo(self.__ctx, val) + return old + def get_numeric_output(self): """Get the current state of numeric output. From patchwork Tue May 8 11:08:40 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910126 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGvm4D3Pz9rxs for ; Tue, 8 May 2018 21:09:08 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752314AbeEHLJI (ORCPT ); Tue, 8 May 2018 07:09:08 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36676 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752257AbeEHLJH (ORCPT ); Tue, 8 May 2018 07:09:07 -0400 Received: from localhost ([::1]:51952 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0UU-0003r1-Js; Tue, 08 May 2018 13:09:06 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 09/14] py: Add JSON support to nftables Class Date: Tue, 8 May 2018 13:08:40 +0200 Message-Id: <20180508110845.26364-10-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org Signed-off-by: Phil Sutter --- py/nftables.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/py/nftables.py b/py/nftables.py index eb81f5b2fdb9c..47ff14afc9741 100644 --- a/py/nftables.py +++ b/py/nftables.py @@ -68,6 +68,13 @@ class Nftables: self.nft_ctx_output_set_stateless = lib.nft_ctx_output_set_stateless self.nft_ctx_output_set_stateless.argtypes = [c_void_p, c_bool] + self.nft_ctx_output_get_json = lib.nft_ctx_output_get_json + self.nft_ctx_output_get_json.restype = c_bool + self.nft_ctx_output_get_json.argtypes = [c_void_p] + + self.nft_ctx_output_set_json = lib.nft_ctx_output_set_json + self.nft_ctx_output_set_json.argtypes = [c_void_p, c_bool] + self.nft_ctx_output_get_debug = lib.nft_ctx_output_get_debug self.nft_ctx_output_get_debug.restype = c_int self.nft_ctx_output_get_debug.argtypes = [c_void_p] @@ -169,7 +176,7 @@ class Nftables: return self.nft_ctx_output_get_stateless(self.__ctx) def set_stateless_output(self, val): - """Enable or Disable stateless output. + """Enable or disable stateless output. Accepts a boolean turning stateless output either on or off. @@ -179,6 +186,24 @@ class Nftables: self.nft_ctx_output_set_stateless(self.__ctx, val) return old + def get_json_output(self): + """Get the current state of JSON output. + + Returns a boolean indicating whether JSON output is active or not. + """ + return self.nft_ctx_output_get_json(self.__ctx) + + def set_json_output(self, val): + """Enable or disable JSON output. + + Accepts a boolean turning JSON output either on or off. + + Returns the previous value. + """ + old = self.get_json_output() + self.nft_ctx_output_set_json(self.__ctx, val) + return old + def get_debug(self): """Get currently active debug flags. @@ -247,3 +272,21 @@ class Nftables: error = self.nft_ctx_get_error_buffer(self.__ctx) return (rc, output, error) + + def json_cmd(self, json_root): + """Run an nftables command in JSON syntax via libnftables. + + Accepts a hash object as input. + + Returns a tuple (rc, output, error): + rc -- reutrn code as returned by nft_run_cmd_from_buffer() function + output -- a hash object containing library standard output + error -- a string containing output written to stderr + """ + json_out_old = self.set_json_output(True) + rc, output, error = self.cmd(json.dumps(json_root)) + if not json_out_old: + self.set_json_output(json_out_old) + if len(output): + output = json.loads(output) + return (rc, output, error) From patchwork Tue May 8 11:08:41 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910134 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGwd4MP6z9rvt for ; Tue, 8 May 2018 21:09:53 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752863AbeEHLJx (ORCPT ); Tue, 8 May 2018 07:09:53 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36730 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752331AbeEHLJw (ORCPT ); Tue, 8 May 2018 07:09:52 -0400 Received: from localhost ([::1]:52006 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0VD-0003tu-GA; Tue, 08 May 2018 13:09:51 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 10/14] tests/py: Reduce indenting level in nft-test.py Date: Tue, 8 May 2018 13:08:41 +0200 Message-Id: <20180508110845.26364-11-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org Signed-off-by: Phil Sutter --- tests/py/nft-test.py | 126 ++++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 61 deletions(-) diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py index d4b22817d7665..f4ddc91b39291 100755 --- a/tests/py/nft-test.py +++ b/tests/py/nft-test.py @@ -692,69 +692,73 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): ret = 0 continue - if ret == 0: - # Check for matching payload - if state == "ok" and not payload_check(payload_expected, - payload_log, cmd): - error += 1 - gotf = open("%s.payload.got" % filename_path, 'a') - payload_log.seek(0, 0) - gotf.write("# %s\n" % rule[0]) - while True: - line = payload_log.readline() - if line == "": - break - gotf.write(line) - gotf.close() - print_warning("Wrote payload for rule %s" % rule[0], - gotf.name, 1) - - # Check output of nft - numeric_old = nftables.set_numeric_output("all") - stateless_old = nftables.set_stateless_output(True) - list_cmd = 'list table %s' % table - rc, pre_output, err = nftables.cmd(list_cmd) - nftables.set_numeric_output(numeric_old) - nftables.set_stateless_output(stateless_old) - - output = pre_output.split(";") - if len(output) < 2: - reason = cmd + ": Listing is broken." - print_error(reason, filename, lineno) - ret = -1 - error += 1 + if ret != 0: + continue + + # Check for matching payload + if state == "ok" and not payload_check(payload_expected, + payload_log, cmd): + error += 1 + gotf = open("%s.payload.got" % filename_path, 'a') + payload_log.seek(0, 0) + gotf.write("# %s\n" % rule[0]) + while True: + line = payload_log.readline() + if line == "": + break + gotf.write(line) + gotf.close() + print_warning("Wrote payload for rule %s" % rule[0], + gotf.name, 1) + + # Check output of nft + numeric_old = nftables.set_numeric_output("all") + stateless_old = nftables.set_stateless_output(True) + list_cmd = 'list table %s' % table + rc, pre_output, err = nftables.cmd(list_cmd) + nftables.set_numeric_output(numeric_old) + nftables.set_stateless_output(stateless_old) + + output = pre_output.split(";") + if len(output) < 2: + reason = cmd + ": Listing is broken." + print_error(reason, filename, lineno) + ret = -1 + error += 1 + if not force_all_family_option: + return [ret, warning, error, unit_tests] + continue + + rule_output = output_clean(pre_output, chain) + if len(rule) == 3: + teoric_exit = rule[2] + else: + teoric_exit = rule[0] + + if rule_output.rstrip() != teoric_exit.rstrip(): + if rule[0].find("{") != -1: # anonymous sets + if set_check_element(teoric_exit.rstrip(), + rule_output.rstrip()) != 0: + warning += 1 + print_differences_warning(filename, lineno, + teoric_exit.rstrip(), + rule_output, cmd) + if not force_all_family_option: + return [ret, warning, error, unit_tests] + else: + if len(rule_output) <= 0: + error += 1 + print_differences_error(filename, lineno, cmd) + if not force_all_family_option: + return [ret, warning, error, unit_tests] + + warning += 1 + print_differences_warning(filename, lineno, + teoric_exit.rstrip(), + rule_output, cmd) + if not force_all_family_option: return [ret, warning, error, unit_tests] - else: - rule_output = output_clean(pre_output, chain) - if len(rule) == 3: - teoric_exit = rule[2] - else: - teoric_exit = rule[0] - - if rule_output.rstrip() != teoric_exit.rstrip(): - if rule[0].find("{") != -1: # anonymous sets - if set_check_element(teoric_exit.rstrip(), rule_output.rstrip()) != 0: - warning += 1 - print_differences_warning(filename, lineno, - teoric_exit.rstrip(), - rule_output, cmd) - if not force_all_family_option: - return [ret, warning, error, unit_tests] - else: - if len(rule_output) <= 0: - error += 1 - print_differences_error(filename, lineno, cmd) - if not force_all_family_option: - return [ret, warning, error, unit_tests] - - warning += 1 - print_differences_warning(filename, lineno, - teoric_exit.rstrip(), - rule_output, cmd) - - if not force_all_family_option: - return [ret, warning, error, unit_tests] return [ret, warning, error, unit_tests] From patchwork Tue May 8 11:08:42 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910131 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGwJ6tcFz9rvt for ; Tue, 8 May 2018 21:09:36 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752749AbeEHLJg (ORCPT ); Tue, 8 May 2018 07:09:36 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36710 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752331AbeEHLJf (ORCPT ); Tue, 8 May 2018 07:09:35 -0400 Received: from localhost ([::1]:51986 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0Ux-0003st-0E; Tue, 08 May 2018 13:09:35 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 11/14] tests/py: Simplify parsing of 'set' lines Date: Tue, 8 May 2018 13:08:42 +0200 Message-Id: <20180508110845.26364-12-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org Signed-off-by: Phil Sutter --- tests/py/nft-test.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py index f4ddc91b39291..ef9c6dbb769e5 100755 --- a/tests/py/nft-test.py +++ b/tests/py/nft-test.py @@ -855,19 +855,13 @@ def chain_process(chain_line, lineno): def set_process(set_line, filename, lineno): test_result = set_line[1] - tokens = set_line[0].split(" ") - set_name = tokens[0] - set_type = tokens[2] - - i = 3 - while len(tokens) > i and tokens[i] == ".": - set_type += " . " + tokens[i+1] - i += 2 - - if len(tokens) == i+2 and tokens[i] == "flags": - set_flags = tokens[i+1] + # set_line[0] is like ' type [ flags ]' + tokens = set_line[0].split(" flags ") + if len(tokens) > 1: + set_flags = tokens[1] else: set_flags = "" + set_name, set_type = tokens[0].split(" type ") s = Set("", "", set_name, set_type, set_flags) From patchwork Tue May 8 11:08:43 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910130 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGwC5zdyz9s0W for ; Tue, 8 May 2018 21:09:31 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752612AbeEHLJb (ORCPT ); Tue, 8 May 2018 07:09:31 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36704 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752331AbeEHLJa (ORCPT ); Tue, 8 May 2018 07:09:30 -0400 Received: from localhost ([::1]:51980 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0Ur-0003sY-5P; Tue, 08 May 2018 13:09:29 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 12/14] tests/py: Don't read expected payload for each table Date: Tue, 8 May 2018 13:08:43 +0200 Message-Id: <20180508110845.26364-13-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org When testing rule adding to different table families, expected payload was read for each tested family again. Instead, read it just once and just try to read a family-specific payload for each tested family. Signed-off-by: Phil Sutter --- tests/py/nft-test.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py index ef9c6dbb769e5..5f00b7d658f55 100755 --- a/tests/py/nft-test.py +++ b/tests/py/nft-test.py @@ -647,23 +647,25 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): print_error(reason, filename, lineno) return [-1, warning, error, unit_tests] - payload_expected = [] - - for table in table_list: + if rule[1].strip() == "ok": try: - payload_log = open("%s.payload.%s" % (filename_path, table.family)) - except IOError: payload_log = open("%s.payload" % filename_path) + payload_expected = payload_find_expected(payload_log, rule[0]) + except: + payload_expected = None + for table in table_list: if rule[1].strip() == "ok": + table_payload_expected = None try: - payload_expected.index(rule[0]) - except ValueError: - payload_expected = payload_find_expected(payload_log, rule[0]) - + payload_log = open("%s.payload.%s" % (filename_path, table.family)) + table_payload_expected = payload_find_expected(payload_log, rule[0]) + except: if not payload_expected: print_error("did not find payload information for " "rule '%s'" % rule[0], payload_log.name, 1) + if not table_payload_expected: + table_payload_expected = payload_expected for table_chain in table.chains: chain = chain_get_by_name(table_chain) @@ -696,7 +698,7 @@ def rule_add(rule, filename, lineno, force_all_family_option, filename_path): continue # Check for matching payload - if state == "ok" and not payload_check(payload_expected, + if state == "ok" and not payload_check(table_payload_expected, payload_log, cmd): error += 1 gotf = open("%s.payload.got" % filename_path, 'a') From patchwork Tue May 8 11:08:44 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Phil Sutter X-Patchwork-Id: 910127 X-Patchwork-Delegate: pablo@netfilter.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netfilter-devel-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=nwl.cc Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 40gGvt1mZdz9s0W for ; Tue, 8 May 2018 21:09:14 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752399AbeEHLJN (ORCPT ); Tue, 8 May 2018 07:09:13 -0400 Received: from orbyte.nwl.cc ([151.80.46.58]:36686 "EHLO orbyte.nwl.cc" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752239AbeEHLJN (ORCPT ); Tue, 8 May 2018 07:09:13 -0400 Received: from localhost ([::1]:51962 helo=tatos) by orbyte.nwl.cc with esmtp (Exim 4.90_1) (envelope-from ) id 1fG0Ua-0003rK-58; Tue, 08 May 2018 13:09:12 +0200 From: Phil Sutter To: Pablo Neira Ayuso Cc: netfilter-devel@vger.kernel.org Subject: [nft PATCH v3 13/14] tests/py: Highlight offending parts in differences warnings Date: Tue, 8 May 2018 13:08:44 +0200 Message-Id: <20180508110845.26364-14-phil@nwl.cc> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180508110845.26364-1-phil@nwl.cc> References: <20180508110845.26364-1-phil@nwl.cc> Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org Print the non-equal parts of the two rules in yellow when printing the differences warning. Signed-off-by: Phil Sutter --- tests/py/nft-test.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/py/nft-test.py b/tests/py/nft-test.py index 5f00b7d658f55..9ec38c24bdd37 100755 --- a/tests/py/nft-test.py +++ b/tests/py/nft-test.py @@ -121,8 +121,41 @@ def print_warning(reason, filename=None, lineno=None): print_msg(reason, filename, lineno, Colors.YELLOW, "WARNING:") +def color_differences(rule, other, color): + rlen = len(rule) + olen = len(other) + + # find equal part at start + for i in range(rlen): + if i >= olen or rule[i] != other[i]: + break + start_idx = i + + # find equal part at end + found = False + for i in range(-1, start_idx -rlen - 1, -1): + if i < start_idx -olen: + break + if rule[i] != other[i]: + found = True + break + end_idx = i + if found: + end_idx += 1 + + out = "" + if start_idx > 0: + out += rule[:start_idx] + out += color + rule[start_idx:end_idx] + Colors.ENDC + if end_idx < 0: + out += rule[end_idx:] + + return out + def print_differences_warning(filename, lineno, rule1, rule2, cmd): - reason = "'" + rule1 + "' mismatches '" + rule2 + "'" + colored_rule1 = color_differences(rule1, rule2, Colors.YELLOW) + colored_rule2 = color_differences(rule2, rule1, Colors.YELLOW) + reason = "'" + colored_rule1 + "' mismatches '" + colored_rule2 + "'" print filename + ": " + Colors.YELLOW + "WARNING: " + Colors.ENDC + \ "line: " + str(lineno + 1) + ": '" + cmd + "': " + reason