#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, s:I}", "family", family2str(set->handle.family), "name", set->handle.set.name, "table", set->handle.table.name, "type", set_dtype_json(set->key), "handle", 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_integer(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, "handle", 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, s:I}", "family", family2str(chain->handle.family), "table", chain->handle.table.name, "name", chain->handle.chain.name, "handle", 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.num, "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 *rate_unit = NULL, *burst_unit = NULL; const char *type = obj_type_name(obj->type); uint64_t rate, burst; json_t *root, *tmp; root = json_pack("{s:s, s:s, s:s, s:I}", "family", family2str(obj->handle.family), "name", obj->handle.obj.name, "table", obj->handle.table.name, "handle", 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}", "type", 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: rate = obj->limit.rate; burst = obj->limit.burst; if (obj->limit.type == NFT_LIMIT_PKT_BYTES) { rate_unit = get_rate(obj->limit.rate, &rate); burst_unit = get_rate(obj->limit.burst, &burst); } tmp = json_pack("{s:I, s:s}", "rate", rate, "per", get_unit(obj->limit.unit)); if (obj->limit.flags & NFT_LIMIT_F_INV) json_object_set_new(tmp, "inv", json_true()); if (rate_unit) json_object_set_new(tmp, "rate_unit", json_string(rate_unit)); if (burst) { json_object_set_new(tmp, "burst", json_integer(burst)); if (burst_unit) json_object_set_new(tmp, "burst_unit", json_string(burst_unit)); } 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.num); 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, s:I}", "family", family2str(table->handle.family), "name", table->handle.table.name, "handle", 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); json_t *tmp; 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) { tmp = json_integer(expr->timeout / 1000); json_object_set_new(root, "timeout", tmp); } if (expr->expiration) { tmp = json_integer(expr->expiration / 1000); json_object_set_new(root, "expires", tmp); } if (expr->comment) { tmp = json_string(expr->comment); json_object_set_new(root, "comment", tmp); } 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) { const char *type = "map"; if (expr->mappings->ops->type == EXPR_SET_REF && expr->mappings->set->datatype->type == TYPE_VERDICT) type = "vmap"; return json_pack("{s:{s:o, s:o}}", type, "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 *socket_expr_json(const struct expr *expr, struct output_ctx *octx) { return json_pack("{s:{s:s}}", "socket", "key", socket_templates[expr->socket.key].token); } 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(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 && burst != 5) { 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, *tmp; root = json_pack("{s:o}", "dev", expr_print_json(stmt->fwd.dev, octx)); if (stmt->fwd.addr) { tmp = json_string(family2str(stmt->fwd.family)); json_object_set_new(root, "family", tmp); tmp = expr_print_json(stmt->fwd.addr, octx); json_object_set_new(root, "addr", tmp); } 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, s:i}", "key", expr_print_json(stmt->meter.key, octx), "stmt", tmp, "size", stmt->meter.size); 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); } json_t *connlimit_stmt_json(const struct stmt *stmt, struct output_ctx *octx) { json_t *root = json_pack("{s:i}", "val", stmt->connlimit.count); if (stmt->connlimit.flags & NFT_CONNLIMIT_F_INV) json_object_set_new(root, "inv", json_true()); return json_pack("{s:o}", "ct count", root); } 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; }