diff options
-rw-r--r-- | include/json.h | 15 | ||||
-rw-r--r-- | include/netlink.h | 6 | ||||
-rw-r--r-- | include/nftables.h | 1 | ||||
-rw-r--r-- | src/libnftables.c | 4 | ||||
-rw-r--r-- | src/monitor.c | 13 | ||||
-rw-r--r-- | src/parser_json.c | 280 | ||||
-rwxr-xr-x | tests/json_echo/run-test.py | 211 |
7 files changed, 512 insertions, 18 deletions
diff --git a/include/json.h b/include/json.h index d2dc92d9..8d45c3c3 100644 --- a/include/json.h +++ b/include/json.h @@ -7,6 +7,7 @@ struct chain; struct cmd; struct expr; struct netlink_ctx; +struct nlmsghdr; struct rule; struct set; struct obj; @@ -103,6 +104,10 @@ void monitor_print_obj_json(struct netlink_mon_handler *monh, void monitor_print_rule_json(struct netlink_mon_handler *monh, const char *cmd, struct rule *r); +int json_events_cb(const struct nlmsghdr *nlh, + struct netlink_mon_handler *monh); +void json_print_echo(struct nft_ctx *ctx); + #else /* ! HAVE_LIBJANSSON */ typedef void json_t; @@ -234,6 +239,16 @@ static inline void monitor_print_rule_json(struct netlink_mon_handler *monh, /* empty */ } +static inline int json_events_cb(const struct nlmsghdr *nlh) +{ + return -1; +} + +static inline void json_print_echo(struct nft_ctx *ctx) +{ + /* empty */ +} + #endif /* HAVE_LIBJANSSON */ #endif /* NFTABLES_JSON_H */ diff --git a/include/netlink.h b/include/netlink.h index 5ff129ed..a8528d59 100644 --- a/include/netlink.h +++ b/include/netlink.h @@ -55,6 +55,12 @@ struct netlink_ctx { extern struct nftnl_expr *alloc_nft_expr(const char *name); extern void alloc_setelem_cache(const struct expr *set, struct nftnl_set *nls); +extern struct nftnl_table *netlink_table_alloc(const struct nlmsghdr *nlh); +extern struct nftnl_chain *netlink_chain_alloc(const struct nlmsghdr *nlh); +extern struct nftnl_set *netlink_set_alloc(const struct nlmsghdr *nlh); +extern struct nftnl_obj *netlink_obj_alloc(const struct nlmsghdr *nlh); +extern struct nftnl_rule *netlink_rule_alloc(const struct nlmsghdr *nlh); + struct nft_data_linearize { uint32_t len; uint32_t value[4]; diff --git a/include/nftables.h b/include/nftables.h index 25e78c80..1009e266 100644 --- a/include/nftables.h +++ b/include/nftables.h @@ -53,6 +53,7 @@ struct nft_ctx { uint32_t flags; struct parser_state *state; void *scanner; + void *json_root; }; enum nftables_exit_codes { diff --git a/src/libnftables.c b/src/libnftables.c index e892083f..2f67bb34 100644 --- a/src/libnftables.c +++ b/src/libnftables.c @@ -467,6 +467,8 @@ err: } free(nlbuf); + if (!rc && nft->output.json && nft->output.echo) + json_print_echo(nft); return rc; } @@ -506,6 +508,8 @@ err: nft->scanner = NULL; } + if (!rc && nft->output.json && nft->output.echo) + json_print_echo(nft); return rc; } diff --git a/src/monitor.c b/src/monitor.c index 14ccbc5f..88a61de4 100644 --- a/src/monitor.c +++ b/src/monitor.c @@ -42,7 +42,7 @@ #define nft_mon_print(monh, ...) nft_print(&monh->ctx->nft->output, __VA_ARGS__) -static struct nftnl_table *netlink_table_alloc(const struct nlmsghdr *nlh) +struct nftnl_table *netlink_table_alloc(const struct nlmsghdr *nlh) { struct nftnl_table *nlt; @@ -55,7 +55,7 @@ static struct nftnl_table *netlink_table_alloc(const struct nlmsghdr *nlh) return nlt; } -static struct nftnl_chain *netlink_chain_alloc(const struct nlmsghdr *nlh) +struct nftnl_chain *netlink_chain_alloc(const struct nlmsghdr *nlh) { struct nftnl_chain *nlc; @@ -68,7 +68,7 @@ static struct nftnl_chain *netlink_chain_alloc(const struct nlmsghdr *nlh) return nlc; } -static struct nftnl_set *netlink_set_alloc(const struct nlmsghdr *nlh) +struct nftnl_set *netlink_set_alloc(const struct nlmsghdr *nlh) { struct nftnl_set *nls; @@ -94,7 +94,7 @@ static struct nftnl_set *netlink_setelem_alloc(const struct nlmsghdr *nlh) return nls; } -static struct nftnl_rule *netlink_rule_alloc(const struct nlmsghdr *nlh) +struct nftnl_rule *netlink_rule_alloc(const struct nlmsghdr *nlh) { struct nftnl_rule *nlr; @@ -107,7 +107,7 @@ static struct nftnl_rule *netlink_rule_alloc(const struct nlmsghdr *nlh) return nlr; } -static struct nftnl_obj *netlink_obj_alloc(const struct nlmsghdr *nlh) +struct nftnl_obj *netlink_obj_alloc(const struct nlmsghdr *nlh) { struct nftnl_obj *nlo; @@ -908,6 +908,9 @@ int netlink_echo_callback(const struct nlmsghdr *nlh, void *data) if (!echo_monh.ctx->nft->output.echo) return MNL_CB_OK; + if (ctx->nft->output.json) + return json_events_cb(nlh, &echo_monh); + return netlink_events_cb(nlh, &echo_monh); } diff --git a/src/parser_json.c b/src/parser_json.c index e497f0ce..bc682e92 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -25,6 +25,10 @@ #include <linux/netfilter/nf_tables.h> #include <jansson.h> +#include <mnl.h> +#include <libnftnl/rule.h> +#include <linux/netfilter/nfnetlink.h> + #define CTX_F_RHS (1 << 0) #define CTX_F_STMT (1 << 1) #define CTX_F_PRIMARY (1 << 2) @@ -2333,6 +2337,9 @@ static struct cmd *json_parse_cmd_add_table(struct json_ctx *ctx, json_t *root, if (h.table.name) h.table.name = xstrdup(h.table.name); + if (op == CMD_ADD) + json_object_del(root, "handle"); + return cmd_alloc(op, obj, &h, int_loc, NULL); } @@ -2404,6 +2411,9 @@ static struct cmd *json_parse_cmd_add_chain(struct json_ctx *ctx, json_t *root, } } + if (op == CMD_ADD) + json_object_del(root, "handle"); + handle_merge(&chain->handle, &h); return cmd_alloc(op, obj, &h, int_loc, chain); } @@ -2474,6 +2484,9 @@ static struct cmd *json_parse_cmd_add_rule(struct json_ctx *ctx, json_t *root, list_add_tail(&stmt->list, &rule->stmts); } + if (op == CMD_ADD) + json_object_del(root, "handle"); + return cmd_alloc(op, obj, &h, int_loc, rule); } @@ -2628,6 +2641,10 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root, json_unpack(root, "{s:i}", "size", &set->desc.size); handle_merge(&set->handle, &h); + + if (op == CMD_ADD) + json_object_del(root, "handle"); + return cmd_alloc(op, obj, &h, int_loc, set); } @@ -2943,6 +2960,9 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx, BUG("Invalid CMD '%d'", cmd_obj); } + if (op == CMD_ADD) + json_object_del(root, "handle"); + return cmd_alloc(op, cmd_obj, &h, int_loc, obj); } @@ -3066,6 +3086,9 @@ static struct cmd *json_parse_cmd_replace(struct json_ctx *ctx, list_add_tail(&stmt->list, &rule->stmts); } + if (op == CMD_REPLACE) + json_object_del(root, "handle"); + return cmd_alloc(op, CMD_OBJ_RULE, &h, int_loc, rule); } @@ -3298,7 +3321,7 @@ static int json_verify_metainfo(struct json_ctx *ctx, json_t *root) return 0; } -static int __json_parse(struct json_ctx *ctx, json_t *root) +static int __json_parse(struct json_ctx *ctx) { struct eval_ctx ectx = { .nft = ctx->nft, @@ -3307,7 +3330,8 @@ static int __json_parse(struct json_ctx *ctx, json_t *root) json_t *tmp, *value; size_t index; - if (json_unpack_err(ctx, root, "{s:o}", "nftables", &tmp)) + if (json_unpack_err(ctx, ctx->nft->json_root, + "{s:o}", "nftables", &tmp)) return -1; if (!json_is_array(tmp)) { @@ -3355,7 +3379,6 @@ static int __json_parse(struct json_ctx *ctx, json_t *root) return 0; } - int nft_parse_json_buffer(struct nft_ctx *nft, const char *buf, struct list_head *msgs, struct list_head *cmds) { @@ -3368,16 +3391,18 @@ int nft_parse_json_buffer(struct nft_ctx *nft, const char *buf, .msgs = msgs, .cmds = cmds, }; - json_t *root; int ret; - root = json_loads(buf, 0, NULL); - if (!root) + nft->json_root = json_loads(buf, 0, NULL); + if (!nft->json_root) return -EINVAL; - ret = __json_parse(&ctx, root); + ret = __json_parse(&ctx); - json_decref(root); + if (!nft->output.echo) { + json_decref(nft->json_root); + nft->json_root = NULL; + } return ret; } @@ -3394,15 +3419,244 @@ int nft_parse_json_filename(struct nft_ctx *nft, const char *filename, .cmds = cmds, }; json_error_t err; - json_t *root; int ret; - root = json_load_file(filename, 0, &err); - if (!root) + nft->json_root = json_load_file(filename, 0, &err); + if (!nft->json_root) return -EINVAL; - ret = __json_parse(&ctx, root); + ret = __json_parse(&ctx); - json_decref(root); + if (!nft->output.echo) { + json_decref(nft->json_root); + nft->json_root = NULL; + } return ret; } + +static int json_echo_error(struct netlink_mon_handler *monh, + const char *fmt, ...) +{ + struct error_record *erec; + va_list ap; + + va_start(ap, fmt); + erec = erec_vcreate(EREC_ERROR, int_loc, fmt, ap); + va_end(ap); + erec_queue(erec, monh->ctx->msgs); + + return MNL_CB_ERROR; +} + +static int obj_prop_check(json_t *obj, const char *key, const char *value) +{ + const char *cmp; + + if (!value) + return 0; + + if (json_unpack(obj, "{s:s}", key, &cmp) || strcmp(value, cmp)) + return 1; + + return 0; +} + +static bool obj_info_matches(json_t *obj, const char *family, const char *table, + const char *chain, const char *name) +{ + if (obj_prop_check(obj, "family", family) || + obj_prop_check(obj, "table", table) || + obj_prop_check(obj, "chain", chain) || + obj_prop_check(obj, "name", name)) + return false; + + return true; +} + +static int json_update_table(struct netlink_mon_handler *monh, + json_t *array, const struct nlmsghdr *nlh) +{ + const char *family, *name; + struct nftnl_table *nlt; + uint64_t handle; + json_t *value; + size_t index; + + nlt = netlink_table_alloc(nlh); + family = family2str(nftnl_table_get_u32(nlt, NFTNL_TABLE_FAMILY)); + name = nftnl_table_get_str(nlt, NFTNL_TABLE_NAME); + handle = nftnl_table_get_u64(nlt, NFTNL_TABLE_HANDLE); + + json_array_foreach(array, index, value) { + if (json_unpack(value, "{s:{s:o}}", "add", "table", &value) || + !obj_info_matches(value, family, NULL, NULL, name)) + continue; + + json_object_set_new(value, "handle", json_integer(handle)); + return MNL_CB_OK; + } + + return json_echo_error(monh, "JSON table object '%s %s' not found.\n", + family, name); +} + +static int json_update_chain(struct netlink_mon_handler *monh, + json_t *array, const struct nlmsghdr *nlh) +{ + const char *family, *table, *name; + struct nftnl_chain *nlc; + uint64_t handle; + json_t *value; + size_t index; + + nlc = netlink_chain_alloc(nlh); + family = family2str(nftnl_chain_get_u32(nlc, NFTNL_CHAIN_FAMILY)); + table = nftnl_chain_get_str(nlc, NFTNL_CHAIN_TABLE); + name = nftnl_chain_get_str(nlc, NFTNL_CHAIN_NAME); + handle = nftnl_chain_get_u64(nlc, NFTNL_CHAIN_HANDLE); + + json_array_foreach(array, index, value) { + if (json_unpack(value, "{s:{s:o}}", "add", "chain", &value) || + !obj_info_matches(value, family, table, NULL, name)) + continue; + + json_object_set_new(value, "handle", json_integer(handle)); + return MNL_CB_OK; + } + return json_echo_error(monh, + "JSON chain object '%s %s %s' not found.\n", + family, table, name); +} + +static int json_update_rule(struct netlink_mon_handler *monh, + json_t *array, const struct nlmsghdr *nlh) +{ + const char *family, *table, *chain; + struct nftnl_rule *nlr; + uint64_t handle; + json_t *value; + size_t index; + + nlr = netlink_rule_alloc(nlh); + family = family2str(nftnl_rule_get_u32(nlr, NFTNL_RULE_FAMILY)); + table = nftnl_rule_get_str(nlr, NFTNL_RULE_TABLE); + chain = nftnl_rule_get_str(nlr, NFTNL_RULE_CHAIN); + handle = nftnl_rule_get_u64(nlr, NFTNL_RULE_HANDLE); + + json_array_foreach(array, index, value) { + if (json_unpack(value, "{s:{s:o}}", "add", "rule", &value) || + !obj_info_matches(value, family, table, chain, NULL)) + continue; + + /* this is a hack - assume rules are added in order and caller + * sets the handle for each we previously returned */ + if (json_object_get(value, "handle")) + continue; + + json_object_set_new(value, "handle", json_integer(handle)); + return MNL_CB_OK; + } + return json_echo_error(monh, + "JSON rule object in '%s %s %s' without handle not found\n", + family, table, chain); +} + +static int json_update_set(struct netlink_mon_handler *monh, + json_t *array, const struct nlmsghdr *nlh) +{ + const char *family, *table, *name; + struct nftnl_set *nls; + uint64_t handle; + uint32_t flags; + json_t *value; + size_t index; + + nls = netlink_set_alloc(nlh); + flags = nftnl_set_get_u32(nls, NFTNL_SET_FLAGS); + if (flags & NFT_SET_ANONYMOUS) + return MNL_CB_OK; + + family = family2str(nftnl_set_get_u32(nls, NFTNL_SET_FAMILY)); + table = nftnl_set_get_str(nls, NFTNL_SET_TABLE); + name = nftnl_set_get_str(nls, NFTNL_SET_NAME); + handle = nftnl_set_get_u64(nls, NFTNL_SET_HANDLE); + + json_array_foreach(array, index, value) { + if (json_unpack(value, "{s:{s:o}}", "add", "set", &value) || + !obj_info_matches(value, family, table, NULL, name)) + continue; + + json_object_set_new(value, "handle", json_integer(handle)); + return MNL_CB_OK; + } + return json_echo_error(monh, "JSON set object '%s %s %s' not found.\n", + family, table, name); +} + +static int json_update_obj(struct netlink_mon_handler *monh, + json_t *array, const struct nlmsghdr *nlh) +{ + const char *family, *table, *name, *type; + struct nftnl_obj *nlo; + uint64_t handle; + json_t *value; + size_t index; + + nlo = netlink_obj_alloc(nlh); + family = family2str(nftnl_obj_get_u32(nlo, NFTNL_OBJ_FAMILY)); + table = nftnl_obj_get_str(nlo, NFTNL_OBJ_TABLE); + name = nftnl_obj_get_str(nlo, NFTNL_OBJ_NAME); + type = obj_type_name(nftnl_obj_get_u32(nlo, NFTNL_OBJ_TYPE)); + handle = nftnl_obj_get_u64(nlo, NFTNL_OBJ_HANDLE); + + json_array_foreach(array, index, value) { + if (json_unpack(value, "{s:{s:o}}", "add", type, &value) || + !obj_info_matches(value, family, table, NULL, name)) + continue; + + json_object_set_new(value, "handle", json_integer(handle)); + return MNL_CB_OK; + } + return json_echo_error(monh, "JSON %s object '%s %s %s' not found.\n", + type, family, table, name); +} + +int json_events_cb(const struct nlmsghdr *nlh, struct netlink_mon_handler *monh) +{ + json_t *root = monh->ctx->nft->json_root; + json_t *array; + + if (!root) + return MNL_CB_OK; + + if (json_unpack(root, "{s:o}", "nftables", &array)) { + erec_queue(error(int_loc, "Invalid JSON root element\n"), + monh->ctx->msgs); + return MNL_CB_STOP; + } + + switch (NFNL_MSG_TYPE(nlh->nlmsg_type)) { + case NFT_MSG_NEWTABLE: + return json_update_table(monh, array, nlh); + case NFT_MSG_NEWCHAIN: + return json_update_chain(monh, array, nlh); + case NFT_MSG_NEWRULE: + return json_update_rule(monh, array, nlh); + case NFT_MSG_NEWSET: + return json_update_set(monh, array, nlh); + case NFT_MSG_NEWOBJ: + return json_update_obj(monh, array, nlh); + } + + return MNL_CB_OK; +} + +void json_print_echo(struct nft_ctx *ctx) +{ + if (!ctx->json_root) + return; + + json_dumpf(ctx->json_root, ctx->output.output_fp, JSON_PRESERVE_ORDER); + json_decref(ctx->json_root); + ctx->json_root = NULL; +} diff --git a/tests/json_echo/run-test.py b/tests/json_echo/run-test.py new file mode 100755 index 00000000..dcef3f1b --- /dev/null +++ b/tests/json_echo/run-test.py @@ -0,0 +1,211 @@ +#!/usr/bin/python2 + +import sys +import os +import json + +TESTS_PATH = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(TESTS_PATH, '../../py/')) + +from nftables import Nftables + +# Change working directory to repository root +os.chdir(TESTS_PATH + "/../..") + +if not os.path.exists('src/.libs/libnftables.so'): + print "The nftables library does not exist. " \ + "You need to build the project." + sys.exit(1) + +nftables = Nftables(sofile = 'src/.libs/libnftables.so') +nftables.set_echo_output(True) + +# various commands to work with + +flush_ruleset = { "flush": { "ruleset": None } } + +add_table = { "add": { + "table": { + "family": "inet", + "name": "t", + } +}} + +add_chain = { "add": { + "chain": { + "family": "inet", + "table": "t", + "name": "c" + } +}} + +add_set = { "add": { + "set": { + "family": "inet", + "table": "t", + "name": "s", + "type": "inet_service" + } +}} + +add_rule = { "add": { + "rule": { + "family": "inet", + "table": "t", + "chain": "c", + "expr": [ { "accept": None } ] + } +}} + +add_counter = { "add": { + "counter": { + "family": "inet", + "table": "t", + "name": "c" + } +}} + +add_quota = { "add": { + "quota": { + "family": "inet", + "table": "t", + "name": "q", + "bytes": 65536 + } +}} + +# helper functions + +def exit_err(msg): + print "Error: %s" % msg + sys.exit(1) + +def exit_dump(e, obj): + print "FAIL: %s" % e + print "Output was:" + json.dumps(out, sort_keys = True, indent = 4, separators = (',', ': ')) + sys.exit(1) + +def do_flush(): + rc, out, err = nftables.json_cmd({ "nftables": [flush_ruleset] }) + if not rc is 0: + exit_err("flush ruleset failed: %s" % err) + +def do_command(cmd): + if not type(cmd) is list: + cmd = [cmd] + rc, out, err = nftables.json_cmd({ "nftables": cmd }) + if not rc is 0: + exit_err("command failed: %s" % err) + return out + +# single commands first + +do_flush() + +print "Adding table t" +out = do_command(add_table) +try: + table_out = out["nftables"][0] + table_handle = out["nftables"][0]["add"]["table"]["handle"] +except Exception as e: + exit_dump(e, out) + +print "Adding chain c" +out = do_command(add_chain) +try: + chain_out = out["nftables"][0] + chain_handle = out["nftables"][0]["add"]["chain"]["handle"] +except Exception as e: + exit_dump(e, out) + +print "Adding set s" +out = do_command(add_set) +try: + set_out = out["nftables"][0] + set_handle = out["nftables"][0]["add"]["set"]["handle"] +except Exception as e: + exit_dump(e, out) + +print "Adding rule" +out = do_command(add_rule) +try: + rule_out = out["nftables"][0] + rule_handle = out["nftables"][0]["add"]["rule"]["handle"] +except Exception as e: + exit_dump(e, out) + +print "Adding counter" +out = do_command(add_counter) +try: + counter_out = out["nftables"][0] + counter_handle = out["nftables"][0]["add"]["counter"]["handle"] +except Exception as e: + exit_dump(e, out) + +print "Adding quota" +out = do_command(add_quota) +try: + quota_out = out["nftables"][0] + quota_handle = out["nftables"][0]["add"]["quota"]["handle"] +except Exception as e: + exit_dump(e, out) + +# adjust names and add items again +# Note: Add second chain to same table, otherwise it's handle will be the same +# as before. Same for the set and the rule. Bug? + +table_out["add"]["table"]["name"] = "t2" +#chain_out["add"]["chain"]["table"] = "t2" +chain_out["add"]["chain"]["name"] = "c2" +#set_out["add"]["set"]["table"] = "t2" +set_out["add"]["set"]["name"] = "s2" +#rule_out["add"]["rule"]["table"] = "t2" +#rule_out["add"]["rule"]["chain"] = "c2" +counter_out["add"]["counter"]["name"] = "c2" +quota_out["add"]["quota"]["name"] = "q2" + +print "Adding table t2" +out = do_command(table_out) +if out["nftables"][0]["add"]["table"]["handle"] == table_handle: + exit_err("handle not changed in re-added table!") + +print "Adding chain c2" +out = do_command(chain_out) +if out["nftables"][0]["add"]["chain"]["handle"] == chain_handle: + exit_err("handle not changed in re-added chain!") + +print "Adding set s2" +out = do_command(set_out) +if out["nftables"][0]["add"]["set"]["handle"] == set_handle: + exit_err("handle not changed in re-added set!") + +print "Adding rule again" +out = do_command(rule_out) +if out["nftables"][0]["add"]["rule"]["handle"] == rule_handle: + exit_err("handle not changed in re-added rule!") + +print "Adding counter c2" +out = do_command(counter_out) +if out["nftables"][0]["add"]["counter"]["handle"] == counter_handle: + exit_err("handle not changed in re-added counter!") + +print "Adding quota q2" +out = do_command(quota_out) +if out["nftables"][0]["add"]["quota"]["handle"] == quota_handle: + exit_err("handle not changed in re-added quota!") + +# now multiple commands + +do_flush() + +print "doing multi add" +add_multi = [ add_table, add_chain, add_set, add_rule ] +out = do_command(add_multi) +out = out["nftables"] + +if not "handle" in out[0]["add"]["table"] or \ + not "handle" in out[1]["add"]["chain"] or \ + not "handle" in out[2]["add"]["set"] or \ + not "handle" in out[3]["add"]["rule"]: + exit_err("handle(s) missing in multi cmd output!") |