Right now this object merely contains the nftables version and release name as well as a JSON schema version, but it could be extended arbitrarily. In the future, this will also allow for non-compatible schema changes should the need for this arise. Adjust the parser to accept metainfo objects and make it verify json_schema_version to be less than or equal to the one hard-coded in the library. Signed-off-by: Phil Sutter <phil@xxxxxx> --- doc/libnftables-json.adoc | 25 +++++++++++++++++++++++-- include/json.h | 2 ++ src/json.c | 19 ++++++++++++++++--- src/parser_json.c | 27 +++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/doc/libnftables-json.adoc b/doc/libnftables-json.adoc index c174a35487d46..59bac17fd0051 100644 --- a/doc/libnftables-json.adoc +++ b/doc/libnftables-json.adoc @@ -16,13 +16,14 @@ libnftables-json - Supported JSON schema by libnftables 'CMD_OBJECTS' := 'CMD_OBJECT' [ *,* 'CMD_OBJECTS' ] -'CMD_OBJECT' := *{* 'CMD'*:* 'LIST_OBJECT' *}* +'CMD_OBJECT' := *{* 'CMD'*:* 'LIST_OBJECT' *}* | 'METAINFO_OBJECT' 'CMD' := *"add"* | *"replace"* | *"create"* | *"insert"* | *"delete"* | *"list"* | *"reset"* | *"flush"* | *"rename"* 'LIST_OBJECT' := 'TABLE' | 'CHAIN' | 'RULE' | 'SET' | 'MAP' | 'ELEMENT' | - 'FLOWTABLE' | 'COUNTER' | 'QUOTA' | 'CT_HELPER' | 'LIMIT' + 'FLOWTABLE' | 'COUNTER' | 'QUOTA' | 'CT_HELPER' | 'LIMIT' | + 'METAINFO_OBJECT' == DESCRIPTION libnftables supports JSON formatted input and output. This is implemented as an @@ -47,6 +48,26 @@ It's value is a ruleset element - basically identical to output elements apart from certain properties which may be interpreted differently or are required when output generally omits them. +== METAINFO OBJECT +In output, the first object in *nftables* array is a special one containing +library information. Its content is as follows: + +[verse] +*{ "metainfo": { + "version":* 'STRING'*, + "release_name":* 'STRING'*, + "json_schema_version":* 'NUMBER' +*}}* + +The values of *version* and *release_name* properties are equal to the package +version and release name as printed by *nft -v*. The value of +*json_schema_version* property is an integer indicating the schema version. + +If supplied in library input, the parser will verify *json_schema_version* value +to not exceed the internally hardcoded one (to make sure the given schema is +fully understood). In future, a lower number than the internal one may activate +compatibility mode to parse outdated and incompatible JSON input. + == COMMAND OBJECTS The structure accepts an arbitrary amount of commands which are interpreted in order of appearance. For instance, the following standard syntax input: diff --git a/include/json.h b/include/json.h index b75512b8475fb..7967dd20fc83e 100644 --- a/include/json.h +++ b/include/json.h @@ -15,6 +15,8 @@ struct table; #ifdef HAVE_LIBJANSSON +#define JSON_SCHEMA_VERSION 1 + #include <jansson.h> json_t *binop_expr_json(const struct expr *expr, struct output_ctx *octx); diff --git a/src/json.c b/src/json.c index 199ca0a308bcd..8d55885fa3916 100644 --- a/src/json.c +++ b/src/json.c @@ -1499,6 +1499,14 @@ static json_t *do_list_flowtables_json(struct netlink_ctx *ctx, struct cmd *cmd) return root; } +static json_t *generate_json_metainfo(void) +{ + return json_pack("{s: {s:s, s:s, s:i}}", "metainfo", + "version", PACKAGE_VERSION, + "release_name", RELEASE_NAME, + "json_schema_version", JSON_SCHEMA_VERSION); +} + int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) { struct table *table = NULL; @@ -1565,10 +1573,15 @@ int do_command_list_json(struct netlink_ctx *ctx, struct cmd *cmd) BUG("invalid command object type %u\n", cmd->obj); } - if (json_is_array(root) && !json_array_size(root)) { - json_decref(root); - root = json_null(); + if (!json_is_array(root)) { + json_t *tmp = json_array(); + + json_array_append_new(tmp, root); + root = tmp; } + + json_array_insert_new(root, 0, generate_json_metainfo()); + root = json_pack("{s:o}", "nftables", root); json_dumpf(root, ctx->octx->output_fp, 0); json_decref(root); diff --git a/src/parser_json.c b/src/parser_json.c index 404dbe9fa82a6..ce85b798e908e 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -3060,6 +3060,22 @@ static struct cmd *json_parse_cmd(struct json_ctx *ctx, json_t *root) return NULL; } +static int json_verify_metainfo(struct json_ctx *ctx, json_t *root) +{ + int schema_version; + + if (!json_unpack(root, "{s:i}", "json_schema_version", &schema_version)) + return 0; + + if (schema_version > JSON_SCHEMA_VERSION) { + json_error(ctx, "Schema version %d not supported, maximum supported version is %d\n", + schema_version, JSON_SCHEMA_VERSION); + return 1; + } + + return 0; +} + static int __json_parse(struct json_ctx *ctx, json_t *root) { struct eval_ctx ectx = { @@ -3084,11 +3100,22 @@ static int __json_parse(struct json_ctx *ctx, json_t *root) /* this is more or less from parser_bison.y:716 */ LIST_HEAD(list); struct cmd *cmd; + json_t *tmp2; if (!json_is_object(value)) { json_error(ctx, "Unexpected command array element of type %s, expected object.", json_typename(value)); return -1; } + + tmp2 = json_object_get(value, "metainfo"); + if (tmp2) { + if (json_verify_metainfo(ctx, tmp2)) { + json_error(ctx, "Metainfo verification failed."); + return -1; + } + continue; + } + cmd = json_parse_cmd(ctx, value); if (!cmd) { -- 2.18.0