Hi everybody,

This thread has awaken some evil thoughts I have had since long ago. I know
this might be forbidden here, but still, I could not sleep well until I did
it. Please don't beat me for that. :)
So here is the patch (applies to "thread-next" branch). It is definitely
ugly, ineffective, untested, requires a lot of polishing, etc. It was
mostly created as a proof of concept for now. It seems to be working at
least on my simple tests with device, kernel, pipe, bgp protocols. With it
you can send "json protocols" command and get a dump of protocols in JSON.
It also adds "-q" option to cli to hide the "BIRD ..." header, so you can
do things like:

$ ./birdc -q "json protocols" | jq '.protocols.bgp1.Channels.ipv4["Gateway
mode"]'
"recursive"

I know we all long for a great API to come. But unitl that we sill have to
parse the text cli output. So why not to parse for example JSON isntead? :)
It is safer to extend and not to break compatibility with text parsers.
This and other benefits are mostly well-known. Of course it is ugly piece
of code added to the codebase, that needs to be maintained, etc. All the
minuses has also been well described here already. Anyway, I do not expect
it to be included upstream, just had some strange need to do this coding
exercise. :) On the other hand if it looks appealing, I can polish/improve
it if needed. Also "routes" variant could be added.
Any feedback is welcome! And I can give more comments on the contents, of
course.

PS. Also another idea has come to my mind. What BIRD team think about
introducing a practice of adding experimental features, i.e. those that
have no guarantee to remain compatible between versions, or to remain at
all. It is often said that adding new features is a hard process because
they need to be maintained, harden merges between versions, need thourough
study right away because further redesign can break compatibility. So I
think calling it experimental and giving yourself an opportunity to "undo"
it, could break the tie and give some "sandbox" for the feature to mature
or perish.

PPS. I'm not advocating my json patch with that idea. :)

Regards,
Alexander

On Wed, Sep 10, 2025 at 1:02 PM Douglas Fischer <[email protected]>
wrote:

>
> https://gitlab.nic.cz/labs/uytc/-/blob/dirty/examples/00_leaf/expected.output
> 😍
>
> Em ter., 9 de set. de 2025 às 20:56, Maria Matejka via Bird-users <
> [email protected]> escreveu:
>
>> Hola!
>>
>> With that, there is some internal tooling work on the way (because people
>> around Netconf didn’t bother to think about performance, let aside
>> maintainability) and as soon we have this, at least an experimental API is
>> gonna be there quite fast.
>>
>> The actual tooling is UYTC YANG To Code which should enable you to
>>
>> Doing google (not the best for this atm) on “UYTC YANG To Code” returns a
>> I mistyped this it must be UTC YANG … the rest of the returned data is
>> utterly garbage , Is there a url for this tooling you speak of ?
>>
>> Well, there is just a git repo with pieces of code in a very
>> being-worked-on state. Not much to actually see for now, there are just
>> several branches of dirty python code for now.
>>
>> https://gitlab.nic.cz/labs/uytc/
>>
>> Yet, this thing is, in the end, going to have a module for creating C
>> code. But first we have to digest through all the funky RFCs around YANG,
>> CBOR and whatever else blocks us, including drafting some new stuff, to
>> make everything actually fly the reasonable way.
>>
>> Once, Donald E. Knuth said: »So, I just have this philosophy that there
>> will be always some people who are more interested in quality than others,
>> and I wanted to make TeX good for them.« (pg. 349
>> https://www.tug.org/TUGboat/tb17-4/tb53knun.pdf)
>>
>> And we wanna make this right, and minimize the oversights which would be
>> too late to fix later. The original architecture of BIRD was good for 20
>> years, saved us a lot of hair, and many parts of it are actually still
>> there.
>>
>> Maria
>>
>> –
>> Maria Matejka (she/her) | BIRD Team Leader | CZ.NIC, z.s.p.o.
>>
>
>
> --
> Douglas Fernando Fischer
> Engº de Controle e Automação
>
diff --git a/client/client.c b/client/client.c
index 397db73d4..db9af133d 100644
--- a/client/client.c
+++ b/client/client.c
@@ -38,8 +38,8 @@
 
 #define SERVER_READ_BUF_LEN 4096
 
-static char *opt_list = "s:vrl";
-static int verbose, restricted, once;
+static char *opt_list = "s:vrlq";
+static int verbose, restricted, once, quiet;
 static char *init_cmd;
 
 static char *server_path = PATH_CONTROL_SOCKET;
@@ -84,6 +84,9 @@ parse_args(int argc, char **argv)
       case 'r':
 	restricted = 1;
 	break;
+      case 'q':
+	quiet = 1;
+	break;
       case 'l':
 	if (!server_changed)
 	  server_path = xbasename(server_path);
@@ -281,7 +284,7 @@ server_got_reply(char *x)
            sscanf(x, "%d", &code) == 1 && code >= 0 && code < 10000 &&
            (x[4] == ' ' || x[4] == '-'))
     {
-      if (code)
+      if (code && !(quiet && code < 1000))
         PRINTF(len, "%s\n", verbose ? x : x+5);
 
       last_code = code;
diff --git a/lib/printf.c b/lib/printf.c
index 0d2f95e80..6167ca818 100644
--- a/lib/printf.c
+++ b/lib/printf.c
@@ -10,6 +10,7 @@
 #include "nest/bird.h"
 #include "lib/macro.h"
 #include "lib/string.h"
+#include "lib/timer.h"
 
 #include <errno.h>
 
@@ -34,6 +35,7 @@ static int skip_atoi(const char **s)
 #define LEFT	16		/* left justified */
 #define SPECIAL	32		/* 0x */
 #define LARGE	64		/* use 'ABCDEF' instead of 'abcdef' */
+#define JSON	128		/* use JSON escaping, null type */
 
 static char * number(char * str, u64 num, uint base, int size, int precision,
 	int type, int remains)
@@ -124,6 +126,68 @@ static char * number(char * str, u64 num, uint base, int size, int precision,
 	return str;
 }
 
+char *json_string(char *str, char *s, int remains)
+{
+	if (!s) {
+		if (remains < 4)
+			return NULL;
+		*str++ = 'n';
+		*str++ = 'u';
+		*str++ = 'l';
+		*str++ = 'l';
+		return str;
+	}
+
+	if (remains < 2)
+		return NULL;
+	*str++ = '"';
+	--remains;
+
+	char spec_char;
+	char *start;
+
+	for (start=str; *s; ++s, remains-=(str-start), start=str) {
+		if (!remains)
+			return NULL;
+		switch (*s) {
+		case '"':
+		case '\\':
+			spec_char = *s;
+		special:
+			if (remains < 2)
+				return NULL;
+			*str++ = '\\';
+			*str++ = spec_char;
+			continue;
+		case '\n':
+			spec_char = 'n';
+			goto special;
+		case '\r':
+			spec_char = 'r';
+			goto special;
+		case '\t':
+			spec_char = 't';
+			goto special;
+		}
+		if (*s >= ' ') {
+			*str++ = *s;
+			continue;
+		}
+		if (remains < 6)
+			return NULL;
+		*str++ = '\\';
+		*str++ = 'u';
+		str = number(str, (unsigned char)(*s), 16, 4, 0, ZEROPAD, remains-2);
+		if (!str)
+			return NULL;
+		continue;
+	}
+	if (!remains)
+		return NULL;
+	*str++ = '"';
+	return str;
+}
+
 /**
  * bvsnprintf - BIRD's vsnprintf()
  * @buf: destination buffer
@@ -186,6 +250,7 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args)
 				case ' ': flags |= SPACE; goto repeat;
 				case '#': flags |= SPECIAL; goto repeat;
 				case '0': flags |= ZEROPAD; goto repeat;
+				case 'j': flags |= JSON; goto repeat;
 			}
 
 		/* get field width */
@@ -227,6 +292,11 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args)
 		/* default base */
 		base = 10;
 
+		if (flags & JSON) {
+			flags &= SIGN|JSON;
+			field_width = -1;
+		}
+
 		if (field_width > size)
 			return -1;
 		switch (*fmt) {
@@ -265,10 +335,12 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args)
 			}
 		case 's':
 			s = va_arg(args, char *);
-			if (!s)
+			if (!s && !(flags & JSON))
 				s = "<NULL>";
 
 		str:
+			if (flags & JSON)
+				goto json;
 			len = strlen(s);
 			if (precision >= 0 && len > precision)
 				len = precision;
@@ -284,6 +356,12 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args)
 				*str++ = ' ';
 			continue;
 
+		json:
+			str = json_string(str, s, size);
+			if (!str)
+				return -1;
+			continue;
+
 		case 'V': {
 			const char *vfmt = va_arg(args, const char *);
 			va_list *vargs = va_arg(args, va_list *);
@@ -316,6 +394,14 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args)
 			}
 			continue;
 
+		case 'b':
+			num = va_arg(args, uint);
+			if (num)
+				s = "true";
+			else
+				s = "false";
+			goto str;
+
 		/* IP address */
 		case 'I':
 			if (fmt[1] == '4') {
@@ -352,6 +438,11 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args)
 		/* Interface scope after link-local IP address */
 		case 'J':
 			iface = va_arg(args, struct iface *);
+			if (flags & JSON) {
+				s = iface ? iface->name : NULL;
+				goto json;
+			}
+
 			if (!iface)
 				continue;
 			if (!size)
@@ -388,6 +479,8 @@ int bvsnprintf(char *buf, int size, const char *fmt, va_list args)
 
 		case 't':
 			t = va_arg(args, btime);
+			if (flags & JSON)
+				t = tm_real_time(t);
 			t1 = t TO_S;
 			t2 = t - t1 S;
 
diff --git a/lib/timer.c b/lib/timer.c
index 85519f441..4fa5201ad 100644
--- a/lib/timer.c
+++ b/lib/timer.c
@@ -244,6 +244,14 @@ tm_parse_time(const char *x)
   return ts S + usec;
 }
 
+btime
+tm_real_time(btime t)
+{
+  btime dt = current_time() - t;
+  btime rt = current_real_time() - dt;
+  return rt;
+}
+
 /**
  * tm_format_time - convert date and time to textual representation
  * @x: destination buffer of size %TM_DATETIME_BUFFER_SIZE
diff --git a/lib/timer.h b/lib/timer.h
index f52694c89..fa8a1638b 100644
--- a/lib/timer.h
+++ b/lib/timer.h
@@ -136,6 +136,7 @@ struct timeformat {
 #define TM_DATETIME_BUFFER_SIZE 32	/* Buffer size required by tm_format_time() */
 
 btime tm_parse_time(const char *x);
+btime tm_real_time(btime t);
 void tm_format_time(char *x, struct timeformat *fmt, btime t);
 int tm_format_real_time(char *x, size_t max, const char *fmt, btime t);
 
diff --git a/nest/config.Y b/nest/config.Y
index 681d0edd9..f25463a13 100644
--- a/nest/config.Y
+++ b/nest/config.Y
@@ -166,6 +166,7 @@ CF_KEYWORDS(CHECK, LINK)
 CF_KEYWORDS(CORK, SORTED, TRIE, MIN, MAX, ROA, DIGEST, ROUTE, REFRESH, SETTLE, TIME, GC, THRESHOLD, PERIOD)
 CF_KEYWORDS(MPLS_LABEL, MPLS_POLICY, MPLS_CLASS)
 CF_KEYWORDS(ASPA_PROVIDERS)
+CF_KEYWORDS(JSON)
 
 /* For r_args_channel */
 CF_KEYWORDS(IPV4, IPV4_MC, IPV4_MPLS, IPV6, IPV6_MC, IPV6_MPLS, IPV6_SADR, VPN4, VPN4_MC, VPN4_MPLS, VPN6, VPN6_MC, VPN6_MPLS, ROA4, ROA6, FLOW4, FLOW6, MPLS, PRI, SEC, ASPA)
@@ -979,6 +980,10 @@ CF_CLI(DUMP PROTOCOLS, text,, [[Dump protocol information]])
 CF_CLI(DUMP FILTER ALL, text,, [[Dump all filters in linearized form]])
 { cmd_dump_file(this_cli, $4, "filter bytecode", filters_dump_all); } ;
 
+CF_CLI_HELP(JSON, ..., [[Dump status information as JSON]])
+CF_CLI(JSON PROTOCOLS,,, [[Dump protocol status information as JSON]])
+{ protos_json_all(); } ;
+
 CF_CLI(EVAL, term, <expr>, [[Evaluate an expression]])
 { cmd_eval(f_linearize($2, 1)); } ;
 
diff --git a/nest/proto.c b/nest/proto.c
index be347ea3e..4a04a9235 100644
--- a/nest/proto.c
+++ b/nest/proto.c
@@ -2223,6 +2223,20 @@ protos_dump_all(struct dump_request *dreq)
   }
 }
 
+void
+protos_json_all()
+{
+  cli_msg(-2002, "{\"version\":1,\"protocols\":{");
+  int cnt = 0;
+  WALK_TLIST(proto, p, &global_proto_list) PROTO_LOCKED_FROM_MAIN(p)
+  {
+    proto_cmd_show_json(p, cnt);
+    ++cnt;
+  }
+  cli_msg(-2002, "}}");
+  cli_msg(0, "");
+}
+
 /**
  * proto_build - make a single protocol available
  * @p: the protocol
@@ -2706,6 +2720,61 @@ channel_show_stats(struct channel *c)
 #undef SON
 }
 
+static void
+channel_show_stats_json(struct channel *c)
+{
+  if (c->channel_state == CS_DOWN) {
+    cli_msg(-1006, "\"Stats\":null");
+    return;
+  }
+
+  struct channel_import_stats *ch_is = &c->import_stats;
+  struct channel_export_stats *ch_es = &c->export_stats;
+  struct rt_import_stats *rt_is = c->in_req.hook ? &c->in_req.hook->stats : NULL;
+  struct rt_export_stats *rt_es = &c->out_req.stats;
+
+#define SON(ie, item)	((ie) ? (ie)->item : 0)
+#define SCI(item) SON(ch_is, item)
+#define SCE(item) SON(ch_es, item)
+#define SRI(item) SON(rt_is, item)
+#define SRE(item) SON(rt_es, item)
+
+  u32 rx_routes = c->rx_limit.count;
+  u32 in_routes = c->in_limit.count;
+  u32 out_routes = c->out_limit.count;
+
+  cli_msg(-1006, "\"Stats\":{");
+
+  cli_msg(-1006, "\"Routes\":{\"imported\":%ju,\"exported\":%ju,\"preferred\":%ju",
+	in_routes, out_routes, SRI(pref));
+  if (c->in_keep)
+    cli_msg(-1006, ",\"filtered\":%ju", (rx_routes - in_routes));
+  else
+    cli_msg(-1006, ",\"filtered\":null");
+
+  cli_msg(-1006, "},\"Route change\":{");
+  cli_msg(-1006, "\"Import updates\":{\"received\":%ju,\"rejected\":%ju,\"filtered\":%ju,\"ignored\":%ju,\"RX limit\":%ju,\"limit\":%ju,\"accepted\":%ju}",
+	  SCI(updates_received), SCI(updates_invalid),
+	  SCI(updates_filtered), SRI(updates_ignored),
+	  SCI(updates_limited_rx), SCI(updates_limited_in),
+	  SRI(updates_accepted));
+  cli_msg(-1006, ",\"Import withdraws\":{\"received\":%ju,\"rejected\":%ju,\"ignored\":%ju,\"accepted\":%ju}",
+	  SCI(withdraws_received), SCI(withdraws_invalid),
+	  SRI(withdraws_ignored), SRI(withdraws_accepted));
+  cli_msg(-1006, ",\"Export updates\":{\"received\":%ju,\"rejected\":%ju,\"filtered\":%ju,\"ignored\":%ju,\"limit\":%ju,\"accepted\":%ju}",
+	  SRE(updates_received), SCE(updates_rejected),	SCE(updates_filtered),
+	  SCE(updates_ignored), SCE(updates_limited), SCE(updates_accepted));
+  cli_msg(-1006, ",\"Export withdraws\":{\"received\":%ju,\"ignored\":%ju,\"accepted\":%ju}",
+	  SRE(withdraws_received), SCE(withdraws_ignored), SCE(withdraws_accepted));
+  cli_msg(-1006, "}}");
+
+#undef SRI
+#undef SRE
+#undef SCI
+#undef SCE
+#undef SON
+}
+
 void
 channel_show_limit(struct limit *l, const char *dsc, int active, int action)
 {
@@ -2716,6 +2785,17 @@ channel_show_limit(struct limit *l, const char *dsc, int active, int action)
   cli_msg(-1006, "      Action:       %s", channel_limit_name[action]);
 }
 
+void
+channel_show_limit_json(struct limit *l, const char *dsc, int active, int action)
+{
+  if (!l->action) {
+    cli_msg(-1006, "%js:null", dsc);
+    return;
+  }
+  cli_msg(-1006, "%js:{\"max\":%jd,\"active\":%jb", dsc, l->max, active);
+  cli_msg(-1006, ",\"Action\":%js}", channel_limit_name[action]);
+}
+
 void
 channel_show_info(struct channel *c)
 {
@@ -2741,6 +2821,35 @@ channel_show_info(struct channel *c)
     channel_show_stats(c);
 }
 
+void
+channel_show_info_json(struct channel *c)
+{
+  cli_msg(-1006, "\"State\":%js", c_states[c->channel_state]);
+  cli_msg(-1006, ",\"Import state\":%js", rt_import_state_name(rt_import_get_state(c->in_req.hook)));
+  cli_msg(-1006, ",\"Export state\":%js", rt_export_state_name(rt_export_get_state(&c->out_req)));
+  cli_msg(-1006, ",\"Table\":%js", c->table->name);
+  cli_msg(-1006, ",\"Preference\":%jd", c->preference);
+  cli_msg(-1006, ",\"Input filter\":%js", filter_name(c->in_filter));
+  cli_msg(-1006, ",\"Output filter\":%js", filter_name(c->out_filter));
+
+  if (_graceful_recovery_context.grc_state == GRS_ACTIVE) {
+    cli_msg(-1006, ",\"GR recovery\":{\"pending\":%jb,\"waiting\":%jb}",
+	    OBSREF_GET(c->gr_lock), c->gr_wait);
+  } else {
+    cli_msg(-1006, ",\"GR recovery\":null");
+  }
+
+  cli_msg(-1006, ",");
+  channel_show_limit_json(&c->rx_limit, "Receive limit", c->limit_active & (1 << PLD_RX), c->limit_actions[PLD_RX]);
+  cli_msg(-1006, ",");
+  channel_show_limit_json(&c->in_limit, "Import limit", c->limit_active & (1 << PLD_IN), c->limit_actions[PLD_IN]);
+  cli_msg(-1006, ",");
+  channel_show_limit_json(&c->out_limit, "Export limit", c->limit_active & (1 << PLD_OUT), c->limit_actions[PLD_OUT]);
+
+  cli_msg(-1006, ",");
+  channel_show_stats_json(c);
+}
+
 void
 channel_cmd_debug(struct channel *c, uint mask)
 {
@@ -2809,6 +2918,58 @@ proto_cmd_show(struct proto *p, uintptr_t verbose, int cnt)
   }
 }
 
+void
+proto_cmd_show_json(struct proto *p, int cnt)
+{
+  byte buf[256];
+
+  /* Not first protocol - show comma */
+  if (cnt)
+    cli_msg(-2002, ",");
+
+  buf[0] = 0;
+  if (p->proto->get_status)
+    p->proto->get_status(p, buf);
+
+  cli_msg(-1002, "%js:{\"Proto\":%js,\"Table\":%js,\"State\":%js,\"Since\":%jt,\"Info\":%js",
+	  p->name,
+	  p->proto->name,
+	  p->main_channel ? p->main_channel->table->name : NULL,
+	  proto_state_name(p),
+	  p->last_state_change,
+	  buf);
+
+  cli_msg(-1006, ",\"Description\":%js", p->cf->dsc);
+  cli_msg(-1006, ",\"Message\":%js", p->message);
+  cli_msg(-1006, ",\"Created\":%jt", p->last_reconfiguration);
+  if (p->last_restart > p->last_reconfiguration)
+    cli_msg(-1006, ",\"Last autorestart\":%jt", p->last_restart);
+  else
+    cli_msg(-1006, ",\"Last autorestart\":null");
+  cli_msg(-1006, ",\"Router ID\":%jR", p->cf->router_id);
+  cli_msg(-1006, ",\"VRF\":%js", p->vrf ? p->vrf->name : NULL);
+
+  if (p->proto->show_proto_info_json)
+    p->proto->show_proto_info_json(p);
+  else
+  {
+    struct channel *c;
+    cli_msg(-1006, ",\"Channels\":{");
+    int ch_cnt = 0;
+    WALK_LIST(c, p->channels) {
+      if (ch_cnt)
+        cli_msg(-1006, ",");
+      ++ch_cnt;
+      cli_msg(-1006, "%js:{", c->name);
+      channel_show_info_json(c);
+      cli_msg(-1006, "}");
+    }
+    cli_msg(-1006, "}");
+  }
+
+  cli_msg(-1006, "}");
+}
+
 void
 proto_cmd_disable(struct proto *p, uintptr_t arg, int cnt UNUSED)
 {
diff --git a/nest/protocol.h b/nest/protocol.h
index f5d8a7571..4414dd7e6 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -70,6 +70,7 @@ struct protocol {
   void (*get_status)(struct proto *, byte *buf); /* Get instance status (for `show protocols' command) */
 //  int (*get_attr)(const struct eattr *, byte *buf, int buflen);	/* ASCIIfy dynamic attribute (returns GA_*) */
   void (*show_proto_info)(struct proto *);	/* Show protocol info (for `show protocols all' command) */
+  void (*show_proto_info_json)(struct proto *);	/* Dump protocol info (for `json protocols' command) */
   void (*copy_config)(struct proto_config *, struct proto_config *);	/* Copy config from given protocol instance */
 };
 
@@ -81,6 +82,7 @@ struct proto * proto_spawn(struct proto_config *cf, uint disabled);
 bool proto_disable(struct proto *p);
 bool proto_enable(struct proto *p);
 void protos_dump_all(struct dump_request *);
+void protos_json_all(void);
 
 #define GA_UNKNOWN	0		/* Attribute not recognized */
 #define GA_NAME		1		/* Result = name */
@@ -265,10 +267,13 @@ static inline void proto_send_event(struct proto *p, event *e)
 { ev_send(proto_event_list(p), e); }
 
 void channel_show_limit(struct limit *l, const char *dsc, int active, int action);
+void channel_show_limit_json(struct limit *l, const char *dsc, int active, int action);
 void channel_show_info(struct channel *c);
+void channel_show_info_json(struct channel *c);
 void channel_cmd_debug(struct channel *c, uint mask);
 
 void proto_cmd_show(struct proto *, uintptr_t, int);
+void proto_cmd_show_json(struct proto *, int);
 void proto_cmd_disable(struct proto *, uintptr_t, int);
 void proto_cmd_enable(struct proto *, uintptr_t, int);
 void proto_cmd_restart(struct proto *, uintptr_t, int);
diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c
index 394759255..127051ca4 100644
--- a/proto/bgp/bgp.c
+++ b/proto/bgp/bgp.c
@@ -3423,6 +3423,23 @@ bgp_show_afis(int code, char *s, u32 *afis, uint count)
   cli_msg(code, b.start);
 }
 
+static void
+bgp_show_afis_json(int code, u32 *afis, uint count)
+{
+  cli_msg(code, "[");
+
+  for (u32 *af = afis; af < (afis + count); af++)
+  {
+    if (af != afis)
+      cli_msg(code, ",");
+    const struct bgp_af_desc *desc = bgp_get_af_desc(*af);
+    cli_msg(code, "{\"AFI\":%ju,\"SAFI\":%ju,\"desc\":%js}",
+            BGP_AFI(*af), BGP_SAFI(*af), desc ? desc->name : NULL);
+  }
+
+  cli_msg(code, "]");
+}
+
 const char *
 bgp_format_role_name(u8 role)
 {
@@ -3565,6 +3582,147 @@ bgp_show_capabilities(struct bgp_proto *p UNUSED, struct bgp_caps *caps)
     cli_msg(-1006, "      Role: %s", bgp_format_role_name(caps->role));
 }
 
+static void
+bgp_show_capabilities_json(struct bgp_proto *p UNUSED, struct bgp_caps *caps)
+{
+  struct bgp_af_caps *ac;
+  uint any_mp_bgp = 0;
+  uint any_gr_able = 0;
+  uint any_add_path = 0;
+  uint any_ext_next_hop = 0;
+  uint any_llgr_able = 0;
+  u32 *afl1 = alloca(caps->af_count * sizeof(u32));
+  u32 *afl2 = alloca(caps->af_count * sizeof(u32));
+  uint afn1, afn2;
+
+  WALK_AF_CAPS(caps, ac)
+  {
+    any_mp_bgp |= ac->ready;
+    any_gr_able |= ac->gr_able;
+    any_add_path |= ac->add_path;
+    any_ext_next_hop |= ac->ext_next_hop;
+    any_llgr_able |= ac->llgr_able;
+  }
+
+  cli_msg(-1006, "\"Multiprotocol\":%jb", any_mp_bgp);
+  if (any_mp_bgp)
+  {
+    afn1 = 0;
+    WALK_AF_CAPS(caps, ac)
+      if (ac->ready)
+	afl1[afn1++] = ac->afi;
+
+    cli_msg(-1006, ",\"AF announced\":");
+    bgp_show_afis_json(-1006, afl1, afn1);
+  }
+
+  cli_msg(-1006, ",\"Route refresh\":%jb", caps->route_refresh);
+
+  cli_msg(-1006, ",\"Extended next hop\":%jb", any_ext_next_hop);
+  if (any_ext_next_hop)
+  {
+    afn1 = 0;
+    WALK_AF_CAPS(caps, ac)
+      if (ac->ext_next_hop)
+	afl1[afn1++] = ac->afi;
+
+    cli_msg(-1006, ",\"IPv6 nexthop\":");
+    bgp_show_afis_json(-1006, afl1, afn1);
+  }
+
+  cli_msg(-1006, ",\"Extended message\":%jb", caps->ext_messages);
+  cli_msg(-1006, ",\"Graceful restart aware\":%jb", caps->gr_aware);
+
+  if (any_gr_able)
+  {
+    cli_msg(-1006, ",\"Graceful restart\":{");
+    /* Continues from gr_aware */
+    cli_msg(-1006, "\"Restart time\":%ju", caps->gr_time);
+    cli_msg(-1006, ",\"Restart recovery\":%jb", caps->gr_flags & BGP_GRF_RESTART);
+
+    afn1 = afn2 = 0;
+    WALK_AF_CAPS(caps, ac)
+    {
+      if (ac->gr_able)
+	afl1[afn1++] = ac->afi;
+
+      if (ac->gr_af_flags & BGP_GRF_FORWARDING)
+	afl2[afn2++] = ac->afi;
+    }
+
+    cli_msg(-1006, ",\"AF supported\":");
+    bgp_show_afis_json(-1006, afl1, afn1);
+    cli_msg(-1006, ",\"AF preserved\":");
+    bgp_show_afis_json(-1006, afl2, afn2);
+
+    cli_msg(-1006, "}");
+  }
+
+  cli_msg(-1006, ",\"4-octet AS numbers\":%jb", caps->as4_support);
+  cli_msg(-1006, ",\"ADD-PATH aware\":%jb", any_add_path);
+
+  if (any_add_path)
+  {
+    cli_msg(-1006, ",\"ADD-PATH\":{");
+
+    afn1 = afn2 = 0;
+    WALK_AF_CAPS(caps, ac)
+    {
+      if (ac->add_path & BGP_ADD_PATH_RX)
+	afl1[afn1++] = ac->afi;
+
+      if (ac->add_path & BGP_ADD_PATH_TX)
+	afl2[afn2++] = ac->afi;
+    }
+
+    cli_msg(-1006, "\"RX\":");
+    bgp_show_afis_json(-1006, afl1, afn1);
+    cli_msg(-1006, ",\"TX\":");
+    bgp_show_afis_json(-1006, afl2, afn2);
+
+    cli_msg(-1006, "}");
+  }
+
+  cli_msg(-1006, ",\"Enhanced refresh\":%jb", caps->enhanced_refresh);
+  cli_msg(-1006, ",\"Long-lived graceful restart aware\":%jb", caps->llgr_aware);
+
+  if (any_llgr_able)
+  {
+    cli_msg(-1006, ",\"Long-lived graceful restart\":{");
+
+    u32 stale_time = 0;
+
+    afn1 = afn2 = 0;
+    WALK_AF_CAPS(caps, ac)
+    {
+      stale_time = MAX(stale_time, ac->llgr_time);
+
+      if (ac->llgr_able && ac->llgr_time)
+	afl1[afn1++] = ac->afi;
+
+      if (ac->llgr_flags & BGP_GRF_FORWARDING)
+	afl2[afn2++] = ac->afi;
+    }
+
+    /* Continues from llgr_aware */
+    cli_msg(-1006, "\"LL stale time\":%ju", stale_time);
+
+    cli_msg(-1006, ",\"AF supported\":");
+    bgp_show_afis_json(-1006, afl1, afn1);
+    cli_msg(-1006, ",\"AF preserved\":");
+    bgp_show_afis_json(-1006, afl2, afn2);
+
+    cli_msg(-1006, "}");
+  }
+
+  cli_msg(-1006, ",\"Hostname\":%js", caps->hostname);
+
+  if (caps->role != BGP_ROLE_UNDEFINED)
+    cli_msg(-1006, ",\"Role\":%js", bgp_format_role_name(caps->role));
+  else
+    cli_msg(-1006, ",\"Role\":null");
+}
+
 static void
 bgp_show_proto_info(struct proto *P)
 {
@@ -3719,6 +3877,177 @@ bgp_show_proto_info(struct proto *P)
   }
 }
 
+static void
+bgp_show_proto_info_json(struct proto *P)
+{
+  struct bgp_proto *p = (struct bgp_proto *) P;
+
+  cli_msg(-1006, ",\"BGP state\":%js", bgp_state_dsc(p));
+
+  if (bgp_is_dynamic(p) && p->cf->remote_range)
+    cli_msg(-1006, ",\"Neighbor range\":%jN", p->cf->remote_range);
+  else
+    cli_msg(-1006, ",\"Neighbor address IP\":%jI,\"Neighbor address iface\":%jJ", p->remote_ip, p->cf->iface);
+
+  if (p->conn == &p->outgoing_conn)
+    cli_msg(-1006, ",\"Neighbor port\":%ju", p->cf->remote_port);
+
+  cli_msg(-1006, ",\"Neighbor AS\":%ju", p->remote_as);
+  cli_msg(-1006, ",\"Local AS\":%ju", p->cf->local_as);
+
+  cli_msg(-1006, ",\"Neighbor graceful restart active\":%jb", p->gr_active_num);
+
+  if (P->proto_state == PS_START)
+  {
+    struct bgp_conn *oc = &p->outgoing_conn;
+
+    if ((bgp_start_state(p) < BSS_CONNECT) &&
+	(tm_active(p->startup_timer)))
+      cli_msg(-1006, ",\"Error wait timer\":%t,\"Error wait delay\":%ju",
+	      tm_remains(p->startup_timer), p->startup_delay);
+
+    if ((oc->state == BS_ACTIVE) &&
+	(tm_active(oc->connect_timer)))
+      cli_msg(-1006, ",\"Connect delay timer\":%t,\"Connect delay time\":%ju",
+	      tm_remains(oc->connect_timer), p->cf->connect_delay_time);
+
+    if (p->gr_active_num && tm_active(p->gr_timer))
+      cli_msg(-1006, ",\"Restart timer\":%t",
+	      tm_remains(p->gr_timer));
+  }
+  else if (P->proto_state == PS_UP)
+  {
+    cli_msg(-1006, ",\"Neighbor ID\":%jR", p->remote_id);
+    cli_msg(-1006, ",\"Local capabilities\":{");
+    bgp_show_capabilities_json(p, p->conn->local_caps);
+    cli_msg(-1006, "},\"Neighbor capabilities\":{");
+    bgp_show_capabilities_json(p, p->conn->remote_caps);
+    cli_msg(-1006, "},\"Session\":{\"internal\":%jb,\"multihop\":%jb,\"route-reflecor\":%jb,\"route-server\":%jb,\"AS4\":%jb}",
+	    p->is_internal,
+	    p->cf->multihop,
+	    p->rr_client,
+	    p->rs_client,
+	    p->as4_session);
+    cli_msg(-1006, ",\"Source address\":%jI", p->local_ip);
+    cli_msg(-1006, ",\"Hold timer remains\":%t,\"Hold time\":%ju",
+	    tm_remains(p->conn->hold_timer), p->conn->hold_time);
+    cli_msg(-1006, ",\"Keepalive timer remains\":%t,\"Keepalive time\":%ju",
+	    tm_remains(p->conn->keepalive_timer), p->conn->keepalive_time);
+    cli_msg(-1006, ",\"TX pending bytes\":%jd",
+	    p->conn->sk->tpos - p->conn->sk->ttx);
+    cli_msg(-1006, ",\"Send hold timer remains\":%t,\"Send hold time\":%ju",
+	    tm_remains(p->conn->send_hold_timer), p->conn->send_hold_time);
+
+    if (EMPTY_LIST(p->ao.keys))
+    {
+      cli_msg(-1006, ",\"TCP-AO\":null");
+    }
+    else
+    {
+      struct ao_info info;
+      sk_get_ao_info(p->conn->sk, &info);
+
+      cli_msg(-1006, ",\"TCP-AO\":{");
+      cli_msg(-1006, "\"Current key\":%ji", info.current_key);
+      cli_msg(-1006, ",\"RNext key\":%ji", info.rnext_key);
+      cli_msg(-1006, ",\"Good packets\":%jlu", info.pkt_good);
+      cli_msg(-1006, ",\"Bad packets\":%jlu", info.pkt_bad);
+      cli_msg(-1006, "}");
+    }
+  }
+
+  struct bgp_stats *s = &p->stats;
+  cli_msg(-1006, ",\"FSM established transitions\":%ju",
+	  s->fsm_established_transitions);
+  cli_msg(-1006, ",\"Rcvd messages\":{\"total\":%ju,\"updates\":%ju,\"bytes\":%jlu}",
+	  s->rx_messages, s->rx_updates, s->rx_bytes);
+  cli_msg(-1006, ",\"Sent messages\":{\"total\":%ju,\"updates\":%ju,\"bytes\":%jlu}",
+	  s->tx_messages, s->tx_updates, s->tx_bytes);
+  cli_msg(-1006, ",\"Last rcvd update elapsed time\":%t",
+	  p->last_rx_update ? (current_time() - p->last_rx_update) : 0);
+
+  if ((p->last_error_class != BE_NONE) &&
+      (p->last_error_class != BE_MAN_DOWN))
+  {
+    const char *err1 = bgp_err_classes[p->last_error_class];
+    const char *err2 = bgp_last_errmsg(p);
+    cli_msg(-1006, ",\"Last error\":[%js,%js]", err1, err2);
+  }
+
+  {
+    cli_msg(-1006, ",\"Channels\":{");
+    int cnt = 0;
+    struct bgp_channel *c;
+    WALK_LIST(c, p->p.channels)
+    {
+      if (cnt)
+        cli_msg(-1006, "},");
+      ++cnt;
+
+      cli_msg(-1006, "%js:{", c->c.name);
+      channel_show_info_json(&c->c);
+
+      if (c->c.class != &channel_bgp)
+	continue;
+
+      if (p->gr_active_num)
+	cli_msg(-1006, ",\"Neighbor GR\":%js", bgp_gr_states[c->gr_active]);
+
+      if (c->stale_timer && tm_active(c->stale_timer))
+	cli_msg(-1006, ",\"LL stale timer\":%t", tm_remains(c->stale_timer));
+
+      if (c->cf->gw_mode == GW_DIRECT)
+        cli_msg(-1006, ",\"Gateway mode\":\"direct\"");
+      else if (c->cf->gw_mode == GW_RECURSIVE)
+        cli_msg(-1006, ",\"Gateway mode\":\"recursive\"");
+      else
+        cli_msg(-1006, ",\"Gateway mode\":null");
+
+      if (c->c.channel_state == CS_UP)
+      {
+	cli_msg(-1006, ",\"BGP Next hop\":%jI", c->next_hop_addr);
+	if (!ipa_zero(c->link_addr))
+	  cli_msg(-1006, ",\"BGP Next hop link\":%jI", c->link_addr);
+      }
+
+      /* After channel is deconfigured, these pointers are no longer valid */
+      if (!p->p.reconfiguring || (c->c.channel_state != CS_DOWN))
+      {
+	if (c->igp_table_ip4)
+	  cli_msg(-1006, ",\"IGP IPv4 table\":%js", c->igp_table_ip4->name);
+
+	if (c->igp_table_ip6)
+	  cli_msg(-1006, ",\"IGP IPv6 table\":%js", c->igp_table_ip6->name);
+
+	if (c->base_table)
+	  cli_msg(-1006, ",\"Base table\":%js", c->base_table->name);
+      }
+
+      if (!c->tx)
+	continue;
+
+      BGP_PTX_LOCK(c->tx, tx);
+
+      uint bucket_cnt = 0;
+      uint prefix_cnt = 0;
+      struct bgp_bucket *buck;
+      struct bgp_prefix *px;
+      WALK_LIST(buck, tx->bucket_queue)
+      {
+	bucket_cnt++;
+	WALK_LIST(px, buck->prefixes)
+	  if (px->cur)
+	    prefix_cnt++;
+      }
+
+      cli_msg(-1006, ",\"Pending\":{\"attribute sets\":%ju,\"total prefixes to send\":%ju}",
+	 bucket_cnt, prefix_cnt);
+    }
+    cli_msg(-1006, "}}");
+  }
+}
+
+
 const struct channel_class channel_bgp = {
   .channel_size =	sizeof(struct bgp_channel),
   .config_size =	sizeof(struct bgp_channel_config),
@@ -3743,6 +4072,7 @@ struct protocol proto_bgp = {
   .reconfigure = 	bgp_reconfigure,
   .copy_config = 	bgp_copy_config,
   .get_status = 	bgp_get_status,
+  .show_proto_info_json = 	bgp_show_proto_info_json,
   .show_proto_info = 	bgp_show_proto_info
 };
 
diff --git a/proto/bmp/bmp.c b/proto/bmp/bmp.c
index baed8bf5f..86a492dec 100644
--- a/proto/bmp/bmp.c
+++ b/proto/bmp/bmp.c
@@ -1584,6 +1584,31 @@ bmp_show_proto_info(struct proto *P)
   }
 }
 
+static void
+bmp_show_proto_info_json(struct proto *P)
+{
+  struct bmp_proto *p = (void *) P;
+
+  if (P->proto_state != PS_DOWN_XX)
+  {
+    cli_msg(-1006, ",\"Station address\":%jI", p->station_ip);
+    cli_msg(-1006, ",\"Station port\":%ju", p->station_port);
+    if (ipa_zero(p->local_addr))
+      cli_msg(-1006, ",\"Local address\":null");
+    else
+      cli_msg(-1006, ",\"Local address\":%jI", p->local_addr);
+
+    cli_msg(-1006, ",\"Last error\":%jM", p->sock_err);
+
+    cli_msg(-1006, ",\"Pending TX count\":%ju,\"Pending TX limit\":%ju",
+	p->tx_pending_count * (u64) page_size,
+	p->tx_pending_limit * (u64) page_size);
+
+    cli_msg(-1006, ",\"Session TX\":%ju", p->tx_sent);
+    cli_msg(-1006, ",\"Total TX\":%ju", p->tx_sent_total);
+  }
+}
+
 struct protocol proto_bmp = {
   .name = "BMP",
   .template = "bmp%d",
@@ -1595,6 +1620,7 @@ struct protocol proto_bmp = {
   .shutdown = bmp_shutdown,
   .reconfigure = bmp_reconfigure,
   .get_status = bmp_get_status,
+  .show_proto_info_json = bmp_show_proto_info_json,
   .show_proto_info = bmp_show_proto_info,
 };
 
diff --git a/proto/pipe/pipe.c b/proto/pipe/pipe.c
index 0420ca140..1278f6367 100644
--- a/proto/pipe/pipe.c
+++ b/proto/pipe/pipe.c
@@ -266,6 +266,69 @@ pipe_show_stats(struct pipe_proto *p)
 	  rs2i->withdraws_ignored, rs2i->withdraws_accepted);
 }
 
+static void
+pipe_show_stats_json(struct pipe_proto *p)
+{
+  if (p->p.proto_state == PS_DOWN_XX) {
+    cli_msg(-1006, "\"Stats\":null");
+    return;
+  }
+
+  struct channel_import_stats *s1i = &p->pri->import_stats;
+  struct channel_export_stats *s1e = &p->pri->export_stats;
+  struct channel_import_stats *s2i = &p->sec->import_stats;
+  struct channel_export_stats *s2e = &p->sec->export_stats;
+
+  struct rt_import_stats *rs1i = p->pri->in_req.hook ? &p->pri->in_req.hook->stats : NULL;
+  struct rt_export_stats *rs1e = &p->pri->out_req.stats;
+  struct rt_import_stats *rs2i = p->sec->in_req.hook ? &p->sec->in_req.hook->stats : NULL;
+  struct rt_export_stats *rs2e = &p->sec->out_req.stats;
+
+  u32 pri_routes = p->pri->in_limit.count;
+  u32 sec_routes = p->sec->in_limit.count;
+
+  /*
+   * Pipe stats (as anything related to pipes) are a bit tricky. There
+   * are two sets of stats - s1 for ahook to the primary routing and
+   * s2 for the ahook to the secondary routing table. The user point
+   * of view is that routes going from the primary routing table to
+   * the secondary routing table are 'exported', while routes going in
+   * the other direction are 'imported'.
+   *
+   * Each route going through a pipe is, technically, first exported
+   * to the pipe and then imported from that pipe and such operations
+   * are counted in one set of stats according to the direction of the
+   * route propagation. Filtering is done just in the first part
+   * (export). Therefore, we compose stats for one directon for one
+   * user direction from both import and export stats, skipping
+   * immediate and irrelevant steps (exp_updates_accepted,
+   * imp_updates_received, imp_updates_filtered, ...).
+   *
+   * Rule of thumb is that stats s1 have the correct 'polarity'
+   * (imp/exp), while stats s2 have switched 'polarity'.
+   */
+
+  cli_msg(-1006, "\"Stats\":{");
+
+  cli_msg(-1006, "\"Routes\":{\"imported\":%ju,\"exported\":%ju,\"preferred\":null,\"filtered\":null}",
+	  pri_routes, sec_routes);
+
+  cli_msg(-1006, ",\"Route change\":{");
+  cli_msg(-1006, "\"Import updates\":{\"received\":%ju,\"rejected\":%ju,\"filtered\":%ju,\"ignored\":%ju,\"accepted\":%ju}",
+	  rs2e->updates_received, s2e->updates_rejected + s1i->updates_invalid,
+	  s2e->updates_filtered, rs1i->updates_ignored, rs1i->updates_accepted);
+  cli_msg(-1006, ",\"Import withdraws\":{\"received\":%ju,\"rejected\":%ju,\"ignored\":%ju,\"accepted\":%ju}",
+	  rs2e->withdraws_received, s1i->withdraws_invalid,
+	  rs1i->withdraws_ignored, rs1i->withdraws_accepted);
+  cli_msg(-1006, ",\"Export updates\":{\"received\":%ju,\"rejected\":%ju,\"filtered\":%ju,\"ignored\":%ju,\"accepted\":%ju}",
+	  rs1e->updates_received, s1e->updates_rejected + s2i->updates_invalid,
+	  s1e->updates_filtered, rs2i->updates_ignored, rs2i->updates_accepted);
+  cli_msg(-1006, ",\"Export withdraws\":{\"received\":%ju,\"rejected\":%ju,\"ignored\":%ju,\"accepted\":%ju}",
+	  rs1e->withdraws_received, s2i->withdraws_invalid,
+	  rs2i->withdraws_ignored, rs2i->withdraws_accepted);
+  cli_msg(-1006, "}}");
+}
+
 static void
 pipe_show_proto_info(struct proto *P)
 {
@@ -290,6 +353,29 @@ pipe_show_proto_info(struct proto *P)
     pipe_show_stats(p);
 }
 
+static void
+pipe_show_proto_info_json(struct proto *P)
+{
+  struct pipe_proto *p = (void *) P;
+
+  cli_msg(-1006, ",\"Channels\":{\"main\":{");
+  cli_msg(-1006, "\"Table\":%js", p->pri->table->name);
+  cli_msg(-1006, ",\"Peer table\":%js", p->sec->table->name);
+  cli_msg(-1006, ",\"Import state\":%js", rt_export_state_name(rt_export_get_state(&p->sec->out_req)));
+  cli_msg(-1006, ",\"Export state\":%js", rt_export_state_name(rt_export_get_state(&p->pri->out_req)));
+  cli_msg(-1006, ",\"Import filter\":%js", filter_name(p->sec->out_filter));
+  cli_msg(-1006, ",\"Export filter\":%js", filter_name(p->pri->out_filter));
+  cli_msg(-1006, ",");
+  channel_show_limit_json(&p->pri->in_limit, "Import limit:",
+      (p->pri->limit_active & (1 << PLD_IN)), p->pri->limit_actions[PLD_IN]);
+  cli_msg(-1006, ",");
+  channel_show_limit_json(&p->sec->in_limit, "Export limit:",
+      (p->sec->limit_active & (1 << PLD_IN)), p->sec->limit_actions[PLD_IN]);
+  cli_msg(-1006, "},");
+  pipe_show_stats_json(p);
+  cli_msg(-1006, "}");
+}
+
 void
 pipe_update_debug(struct proto *P)
 {
@@ -310,6 +396,7 @@ struct protocol proto_pipe = {
   .reconfigure =	pipe_reconfigure,
   .copy_config = 	pipe_copy_config,
   .get_status = 	pipe_get_status,
+  .show_proto_info_json = 	pipe_show_proto_info_json,
   .show_proto_info = 	pipe_show_proto_info
 };
 
diff --git a/proto/rpki/rpki.c b/proto/rpki/rpki.c
index fe6c75dd0..cb72bc5cb 100644
--- a/proto/rpki/rpki.c
+++ b/proto/rpki/rpki.c
@@ -929,6 +929,16 @@ rpki_show_proto_info_timer(const char *name, uint num, timer *t)
     cli_msg(-1006, "  %-16s: ---", name);
 }
 
+static void
+rpki_show_proto_info_timer_json(const char *name, uint num, timer *t)
+{
+  cli_msg(-1006, "%js:", name);
+  if (tm_active(t))
+    cli_msg(-1006, "{\"remains\":%t,\"interval\":%ju}", tm_remains(t), num);
+  else
+    cli_msg(-1006, "null");
+}
+
 static void
 rpki_show_proto_info(struct proto *P)
 {
@@ -1008,6 +1018,97 @@ rpki_show_proto_info(struct proto *P)
   }
 }
 
+static void
+rpki_show_proto_info_json(struct proto *P)
+{
+  struct rpki_proto *p = (struct rpki_proto *) P;
+  struct rpki_config *cf = (void *) p->p.cf;
+  struct rpki_cache *cache = p->cache;
+
+  if ((P->proto_state == PS_DOWN_XX) || (P->proto_state == PS_FLUSH))
+    return;
+
+  if (cache)
+  {
+    const char *transport_name = NULL;
+
+    switch (cf->tr_config.type)
+    {
+#if HAVE_LIBSSH
+    case RPKI_TR_SSH:
+      transport_name = "SSHv2";
+      break;
+#endif
+    case RPKI_TR_TCP:;
+      struct rpki_tr_tcp_config *tcp_cf = (void *) cf->tr_config.spec;
+      if (tcp_cf->auth_type == RPKI_TCP_AUTH_MD5)
+	transport_name = "TCP-MD5";
+      else
+	transport_name = "Unprotected over TCP";
+      break;
+    };
+
+    cli_msg(-1006, ",\"Cache server\":%js", cf->hostname);
+    cli_msg(-1006, ",\"Cache port\":%ju", cf->port);
+
+    cli_msg(-1006, ",\"Status\":%js", rpki_cache_state_to_str(cache->state));
+    cli_msg(-1006, ",\"Transport\":%js", transport_name);
+    cli_msg(-1006, ",\"Protocol version\":%ju", cache->version);
+
+    if (cache->request_session_id)
+      cli_msg(-1006, ",\"Session ID\":null");
+    else
+      cli_msg(-1006, ",\"Session ID\":%ju", cache->session_id);
+
+    if (cache->last_update)
+    {
+      cli_msg(-1006, ",\"Serial number\":%ju", cache->serial_num);
+      cli_msg(-1006, ",\"Last update\":%t", current_time() - cache->last_update);
+    }
+    else
+    {
+      cli_msg(-1006, ",\"Serial number\":null");
+      cli_msg(-1006, ",\"Last update\":null");
+    }
+
+    cli_msg(-1006, ",\"timers\":{");
+    rpki_show_proto_info_timer_json("Refresh timer", cache->refresh_interval, cache->refresh_timer);
+    cli_msg(-1006, ",");
+    rpki_show_proto_info_timer_json("Retry timer", cache->retry_interval, cache->retry_timer);
+    cli_msg(-1006, ",");
+    rpki_show_proto_info_timer_json("Expire timer", cache->expire_interval, cache->expire_timer);
+    cli_msg(-1006, "},\"Channels\":{");
+
+    if (p->roa4_channel) {
+      cli_msg(-1006, "\"roa4\":{");
+      channel_show_info_json(p->roa4_channel);
+      cli_msg(-1006, "}");
+    } else {
+      cli_msg(-1006, "\"roa4\":null");
+    }
+
+    cli_msg(-1006, ",");
+    if (p->roa6_channel) {
+      cli_msg(-1006, "\"roa6\":{");
+      channel_show_info_json(p->roa6_channel);
+      cli_msg(-1006, "}");
+    } else {
+      cli_msg(-1006, "\"roa6\":null");
+    }
+
+    cli_msg(-1006, ",");
+    if (p->aspa_channel) {
+      cli_msg(-1006, "\"aspa\":{");
+      channel_show_info_json(p->aspa_channel);
+      cli_msg(-1006, "}");
+    } else {
+      cli_msg(-1006, "\"aspa\":null");
+    }
+
+    cli_msg(-1006, "}");
+  }
+}
+
 
 /*
  * 	RPKI Protocol Configuration
@@ -1080,6 +1181,7 @@ struct protocol proto_rpki = {
   .start = 		rpki_start,
   .postconfig = 	rpki_postconfig,
   .channel_mask =	(NB_ROA4 | NB_ROA6 | NB_ASPA),
+  .show_proto_info_json =	rpki_show_proto_info_json,
   .show_proto_info =	rpki_show_proto_info,
   .shutdown = 		rpki_shutdown,
   .copy_config = 	rpki_copy_config,

Reply via email to