summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/json.h15
-rw-r--r--include/netlink.h6
-rw-r--r--include/nftables.h1
-rw-r--r--src/libnftables.c4
-rw-r--r--src/monitor.c13
-rw-r--r--src/parser_json.c280
-rwxr-xr-xtests/json_echo/run-test.py211
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!")