summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhil Sutter <phil@nwl.cc>2018-10-26 15:01:38 +0200
committerPablo Neira Ayuso <pablo@netfilter.org>2018-10-29 11:15:08 +0100
commitbb32d8db9a125d9676f87866e48ffbf0221ec16a (patch)
treebfbeaf0ffb248aad38fab54b7630e2fe67e64ca7
parent21d678639b28b99c301262c163128fdf67397ca6 (diff)
JSON: Add support for echo option
The basic principle is to not return a JSON object freshly created from netlink responses, but just update the existing user-provided one to make sure callers get back exactly what they expect. To achieve that, keep the parsed JSON object around in a global variable ('cur_root') and provide a custom callback to insert handles into it from received netlink messages. The tricky bit here is updating rules since unique identification is problematic. Therefore drop possibly present handles from input and later assume updates are received in order so the first rule not having a handle set is the right one. Signed-off-by: Phil Sutter <phil@nwl.cc> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
-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!")