[v2,2/2] batctl: Add JSON debug commands

Message ID 20210507171435.370648-1-asarmanow@gmail.com (mailing list archive)
State Superseded, archived
Delegated to: Simon Wunderlich
Headers
Series [v2,1/2] batctl: Add generic JSON interface |

Commit Message

Alexander Sarmanow May 7, 2021, 5:14 p.m. UTC
  According to part 1 of this patch following commands will be added to
the JSON debug: meshinfo_json, originators_json, neighbors_json,
translocal_json, transglobal_json.

Signed-off-by: Alexander Sarmanow <asarmanow@gmail.com>
---
 Makefile           |   5 ++
 gateways.c         |   2 +-
 main.c             |  14 +++--
 main.h             |   1 +
 mcast_flags.c      |   2 +-
 meshinfo_json.c    | 139 +++++++++++++++++++++++++++++++++++++++++++++
 neighbors_json.c   |  89 +++++++++++++++++++++++++++++
 netlink.c          |  39 ++++++++-----
 netlink.h          |   2 +-
 originators.c      |   2 +-
 originators_json.c | 107 ++++++++++++++++++++++++++++++++++
 transglobal_json.c | 109 +++++++++++++++++++++++++++++++++++
 translocal_json.c  | 102 +++++++++++++++++++++++++++++++++
 13 files changed, 592 insertions(+), 21 deletions(-)
 create mode 100644 meshinfo_json.c
 create mode 100644 neighbors_json.c
 create mode 100644 originators_json.c
 create mode 100644 transglobal_json.c
 create mode 100644 translocal_json.c
  

Comments

Sven Eckelmann May 7, 2021, 6:58 p.m. UTC | #1
On Friday, 7 May 2021 19:14:35 CEST Alexander Sarmanow wrote:
> According to part 1 of this patch following commands will be added to
> the JSON debug: meshinfo_json, originators_json, neighbors_json,
> translocal_json, transglobal_json.

I've asked you to split this up into helper code and one commit per command. 
And also add the documentation to the manpage and README.rst in each commit 
which adds/changes a command.

Why are you only printing selected attributes and not all?

Kind regards,
	Sven
  
Sven Eckelmann May 7, 2021, 7:08 p.m. UTC | #2
On Friday, 7 May 2021 19:14:35 CEST Alexander Sarmanow wrote:
> --- a/netlink.c
> +++ b/netlink.c
> @@ -363,17 +363,21 @@ static int info_callback(struct nl_msg *msg, void *arg)
>                 else
>                         extra_header = "";
>  
> -               ret = asprintf(&opts->remaining_header,
> -                              "[B.A.T.M.A.N. adv %s, MainIF/MAC: %s/%02x:%02x:%02x:%02x:%02x:%02x (%s/%02x:%02x:%02x:%02x:%02x:%02x %s)%s]\n%s",
> -                              version, primary_if,
> -                              primary_mac[0], primary_mac[1], primary_mac[2],
> -                              primary_mac[3], primary_mac[4], primary_mac[5],
> -                              mesh_name,
> -                              mesh_mac[0], mesh_mac[1], mesh_mac[2],
> -                              mesh_mac[3], mesh_mac[4], mesh_mac[5],
> -                              algo_name, extra_info, extra_header);
> -               if (ret < 0)
> -                       opts->remaining_header = NULL;
> +               if (!opts->is_json) {
> +                       ret = asprintf(&opts->remaining_header,
> +                                      "[B.A.T.M.A.N. adv %s, MainIF/MAC: %s/%02x:%02x:%02x:%02x:%02x:%02x (%s/%02x:%02x:%02x:%02x:%02x:%02x %s)%s]\n%s",
> +                                      version, primary_if,
> +                                      primary_mac[0], primary_mac[1],
> +                                      primary_mac[2], primary_mac[3],
> +                                      primary_mac[4], primary_mac[5],
> +                                      mesh_name,
> +                                      mesh_mac[0], mesh_mac[1], mesh_mac[2],
> +                                      mesh_mac[3], mesh_mac[4], mesh_mac[5],
> +                                      algo_name, extra_info, extra_header);
> +
> +                       if (ret < 0)
> +                               opts->remaining_header = NULL;
> +               }

This is rather bad. You've already queried stuff before. Just make sure that 
you set the  SKIP_HEADER flag and get rid of this is_json.

Kind regards,
	Sven
  
Alexander Sarmanow May 10, 2021, 7:58 a.m. UTC | #3
Sven Eckelmann wrote:

> Why are you only printing selected attributes and not all?
I wanted to reduce the amount to query for the netlink_print_json_entries function. There are only a few of the attributes needed for each of the JSON commands. 

Best regards,
Alex
  
Sven Eckelmann May 10, 2021, 10:14 a.m. UTC | #4
On Monday, 10 May 2021 09:58:26 CEST asarmanow@gmail.com wrote:
> Sven Eckelmann wrote:
> 
> > Why are you only printing selected attributes and not all?
> I wanted to reduce the amount to query for the netlink_print_json_entries function. There are only a few of the attributes needed for each of the JSON commands. 

You are doing currently two queries (one BATADV_CMD_GET_MESH_INFO and one for 
the actual data). I want to you to get rid of the BATADV_CMD_GET_MESH_INFO and 
print all data which was returned for the actual data query.

So I don't see a reason why you're approach would help here at all. Just go 
with netlink_print_json_entries through all entries in attrs till 
BATADV_ATTR_MAX and check if you have a function to print them. If you have, 
just print it.

You can then also get rid of missing_mandatory_attrs and also extra checks 
like MULTICAST_ONLY/UNICAST_ONLY/NO_OLD_ORIGS. The json is not for human 
consumption and thus we don't need special flags to prefilter the data. The 
consumer can take care of processing the data.

Btw. I am also more of a fan of following style of struct/array 
initialization. 

    struct nla_policy_json batadv_netlink_policy_json[NUM_BATADV_ATTR] = {
    	[BATADV_ATTR_VERSION] = {
    		.name = "version",
    		.cb = nljson_print_str
    	},
    	[.....]

I have to change this at some point in netlink.c

> +void create_json_print_string(const char *str, const char **str_ret)
> +{
> +       size_t needed = snprintf(NULL, 0, "\"%s\"", str);
> +       char  *str_buf = malloc(needed+1);
> +
> +       sprintf(str_buf, "\"%s\"", str);
> +
> +       *str_ret = str_buf;
> +}

Another thing I saw: you allocate a lot of strings but never free them. Can we 
just avoid to allocate them?

Other than that - why? This function doesn't make a lot of sense in the first 
place.

> +       [BATADV_ATTR_FLAG_BEST]                 = { .name = "best",
> +                                                   .cb = nljson_print_bool},


Also: BATADV_ATTR_FLAG_BEST is not a boolean - it is a flag. So it can be 
present and absent. So is comparable to an HTML attribute without value (for 
example "disabled"). I think we can encode it as "best": true when it exists.

> +void nljson_print_ttflags(struct json_opts *json_opts, struct print_opts *opts,
> +                         struct nlattr *attrs[], int idx)
> +{
> +       const char *key_ls;
> +       uint32_t flags;
> +       char r, n, x, w, i, p, t;
> +
> +       flags = nla_get_u32(attrs[idx]);
> +
> +       r = '.', p = '.', n = '.', x = '.', w = '.', i = '.', t = '.';
> +
> +       if (opts->nl_cmd == BATADV_CMD_GET_TRANSTABLE_LOCAL) {
[...]
> +       } else {
> +               if (flags & BATADV_TT_CLIENT_ROAM)
> +                       r = 'R';
> +               if (flags & BATADV_TT_CLIENT_WIFI)
> +                       w = 'W';
> +               if (flags & BATADV_TT_CLIENT_ISOLA)
> +                       i = 'I';
> +               if (flags & BATADV_TT_CLIENT_TEMP)
> +                       t = 'T';
> +
> +               goto print_global_table;
> +       }
> +
> +print_local_table:
> +       printf("\"%c%c%c%c%c%c\"", r, n, x, w, i, p);
> +
> +       printf(",");
> +       create_json_print_string(batadv_netlink_policy_json[BATADV_ATTR_LAST_SEEN_MSECS].name,
> +                                &key_ls);
> +       sanitize_string(key_ls, strlen(key_ls));
> +       printf(":");
> +       nljson_print_int(json_opts, opts, attrs, BATADV_ATTR_LAST_SEEN_MSECS);
> +
> +       return;
> +
> +print_global_table:
> +       printf("\"%c%c%c%c\"", r, w, i, t);
> +}



nljson_print_ttflags is also odd. If you decode the binary, why are you 
creating a compact representation again? Why aren't you just create a sub 
object with the parsed data? And there is no need to differentiate between 
BATADV_CMD_GET_TRANSTABLE_LOCAL and BATADV_CMD_GET_TRANSTABLE_GLOBAL.

And why is there a goto in the first place at the end of each scope?


And you interpretation of BATADV_ATTR_LAST_SEEN_MSECS seems to be really 
messed up. Just don't print BATADV_ATTR_LAST_SEEN_MSECS when it is not there - 
which can be evaluated automatically. And just as info: the kernel will not 
send it when the nopurge bit is set. So it is absolutely not what you've 
coded here.

> +void nljson_print_crc32(struct json_opts *json_opts, struct print_opts *opts,
> +                     struct nlattr *attrs[], int idx)
> +{
> +       (void) json_opts;
> +       (void) opts;
> +       int32_t value = nla_get_u32(attrs[idx]);
> +       printf("\"0x%.8x\"", value);
> +}

Can we just print the raw 32 bit unsigned integer?

Also, please not the name but the actual number in places were you are now 
calling nljson_print_ifname_by_ifindex. Otherwise you would print the name 
twice and make unnecessary syscalls. And if you want to print the actual name 
for things like the originator/neighbor dump, maybe just add it to the generic 
netlink message in the kernel.


> +void netlink_print_json_entries(struct nlattr *attrs[], int selected_attrs[],
> +                               int arr_len, struct print_opts *opts)
> +{
> +       const char *name;
> +       uint8_t first_valid_attr = 1;
> +       int idx, i;
> +       struct json_opts json_opts = {
> +               .use_alt_int_val = 0,
> +               .alt_int_val = 0,
> +       };
> +
> +
> +       if (!opts->is_first)
> +               printf(",");
> +
> +       for (i = 0; i < arr_len; i++) {
> +               idx = selected_attrs[i];
> +
> +               if (attrs[idx] && batadv_netlink_policy_json[idx].cb) {
> +                       if (!first_valid_attr)
> +                               printf(",");
> +                       else
> +                               printf("{");
> +
> +                       create_json_print_string(batadv_netlink_policy_json[idx].name,
> +                                                &name);
> +                       sanitize_string(name, strlen(name));
> +                       printf(":");
> +                       batadv_netlink_policy_json[idx].cb(&json_opts, opts,
> +                                                          attrs, idx);
> +
> +                       first_valid_attr = 0;
> +               }
> +       }
> +       if (!first_valid_attr)
> +               printf("}");
> +}


Why aren't you handling the is_first = 0 in this function? And why aren't you 
printing empty objects?

And please try to handle rejections and not acceptance. So instead of:

           for (i = 0; i < arr_len; i++) {
                   idx = selected_attrs[i];
    
                   if (attrs[idx] && batadv_netlink_policy_json[idx].cb) {

Maybe something like:

           for (i = 0; i < arr_len; i++) {
                   [...]    
                   if (!attrs[idx])
                        continue;
    
                   if (!batadv_netlink_policy_json[idx].cb)
                        continue;

> +void sanitize_string(const char *str, int str_len)
> +{
> +       int i;
> +
> +       for (i = 0; i < str_len; i++) {
> +               if (str[i] == '"')
> +                       printf("\"");
> +               else if (str[i] == '\\')
> +                       printf("\\\\");
> +               else if (!isprint(str[i]))
> +                       printf("\\x%02x", str[i]);
> +               else
> +                       printf("%c", str[i]);
> +       }
> +}

I know, it is the similar in alfred but maybe you can change it to something 
like:

    void sanitize_string(const char *str)
    {
           while (*str) {
                   if (*str == '"')
                           puts("\"");
                   else if (*str == '\\')
                           puts("\\\\");
                   else if (!isprint(*str))
                           printf("\\x%02x", *str);
                   else
                           putc(*str);

                   str++;
          }
   }

> +static int meshinfo_callback(struct nl_msg *msg, void *arg)
> {
[...]
> +       int selected_attrs[10] = { BATADV_ATTR_MESH_IFNAME,
> +                                  BATADV_ATTR_MESH_ADDRESS,
> +                                  BATADV_ATTR_HARD_IFNAME,
> +                                  BATADV_ATTR_VERSION,
> +                                  BATADV_ATTR_ALGO_NAME,
> +                                  BATADV_ATTR_HARD_ADDRESS,
> +                                  BATADV_ATTR_TT_TTVN };

I hope to get rid of this anyway but this should have been:

	static const enum batadv_nl_attrs selected_attrs[] = {
		BATADV_ATTR_MESH_IFNAME,
		BATADV_ATTR_MESH_ADDRESS,
		BATADV_ATTR_HARD_IFNAME,
		BATADV_ATTR_VERSION,
		BATADV_ATTR_ALGO_NAME,
		BATADV_ATTR_HARD_ADDRESS,
		BATADV_ATTR_TT_TTVN,
	}

So no hardcoded (and wrong) length, correct type, constant, not allocated on 
the stack, different identation.


> +static int netlink_print_meshinfo_json(struct state *state, char *orig_iface,
> +                                      int read_opts, float orig_timeout,
> +                                      float watch_interval)
> +{
> +       (void) orig_iface;
> +       (void) orig_timeout;
> +       (void) watch_interval;
> +               (void) read_opts;

Please annotate unused parameters correctly with __maybe_unused

Also use the already existing netlink socket and don't create a new one. Maybe 
even with sys_simple_nlquery.
Most functionality which creates its own batadv generic netlink socket only 
does it because it needed to support the old sysfs functionality in parallel 
in the past. And no on found the time to clean this up after the sysfs stuff 
was dropped.

If we don't need the "fancy" features of the debug tables then we can also use 
the functionality sys_simple_nlquery for the rest. At least we don't have
the bat-host handling anymore, header is dropped and the filter might be 
dropped. And the only reason why I was against the special code to prevent
the "watch" functionality was that we need to introduce a new hack for it.

Just make sure that you allow the caller to change the 6th parameter of 
genlmsg_put. Maybe by introducing a new function which allows to set this 
parameter. And the sys_simple_nlquery is changed to a wrapper which omits the 
new "flags" parameter.

And then you can also add the putc for '[' and ']' directly to the caller of 
sys_simple_nlquery (or whatever the new function will be called).

> +#define BOOL_STRING(x) (x ? "true" : "false")
> +

I don't see a lot of benefit in this macro.

> + * Andrew Lunn <andrew(a)lunn.ch&gt;
> + * Sven Eckelmann <sven(a)narfation.org&gt;
> + * Alexander Sarmanow <asarmanow(a)gmail.com&gt;

No fancy html tags in headers please.



Overall: please run you patches through Linux's scripts/checkpatch.pl --strict 
and check what of the things you see is only relevant for the kernel and what 
might be actual things you should clean up.


> +       [BATADV_ATTR_MESH_IFNAME]               = { .name = "mesh_if",
> +                                                   .cb = nljson_print_str },
> +       [BATADV_ATTR_MESH_ADDRESS]              = { .name = "mesh_address",
> +                                                   .cb = nljson_print_mac},
> +       [BATADV_ATTR_ORIG_ADDRESS]              = { .name = "originator",
> +                                                   .cb = nljson_print_mac },


Please keep the same order as the attributes in batman_adv.h


Missing print functions:

* BATADV_ATTR_TPMETER_RESULT
* BATADV_ATTR_TPMETER_TEST_TIME
* BATADV_ATTR_TPMETER_BYTES
* BATADV_ATTR_TPMETER_COOKIE
* BATADV_ATTR_ACTIVE
* BATADV_ATTR_BANDWIDTH_UP
* BATADV_ATTR_BANDWIDTH_DOWN
* BATADV_ATTR_ROUTER
* BATADV_ATTR_BLA_OWN
* BATADV_ATTR_BLA_ADDRESS
* BATADV_ATTR_BLA_VID
* BATADV_ATTR_BLA_BACKBONE
* BATADV_ATTR_DAT_CACHE_IP4ADDRESS
* BATADV_ATTR_DAT_CACHE_HWADDRESS
* BATADV_ATTR_DAT_CACHE_VID
* BATADV_ATTR_MCAST_FLAGS
* BATADV_ATTR_MCAST_FLAGS_PRIV
* BATADV_ATTR_VLANID
* BATADV_ATTR_AGGREGATED_OGMS_ENABLED
* BATADV_ATTR_AP_ISOLATION_ENABLED
* BATADV_ATTR_ISOLATION_MARK
* BATADV_ATTR_ISOLATION_MASK
* BATADV_ATTR_BONDING_ENABLED
* BATADV_ATTR_BRIDGE_LOOP_AVOIDANCE_ENABLED
* BATADV_ATTR_DISTRIBUTED_ARP_TABLE_ENABLED
* BATADV_ATTR_FRAGMENTATION_ENABLED
* BATADV_ATTR_GW_BANDWIDTH_DOWN
* BATADV_ATTR_GW_BANDWIDTH_UP
* BATADV_ATTR_GW_MODE
* BATADV_ATTR_GW_SEL_CLASS
* BATADV_ATTR_HOP_PENALTY
* BATADV_ATTR_LOG_LEVEL
* BATADV_ATTR_MULTICAST_FORCEFLOOD_ENABLED
* BATADV_ATTR_NETWORK_CODING_ENABLED
* BATADV_ATTR_ORIG_INTERVAL
* BATADV_ATTR_ELP_INTERVAL
* BATADV_ATTR_THROUGHPUT_OVERRIDE
* BATADV_ATTR_MULTICAST_FANOUT


> +       [BATADV_ATTR_MESH_IFINDEX]              = { .name = "mesh_if_idx",

meshif_idx or mesh_ifindex

> +       [BATADV_ATTR_MESH_IFNAME]               = { .name = "mesh_if",

meshif or mesh_ifname

> +       [BATADV_ATTR_HARD_IFINDEX]              = { .name = "hard_if_idx",

hardif_idx or hard_ifindex

> +       [BATADV_ATTR_HARD_IFNAME]               = { .name = "hard_if",

hardif or hard_ifname

> +       [BATADV_ATTR_TT_ADDRESS]                = { .name = "client",

why client? It is not really a  client.

tt_address

> +       [BATADV_ATTR_TT_VID]                    = { .name = "vid",

tt_vid

> +       [BATADV_ATTR_TT_FLAGS]                  = { .name = "flags",

tt_flags


> +       [BATADV_ATTR_LAST_SEEN_MSECS]           = { .name = "last_seen",

last_seen_msecs

> +       [BATADV_ATTR_NEIGH_ADDRESS]             = { .name = "neighbor",

neigh_address

> @@ -21,8 +21,22 @@ struct print_opts {
>         float watch_interval;
>         nl_recvmsg_msg_cb_t callback;
>         char *remaining_header;
> +       char *remaining_entry;
>         const char *static_header;
>         uint8_t nl_cmd;
> +       uint8_t is_json;
> +       uint8_t is_first;
> +};

     uint8_t is_json:1
     uint8_t is_first:1

And what is remaining_entry?

Kind regards,
	Sven
  
Sven Eckelmann May 10, 2021, 2:22 p.m. UTC | #5
On Monday, 10 May 2021 12:14:10 CEST Sven Eckelmann wrote:
>     void sanitize_string(const char *str)
>     {
>            while (*str) {
>                    if (*str == '"')
>                            puts("\"");
>                    else if (*str == '\\')
>                            puts("\\\\");
>                    else if (!isprint(*str))
>                            printf("\\x%02x", *str);
>                    else
>                            putc(*str);
> 
>                    str++;
>           }
>    }

Should have tested this. It should be more like:

    void sanitize_string(const char *str)
    {
    	while (*str) {
    		if (*str == '"' || *str == '\\') {
    			putchar('\\');
    			putchar(*str);
    		} else if (!isprint(*str)) {
    			printf("\\x%02x", *str);
    		} else {
    			putchar(*str);
    		}
    	}
    }

Kind regards,
	Sven
  

Patch

diff --git a/Makefile b/Makefile
index 98bf695..0f85561 100755
--- a/Makefile
+++ b/Makefile
@@ -54,13 +54,16 @@  $(eval $(call add_command,interface,y))
 $(eval $(call add_command,isolation_mark,y))
 $(eval $(call add_command,loglevel,y))
 $(eval $(call add_command,mcast_flags,y))
+$(eval $(call add_command,meshinfo_json,y))
 $(eval $(call add_command,multicast_fanout,y))
 $(eval $(call add_command,multicast_forceflood,y))
 $(eval $(call add_command,multicast_mode,y))
 $(eval $(call add_command,neighbors,y))
+$(eval $(call add_command,neighbors_json,y))
 $(eval $(call add_command,network_coding,y))
 $(eval $(call add_command,orig_interval,y))
 $(eval $(call add_command,originators,y))
+$(eval $(call add_command,originators_json,y))
 $(eval $(call add_command,ping,y))
 $(eval $(call add_command,routing_algo,y))
 $(eval $(call add_command,statistics,y))
@@ -69,8 +72,10 @@  $(eval $(call add_command,throughput_override,y))
 $(eval $(call add_command,throughputmeter,y))
 $(eval $(call add_command,traceroute,y))
 $(eval $(call add_command,transglobal,y))
+$(eval $(call add_command,transglobal_json,y))
 $(eval $(call add_command,translate,y))
 $(eval $(call add_command,translocal,y))
+$(eval $(call add_command,translocal_json,y))
 
 MANPAGE = man/batctl.8
 
diff --git a/gateways.c b/gateways.c
index 867c882..3704c99 100644
--- a/gateways.c
+++ b/gateways.c
@@ -123,7 +123,7 @@  static int netlink_print_gateways(struct state *state, char *orig_iface,
 	/* only parse routing algorithm name */
 	last_err = -EINVAL;
 	info_header = netlink_get_info(state->mesh_ifindex,
-				       BATADV_CMD_GET_ORIGINATORS, NULL);
+				       BATADV_CMD_GET_ORIGINATORS, NULL, 0);
 	free(info_header);
 
 	if (strlen(algo_name_buf) == 0)
diff --git a/main.c b/main.c
index d9b63f3..1371bc0 100644
--- a/main.c
+++ b/main.c
@@ -43,6 +43,10 @@  static void print_usage(void)
 			.label = "debug tables:                                   \tdisplay the corresponding debug table\n",
 			.types = BIT(DEBUGTABLE),
 		},
+		{
+			.label = "debug JSONs:                                   \tdisplay the corresponding debug JSON\n",
+			.types = BIT(DEBUGJSON),
+		},
 	};
 	const char *default_prefixes[] = {
 		"",
@@ -67,9 +71,9 @@  static void print_usage(void)
 	char buf[64];
 	size_t i;
 
-	fprintf(stderr, "Usage: batctl [options] command|debug table [parameters]\n");
+	fprintf(stderr, "Usage: batctl [options] command|debug table|debug json [parameters]\n");
 	fprintf(stderr, "options:\n");
-	fprintf(stderr, " \t-h print this help (or 'batctl <command|debug table> -h' for the parameter help)\n");
+	fprintf(stderr, " \t-h print this help (or 'batctl <command|debug table|debug json> -h' for the parameter help)\n");
 	fprintf(stderr, " \t-v print version\n");
 
 	for (i = 0; i < sizeof(type) / sizeof(*type); i++) {
@@ -87,6 +91,7 @@  static void print_usage(void)
 				continue;
 
 			switch (cmd->type) {
+			case DEBUGJSON:
 			case DEBUGTABLE:
 			case SUBCOMMAND_MIF:
 				prefixes = meshif_prefixes;
@@ -167,7 +172,8 @@  static const struct command *find_command(struct state *state, const char *name)
 		/* fall through */
 	case SP_MESHIF:
 		types |= BIT(SUBCOMMAND_MIF) |
-			 BIT(DEBUGTABLE);
+			 BIT(DEBUGTABLE)     |
+			 BIT(DEBUGJSON);
 		break;
 	case SP_VLAN:
 		types = BIT(SUBCOMMAND_VID);
@@ -380,7 +386,7 @@  int main(int argc, char **argv)
 	cmd = find_command(&state, argv[0]);
 	if (!cmd) {
 		fprintf(stderr,
-			"Error - no valid command or debug table specified: %s\n",
+			"Error - no valid command or debug table/JSON specified: %s\n",
 			argv[0]);
 		goto err;
 	}
diff --git a/main.h b/main.h
index b1ff050..2efd136 100644
--- a/main.h
+++ b/main.h
@@ -71,6 +71,7 @@  enum command_type {
 	SUBCOMMAND_VID,
 	SUBCOMMAND_HIF,
 	DEBUGTABLE,
+	DEBUGJSON,
 };
 
 struct state {
diff --git a/mcast_flags.c b/mcast_flags.c
index cb6e89d..87fb077 100644
--- a/mcast_flags.c
+++ b/mcast_flags.c
@@ -105,7 +105,7 @@  static int netlink_print_mcast_flags(struct state *state, char *orig_iface,
 
 	/* only parse own multicast flags */
 	info_header = netlink_get_info(state->mesh_ifindex,
-				       BATADV_CMD_GET_MCAST_FLAGS, NULL);
+				       BATADV_CMD_GET_MCAST_FLAGS, NULL, 0);
 	free(info_header);
 
 	if (mcast_flags == -EOPNOTSUPP || mcast_flags_priv == -EOPNOTSUPP)
diff --git a/meshinfo_json.c b/meshinfo_json.c
new file mode 100644
index 0000000..8c6f675
--- /dev/null
+++ b/meshinfo_json.c
@@ -0,0 +1,139 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) B.A.T.M.A.N. contributors:
+ *
+ * Alexander Sarmanow <asarmanow@gmail.com>
+ *
+ * License-Filename: LICENSES/preferred/GPL-2.0
+ */
+
+#include <errno.h>
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "batman_adv.h"
+#include "debug.h"
+#include "main.h"
+#include "netlink.h"
+
+static const int info_mandatory[] = {
+	BATADV_ATTR_MESH_IFINDEX,
+	BATADV_ATTR_MESH_IFNAME,
+};
+
+static int meshinfo_callback(struct nl_msg *msg, void *arg)
+{
+	struct print_opts *opts = arg;
+	struct nlattr *attrs[BATADV_ATTR_MAX+1];
+	struct nlmsghdr *nlh = nlmsg_hdr(msg);
+	struct genlmsghdr *ghdr;
+	int selected_attrs[10] = { BATADV_ATTR_MESH_IFNAME,
+				   BATADV_ATTR_MESH_ADDRESS,
+				   BATADV_ATTR_HARD_IFNAME,
+				   BATADV_ATTR_VERSION,
+				   BATADV_ATTR_ALGO_NAME,
+				   BATADV_ATTR_HARD_ADDRESS,
+				   BATADV_ATTR_TT_TTVN };
+
+	if (!genlmsg_valid_hdr(nlh, 0)) {
+		fputs("Received invalid data from kernel.\n", stderr);
+		exit(1);
+	}
+
+	ghdr = nlmsg_data(nlh);
+
+	if (ghdr->cmd != BATADV_CMD_GET_MESH_INFO)
+		return NL_OK;
+	if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0),
+		      genlmsg_len(ghdr), batadv_netlink_policy)) {
+		fputs("Received invalid data from kernel.\n", stderr);
+		exit(1);
+	}
+
+	if (missing_mandatory_attrs(attrs, info_mandatory,
+				    ARRAY_SIZE(info_mandatory))) {
+		fputs("Missing attributes from kernel\n", stderr);
+		exit(1);
+	}
+
+	netlink_print_json_entries(attrs, selected_attrs,
+				   ARRAY_SIZE(selected_attrs), opts);
+	opts->is_first = 0;
+
+	return NL_OK;
+}
+
+static int netlink_print_meshinfo_json(struct state *state, char *orig_iface,
+				       int read_opts, float orig_timeout,
+				       float watch_interval)
+{
+	(void) orig_iface;
+	(void) orig_timeout;
+	(void) watch_interval;
+       	(void) read_opts;
+	struct nl_sock *sock;
+	struct nl_msg *msg;
+	struct nl_cb *cb;
+	struct print_opts opts = {
+		.is_json = 1,
+		.is_first = 1,
+	};
+	int family;
+
+	if (!state->sock) {
+		last_err = -EOPNOTSUPP;
+		return last_err;
+	}
+
+	sock = nl_socket_alloc();
+	if (!sock)
+		return -1;
+
+	genl_connect(sock);
+
+	family = genl_ctrl_resolve(sock, BATADV_NL_NAME);
+	if (family < 0) {
+		nl_socket_free(sock);
+		return -1;
+	}
+
+	msg = nlmsg_alloc();
+	if (!msg) {
+		nl_socket_free(sock);
+		return -1;
+	}
+
+	genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, 0,
+		    BATADV_CMD_GET_MESH_INFO, 1);
+
+	nla_put_u32(msg, BATADV_ATTR_MESH_IFINDEX, state->mesh_ifindex);
+
+	nl_send_auto_complete(sock, msg);
+
+	nlmsg_free(msg);
+
+	cb = nl_cb_alloc(NL_CB_DEFAULT);
+	if (!cb)
+		goto err_free_sock;
+
+	nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, meshinfo_callback, &opts);
+	nl_cb_err(cb, NL_CB_CUSTOM, netlink_print_error, NULL);
+
+	nl_recvmsgs(sock, cb);
+
+err_free_sock:
+	nl_socket_free(sock);
+
+	return 0;
+}
+
+static struct debug_table_data batctl_debug_json_meshinfo = {
+	.netlink_fn = netlink_print_meshinfo_json,
+};
+
+COMMAND_NAMED(DEBUGJSON, meshinfo_json, "mij", handle_debug_table,
+	      COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK,
+	      &batctl_debug_json_meshinfo, "");
diff --git a/neighbors_json.c b/neighbors_json.c
new file mode 100644
index 0000000..5182e47
--- /dev/null
+++ b/neighbors_json.c
@@ -0,0 +1,89 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) B.A.T.M.A.N. contributors:
+ *
+ * Andrew Lunn <andrew@lunn.ch>
+ * Alexander Sarmanow <asarmanow@gmail.com>
+ *
+ * License-Filename: LICENSES/preferred/GPL-2.0
+ */
+
+#include <net/if.h>
+#include <netinet/if_ether.h>
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "batadv_packet.h"
+#include "batman_adv.h"
+#include "bat-hosts.h"
+#include "debug.h"
+#include "functions.h"
+#include "main.h"
+#include "netlink.h"
+
+static const int neighbors_mandatory[] = {
+	BATADV_ATTR_NEIGH_ADDRESS,
+	BATADV_ATTR_HARD_IFINDEX,
+	BATADV_ATTR_LAST_SEEN_MSECS,
+};
+
+static int neighbors_json_callback(struct nl_msg *msg, void *arg)
+{
+	struct nlattr *attrs[BATADV_ATTR_MAX+1];
+	struct nlmsghdr *nlh = nlmsg_hdr(msg);
+	struct print_opts *opts = arg;
+	struct genlmsghdr *ghdr;
+	int selected_attrs[4] = { BATADV_ATTR_NEIGH_ADDRESS,
+				  BATADV_ATTR_HARD_IFINDEX,
+				  BATADV_ATTR_LAST_SEEN_MSECS,
+				  BATADV_ATTR_THROUGHPUT };
+
+	if (!genlmsg_valid_hdr(nlh, 0)) {
+		fputs("Received invalid data from kernel.\n", stderr);
+		exit(1);
+	}
+
+	ghdr = nlmsg_data(nlh);
+
+	if (ghdr->cmd != BATADV_CMD_GET_NEIGHBORS)
+		return NL_OK;
+
+	if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0),
+		      genlmsg_len(ghdr), batadv_netlink_policy)) {
+		fputs("Received invalid data from kernel.\n", stderr);
+		exit(1);
+	}
+
+	if (missing_mandatory_attrs(attrs, neighbors_mandatory,
+				    ARRAY_SIZE(neighbors_mandatory))) {
+		fputs("Missing attributes from kernel\n", stderr);
+		exit(1);
+	}
+
+	netlink_print_json_entries(attrs, selected_attrs,
+				   ARRAY_SIZE(selected_attrs), opts);
+	opts->is_first = 0;
+
+	return NL_OK;
+}
+
+static int netlink_print_neighbors_json(struct state *state, char *orig_iface,
+					int read_opts, float orig_timeout,
+					float watch_interval)
+{
+	return netlink_print_common(state, orig_iface, read_opts,
+				    orig_timeout, watch_interval, NULL,
+				    BATADV_CMD_GET_NEIGHBORS,
+				    neighbors_json_callback, true);
+}
+
+static struct debug_table_data batctl_debug_json_neighbors = {
+	.netlink_fn = netlink_print_neighbors_json,
+};
+
+COMMAND_NAMED(DEBUGJSON, neighbors_json, "nj", handle_debug_table,
+	      COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK,
+	      &batctl_debug_json_neighbors, "");
diff --git a/netlink.c b/netlink.c
index 26ae5ef..db44d82 100644
--- a/netlink.c
+++ b/netlink.c
@@ -363,17 +363,21 @@  static int info_callback(struct nl_msg *msg, void *arg)
 		else
 			extra_header = "";
 
-		ret = asprintf(&opts->remaining_header,
-			       "[B.A.T.M.A.N. adv %s, MainIF/MAC: %s/%02x:%02x:%02x:%02x:%02x:%02x (%s/%02x:%02x:%02x:%02x:%02x:%02x %s)%s]\n%s",
-			       version, primary_if,
-			       primary_mac[0], primary_mac[1], primary_mac[2],
-			       primary_mac[3], primary_mac[4], primary_mac[5],
-			       mesh_name,
-			       mesh_mac[0], mesh_mac[1], mesh_mac[2],
-			       mesh_mac[3], mesh_mac[4], mesh_mac[5],
-			       algo_name, extra_info, extra_header);
-		if (ret < 0)
-			opts->remaining_header = NULL;
+		if (!opts->is_json) {
+			ret = asprintf(&opts->remaining_header,
+				       "[B.A.T.M.A.N. adv %s, MainIF/MAC: %s/%02x:%02x:%02x:%02x:%02x:%02x (%s/%02x:%02x:%02x:%02x:%02x:%02x %s)%s]\n%s",
+				       version, primary_if,
+				       primary_mac[0], primary_mac[1],
+				       primary_mac[2], primary_mac[3],
+				       primary_mac[4], primary_mac[5],
+				       mesh_name,
+				       mesh_mac[0], mesh_mac[1], mesh_mac[2],
+				       mesh_mac[3], mesh_mac[4], mesh_mac[5],
+				       algo_name, extra_info, extra_header);
+
+			if (ret < 0)
+				opts->remaining_header = NULL;
+		}
 
 		if (extra_info)
 			free(extra_info);
@@ -387,7 +391,8 @@  static int info_callback(struct nl_msg *msg, void *arg)
 	return NL_STOP;
 }
 
-char *netlink_get_info(int ifindex, uint8_t nl_cmd, const char *header)
+char *netlink_get_info(int ifindex, uint8_t nl_cmd, const char *header,
+		       uint8_t is_json)
 {
 	struct nl_sock *sock;
 	struct nl_msg *msg;
@@ -398,6 +403,7 @@  char *netlink_get_info(int ifindex, uint8_t nl_cmd, const char *header)
 		.nl_cmd = nl_cmd,
 		.remaining_header = NULL,
 		.static_header = header,
+		.is_json = is_json,
 	};
 
 	sock = nl_socket_alloc();
@@ -494,6 +500,9 @@  int netlink_print_common(struct state *state, char *orig_iface, int read_opt,
 		}
 	}
 
+	if (is_json)
+		printf("[");
+
 	bat_hosts_init(read_opt);
 
 	nl_cb_set(state->cb, NL_CB_VALID, NL_CB_CUSTOM, netlink_print_common_cb, &opts);
@@ -508,7 +517,8 @@  int netlink_print_common(struct state *state, char *orig_iface, int read_opt,
 		if (!(read_opt & SKIP_HEADER))
 			opts.remaining_header = netlink_get_info(state->mesh_ifindex,
 								 nl_cmd,
-								 header);
+								 header,
+								 is_json);
 
 		msg = nlmsg_alloc();
 		if (!msg)
@@ -529,6 +539,9 @@  int netlink_print_common(struct state *state, char *orig_iface, int read_opt,
 		last_err = 0;
 		nl_recvmsgs(state->sock, state->cb);
 
+		if (is_json)
+			printf("]");
+
 		/* the header should still be printed when no entry was received */
 		if (!last_err)
 			netlink_print_remaining_header(&opts);
diff --git a/netlink.h b/netlink.h
index c766741..47390fc 100644
--- a/netlink.h
+++ b/netlink.h
@@ -44,7 +44,7 @@  struct ether_addr;
 int netlink_create(struct state *state);
 void netlink_destroy(struct state *state);
 
-char *netlink_get_info(int ifindex, uint8_t nl_cmd, const char *header);
+char *netlink_get_info(int ifindex, uint8_t nl_cmd, const char *header, uint8_t is_json);
 int translate_mac_netlink(const char *mesh_iface, const struct ether_addr *mac,
 			  struct ether_addr *mac_out);
 int get_nexthop_netlink(const char *mesh_iface, const struct ether_addr *mac,
diff --git a/originators.c b/originators.c
index 674656c..c3af740 100644
--- a/originators.c
+++ b/originators.c
@@ -175,7 +175,7 @@  static int netlink_print_originators(struct state *state, char *orig_iface,
 	/* only parse routing algorithm name */
 	last_err = -EINVAL;
 	info_header = netlink_get_info(state->mesh_ifindex,
-				       BATADV_CMD_GET_ORIGINATORS, NULL);
+				       BATADV_CMD_GET_ORIGINATORS, NULL, 0);
 	free(info_header);
 
 	if (strlen(algo_name_buf) == 0)
diff --git a/originators_json.c b/originators_json.c
new file mode 100644
index 0000000..91dcb44
--- /dev/null
+++ b/originators_json.c
@@ -0,0 +1,107 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) B.A.T.M.A.N. contributors:
+ *
+ * Andrew Lunn <andrew@lunn.ch>
+ * Sven Eckelmann <sven@narfation.org>
+ * Alexander Sarmanow <asarmanow@gmail.com>
+ *
+ * License-Filename: LICENSES/preferred/GPL-2.0
+ */
+
+#include <errno.h>
+#include <net/if.h>
+#include <netinet/if_ether.h>
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "batadv_packet.h"
+#include "batman_adv.h"
+#include "bat-hosts.h"
+#include "debug.h"
+#include "functions.h"
+#include "main.h"
+#include "netlink.h"
+
+static const int originators_mandatory[] = {
+	BATADV_ATTR_ORIG_ADDRESS,
+	BATADV_ATTR_NEIGH_ADDRESS,
+	BATADV_ATTR_HARD_IFINDEX,
+	BATADV_ATTR_LAST_SEEN_MSECS,
+};
+
+static int originators_json_callback(struct nl_msg *msg, void *arg)
+{
+	struct nlattr *attrs[BATADV_ATTR_MAX+1];
+	struct nlmsghdr *nlh = nlmsg_hdr(msg);
+	int last_seen_msecs;
+	struct print_opts *opts = arg;
+	struct genlmsghdr *ghdr;
+	float last_seen;
+	int selected_attrs[6] = { BATADV_ATTR_ORIG_ADDRESS,
+				  BATADV_ATTR_NEIGH_ADDRESS,
+				  BATADV_ATTR_HARD_IFINDEX,
+				  BATADV_ATTR_LAST_SEEN_MSECS,
+				  BATADV_ATTR_THROUGHPUT,
+				  BATADV_ATTR_TQ };
+
+	if (!genlmsg_valid_hdr(nlh, 0)) {
+		fputs("Received invalid data from kernel.\n", stderr);
+		exit(1);
+	}
+
+	ghdr = nlmsg_data(nlh);
+
+	if (ghdr->cmd != BATADV_CMD_GET_ORIGINATORS)
+		return NL_OK;
+
+	if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0),
+		      genlmsg_len(ghdr), batadv_netlink_policy)) {
+		fputs("Received invalid data from kernel.\n", stderr);
+		exit(1);
+	}
+
+	if (missing_mandatory_attrs(attrs, originators_mandatory,
+				       ARRAY_SIZE(originators_mandatory))) {
+		fputs("Missing attributes from kernel\n", stderr);
+		exit(1);
+	}
+
+	last_seen_msecs = nla_get_u32(attrs[BATADV_ATTR_LAST_SEEN_MSECS]);
+	last_seen = (float)last_seen_msecs / 1000.0;
+
+	/* skip timed out originators */
+	if (opts->read_opt & NO_OLD_ORIGS)
+		if (last_seen > opts->orig_timeout)
+			return NL_OK;
+
+	netlink_print_json_entries(attrs, selected_attrs,
+				   ARRAY_SIZE(selected_attrs), opts);
+	opts->is_first = 0;
+
+	return NL_OK;
+}
+
+static int netlink_print_originators_json(struct state *state, char *orig_iface,
+					  int read_opts, float orig_timeout,
+					  float watch_interval)
+{
+	return netlink_print_common(state, orig_iface, read_opts,
+				    orig_timeout, watch_interval, NULL,
+				    BATADV_CMD_GET_ORIGINATORS,
+				    originators_json_callback, 1);
+}
+
+static struct debug_table_data batctl_debug_json_originators = {
+	.netlink_fn = netlink_print_originators_json,
+	.option_timeout_interval = 1,
+	.option_orig_iface = 1,
+};
+
+COMMAND_NAMED(DEBUGJSON, originators_json, "oj", handle_debug_table,
+	      COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK,
+	      &batctl_debug_json_originators, "");
diff --git a/transglobal_json.c b/transglobal_json.c
new file mode 100644
index 0000000..faff8ec
--- /dev/null
+++ b/transglobal_json.c
@@ -0,0 +1,109 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) B.A.T.M.A.N. contributors:
+ *
+ * Andrew Lunn <andrew(a)lunn.ch&gt;
+ * Sven Eckelmann <sven(a)narfation.org&gt;
+ * Alexander Sarmanow <asarmanow(a)gmail.com&gt;
+ *
+ * License-Filename: LICENSES/preferred/GPL-2.0
+ */
+
+#include <netinet/if_ether.h>
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "batadv_packet.h"
+#include "batman_adv.h"
+#include "bat-hosts.h"
+#include "debug.h"
+#include "functions.h"
+#include "main.h"
+#include "netlink.h"
+
+static const int transglobal_mandatory[] = {
+	BATADV_ATTR_TT_ADDRESS,
+	BATADV_ATTR_ORIG_ADDRESS,
+	BATADV_ATTR_TT_VID,
+	BATADV_ATTR_TT_TTVN,
+	BATADV_ATTR_TT_LAST_TTVN,
+	BATADV_ATTR_TT_CRC32,
+	BATADV_ATTR_TT_FLAGS,
+};
+
+static int transglobal_json_callback(struct nl_msg *msg, void *arg)
+{
+	struct nlattr *attrs[BATADV_ATTR_MAX+1];
+	struct nlmsghdr *nlh = nlmsg_hdr(msg);
+	struct print_opts *opts = arg;
+	struct genlmsghdr *ghdr;
+	uint8_t *addr;
+	int selected_attrs[8] = { BATADV_ATTR_TT_ADDRESS,
+				  BATADV_ATTR_ORIG_ADDRESS,
+			      	  BATADV_ATTR_TT_VID,
+			      	  BATADV_ATTR_TT_TTVN,
+			      	  BATADV_ATTR_TT_LAST_TTVN,
+			      	  BATADV_ATTR_TT_CRC32,
+			      	  BATADV_ATTR_TT_FLAGS,
+			      	  BATADV_ATTR_FLAG_BEST };
+
+	if (!genlmsg_valid_hdr(nlh, 0)) {
+		fputs("Received invalid data from kernel.\n", stderr);
+		exit(1);
+	}
+
+	ghdr = nlmsg_data(nlh);
+
+	if (ghdr->cmd != BATADV_CMD_GET_TRANSTABLE_GLOBAL)
+		return NL_OK;
+
+	if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0),
+		      genlmsg_len(ghdr), batadv_netlink_policy)) {
+		fputs("Received invalid data from kernel.\n", stderr);
+		exit(1);
+	}
+
+	if (missing_mandatory_attrs(attrs, transglobal_mandatory,
+				    ARRAY_SIZE(transglobal_mandatory))) {
+		fputs("Missing attributes from kernel\n", stderr);
+		exit(1);
+	}
+
+	addr = nla_data(attrs[BATADV_ATTR_TT_ADDRESS]);
+
+	if (opts->read_opt & MULTICAST_ONLY && !(addr[0] & 0x01))
+		return NL_OK;
+
+	if (opts->read_opt & UNICAST_ONLY && (addr[0] & 0x01))
+		return NL_OK;
+
+	netlink_print_json_entries(attrs, selected_attrs,
+				   ARRAY_SIZE(selected_attrs), opts);
+	opts->is_first = 0;
+
+
+	return NL_OK;
+}
+
+static int netlink_print_transglobal_json(struct state *state, char *orig_iface,
+					  int read_opts, float orig_timeout,
+					  float watch_interval)
+{
+	return netlink_print_common(state, orig_iface, read_opts,
+				    orig_timeout, watch_interval, NULL,
+				    BATADV_CMD_GET_TRANSTABLE_GLOBAL,
+				    transglobal_json_callback, true);
+}
+
+static struct debug_table_data batctl_debug_json_transglobal = {
+	.netlink_fn = netlink_print_transglobal_json,
+	.option_unicast_only = 1,
+	.option_multicast_only = 1,
+};
+
+COMMAND_NAMED(DEBUGJSON, transglobal_json, "tgj", handle_debug_table,
+	      COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK,
+	      &batctl_debug_json_transglobal, "");
diff --git a/translocal_json.c b/translocal_json.c
new file mode 100644
index 0000000..e78150e
--- /dev/null
+++ b/translocal_json.c
@@ -0,0 +1,102 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) B.A.T.M.A.N. contributors:
+ *
+ * Andrew Lunn <andrew@lunn.ch>
+ * Sven Eckelmann <sven@narfation.org>
+ * Alexander Sarmanow <asarmanow@gmail.com>
+ *
+ * License-Filename: LICENSES/preferred/GPL-2.0
+ */
+
+#include <netinet/if_ether.h>
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "batadv_packet.h"
+#include "batman_adv.h"
+#include "bat-hosts.h"
+#include "debug.h"
+#include "functions.h"
+#include "main.h"
+#include "netlink.h"
+
+static const int translocal_mandatory[] = {
+	BATADV_ATTR_TT_ADDRESS,
+	BATADV_ATTR_TT_VID,
+	BATADV_ATTR_TT_CRC32,
+	BATADV_ATTR_TT_FLAGS,
+};
+
+static int translocal_json_callback(struct nl_msg *msg, void *arg)
+{
+	struct nlattr *attrs[BATADV_ATTR_MAX+1];
+	struct nlmsghdr *nlh = nlmsg_hdr(msg);
+	struct print_opts *opts = arg;
+	struct genlmsghdr *ghdr;
+	uint8_t *addr;
+	int selected_attrs[5] = { BATADV_ATTR_TT_ADDRESS,
+				  BATADV_ATTR_TT_VID,
+				  BATADV_ATTR_TT_CRC32,
+				  BATADV_ATTR_TT_FLAGS,
+				  BATADV_ATTR_LAST_SEEN_MSECS };
+
+	if (!genlmsg_valid_hdr(nlh, 0)) {
+		fputs("Received invalid data from kernel.\n", stderr);
+		exit(1);
+	}
+
+	ghdr = nlmsg_data(nlh);
+
+	if (ghdr->cmd != BATADV_CMD_GET_TRANSTABLE_LOCAL)
+		return NL_OK;
+
+	if (nla_parse(attrs, BATADV_ATTR_MAX, genlmsg_attrdata(ghdr, 0),
+		      genlmsg_len(ghdr), batadv_netlink_policy)) {
+		fputs("Received invalid data from kernel.\n", stderr);
+		exit(1);
+	}
+
+	if (missing_mandatory_attrs(attrs, translocal_mandatory,
+				    ARRAY_SIZE(translocal_mandatory))) {
+		fputs("Missing attributes from kernel\n", stderr);
+		exit(1);
+	}
+
+	addr = nla_data(attrs[BATADV_ATTR_TT_ADDRESS]);
+
+	if (opts->read_opt & MULTICAST_ONLY && !(addr[0] & 0x01))
+		return NL_OK;
+
+	if (opts->read_opt & UNICAST_ONLY && (addr[0] & 0x01))
+		return NL_OK;
+
+	netlink_print_json_entries(attrs, selected_attrs,
+				   ARRAY_SIZE(selected_attrs), opts);
+	opts->is_first = 0;
+
+	return NL_OK;
+}
+
+static int netlink_print_translocal_json(struct state *state, char *orig_iface,
+					 int read_opts, float orig_timeout,
+					 float watch_interval)
+{
+	return netlink_print_common(state, orig_iface, read_opts,
+				    orig_timeout, watch_interval, NULL,
+				    BATADV_CMD_GET_TRANSTABLE_LOCAL,
+				    translocal_json_callback, 1);
+}
+
+static struct debug_table_data batctl_debug_json_translocal = {
+	.netlink_fn = netlink_print_translocal_json,
+	.option_unicast_only = 1,
+	.option_multicast_only = 1,
+};
+
+COMMAND_NAMED(DEBUGJSON, translocal_json, "tlj", handle_debug_table,
+	      COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK,
+	      &batctl_debug_json_translocal, "");