diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 5 | ||||
-rw-r--r-- | src/ct.c | 4 | ||||
-rw-r--r-- | src/datatype.c | 8 | ||||
-rw-r--r-- | src/expression.c | 15 | ||||
-rw-r--r-- | src/exthdr.c | 2 | ||||
-rw-r--r-- | src/fib.c | 1 | ||||
-rw-r--r-- | src/hash.c | 1 | ||||
-rw-r--r-- | src/json.c | 1564 | ||||
-rw-r--r-- | src/libnftables.c | 16 | ||||
-rw-r--r-- | src/main.c | 11 | ||||
-rw-r--r-- | src/meta.c | 6 | ||||
-rw-r--r-- | src/numgen.c | 1 | ||||
-rw-r--r-- | src/payload.c | 3 | ||||
-rw-r--r-- | src/rt.c | 2 | ||||
-rw-r--r-- | src/rule.c | 4 | ||||
-rw-r--r-- | src/statement.c | 15 |
16 files changed, 1657 insertions, 1 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 92e6795f..c5c3b0bc 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 @@ -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 d5a5091e..c77d228e 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -25,6 +25,7 @@ #include <gmputil.h> #include <erec.h> #include <netlink.h> +#include <json.h> #include <netinet/ip_icmp.h> @@ -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, }; @@ -987,6 +993,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, }; @@ -1163,4 +1170,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 3c5ea0ff..53fb1811 100644 --- a/src/expression.c +++ b/src/expression.c @@ -23,6 +23,7 @@ #include <utils.h> #include <list.h> #include <erec.h> +#include <json.h> 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 06a82070..cb0a58e8 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, @@ -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, }; @@ -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 00000000..e458eb3e --- /dev/null +++ b/src/json.c @@ -0,0 +1,1564 @@ +#define _GNU_SOURCE +#include <string.h> + +#include <expression.h> +#include <list.h> +#include <netlink.h> +#include <rule.h> +#include <rt.h> + +#include <netdb.h> +#include <netinet/icmp6.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <linux/netfilter.h> +#include <linux/netfilter/nf_log.h> +#include <linux/netfilter/nf_nat.h> +#include <linux/netfilter/nf_tables.h> +#include <pwd.h> +#include <grp.h> +#include <jansson.h> +#include <syslog.h> + +#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 ae61ce65..68e53f70 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 = "<cmdline>", @@ -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[] = { { @@ -95,6 +96,10 @@ static const struct option options[] = { .val = OPT_ECHO, }, { + .name = "json", + .val = OPT_JSON, + }, + { .name = NULL } }; @@ -112,6 +117,7 @@ static void show_help(const char *name) " -f, --file <filename> Read input from <filename>\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); } @@ -35,6 +35,7 @@ #include <utils.h> #include <erec.h> #include <iface.h> +#include <json.h> 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 aa6da490..b7751b07 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 09665a0e..6517686c 100644 --- a/src/payload.c +++ b/src/payload.c @@ -25,6 +25,7 @@ #include <payload.h> #include <gmputil.h> #include <utils.h> +#include <json.h> 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, @@ -22,6 +22,7 @@ #include <datatype.h> #include <rt.h> #include <rule.h> +#include <json.h> 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, }; @@ -21,6 +21,7 @@ #include <utils.h> #include <netdb.h> #include <netlink.h> +#include <json.h> #include <libnftnl/common.h> #include <libnftnl/ruleset.h> @@ -1819,6 +1820,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 8160e0ad..d2910018 100644 --- a/src/statement.c +++ b/src/statement.c @@ -24,6 +24,7 @@ #include <utils.h> #include <list.h> #include <xt.h> +#include <json.h> #include <netinet/in.h> #include <linux/netfilter/nf_nat.h> @@ -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, }; |