diff options
author | Claudio Jeker <claudio@cvs.openbsd.org> | 2020-05-02 14:33:34 +0000 |
---|---|---|
committer | Claudio Jeker <claudio@cvs.openbsd.org> | 2020-05-02 14:33:34 +0000 |
commit | 430080cae426642f5c6ac9457d27a50bcb1d0bfa (patch) | |
tree | b59822ad50635583bdfc53921ad67f17965b19d6 | |
parent | 29e5a10faeed6b5a9131380023dc689c65f829bf (diff) |
Add support to output data in JSON. This is still work in progress and
the output structures may still change but it should be a good starting
point for poeple to start playing with it.
OK benno@, job@, deraadt@
-rw-r--r-- | usr.sbin/bgpctl/Makefile | 4 | ||||
-rw-r--r-- | usr.sbin/bgpctl/bgpctl.c | 9 | ||||
-rw-r--r-- | usr.sbin/bgpctl/bgpctl.h | 2 | ||||
-rw-r--r-- | usr.sbin/bgpctl/json.c | 224 | ||||
-rw-r--r-- | usr.sbin/bgpctl/json.h | 31 | ||||
-rw-r--r-- | usr.sbin/bgpctl/output_json.c | 954 |
6 files changed, 1218 insertions, 6 deletions
diff --git a/usr.sbin/bgpctl/Makefile b/usr.sbin/bgpctl/Makefile index 36167ef73b1..106495df673 100644 --- a/usr.sbin/bgpctl/Makefile +++ b/usr.sbin/bgpctl/Makefile @@ -1,9 +1,9 @@ -# $OpenBSD: Makefile,v 1.16 2019/12/20 09:16:05 claudio Exp $ +# $OpenBSD: Makefile,v 1.17 2020/05/02 14:33:33 claudio Exp $ .PATH: ${.CURDIR}/../bgpd PROG= bgpctl -SRCS= bgpctl.c output.c parser.c mrtparser.c util.c +SRCS= bgpctl.c output.c output_json.c parser.c mrtparser.c util.c json.c CFLAGS+= -Wall CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes CFLAGS+= -Wmissing-declarations diff --git a/usr.sbin/bgpctl/bgpctl.c b/usr.sbin/bgpctl/bgpctl.c index 434750c4100..085fe5c68b7 100644 --- a/usr.sbin/bgpctl/bgpctl.c +++ b/usr.sbin/bgpctl/bgpctl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: bgpctl.c,v 1.261 2020/05/02 14:31:32 claudio Exp $ */ +/* $OpenBSD: bgpctl.c,v 1.262 2020/05/02 14:33:33 claudio Exp $ */ /* * Copyright (c) 2003 Henning Brauer <henning@openbsd.org> @@ -69,7 +69,7 @@ usage(void) { extern char *__progname; - fprintf(stderr, "usage: %s [-n] [-s socket] command [argument ...]\n", + fprintf(stderr, "usage: %s [-jn] [-s socket] command [argument ...]\n", __progname); exit(1); } @@ -94,12 +94,15 @@ main(int argc, char *argv[]) if (asprintf(&sockname, "%s.%d", SOCKET_NAME, tableid) == -1) err(1, "asprintf"); - while ((ch = getopt(argc, argv, "ns:")) != -1) { + while ((ch = getopt(argc, argv, "jns:")) != -1) { switch (ch) { case 'n': if (++nodescr > 1) usage(); break; + case 'j': + output = &json_output; + break; case 's': sockname = optarg; break; diff --git a/usr.sbin/bgpctl/bgpctl.h b/usr.sbin/bgpctl/bgpctl.h index 922ab9c15ca..03d0a16cc7f 100644 --- a/usr.sbin/bgpctl/bgpctl.h +++ b/usr.sbin/bgpctl/bgpctl.h @@ -34,7 +34,7 @@ struct output { void (*tail)(void); }; -extern const struct output show_output; +extern const struct output show_output, json_output; extern const size_t pt_sizes[]; #define EOL0(flag) ((flag & F_CTL_SSV) ? ';' : '\n') diff --git a/usr.sbin/bgpctl/json.c b/usr.sbin/bgpctl/json.c new file mode 100644 index 00000000000..106fd2f7729 --- /dev/null +++ b/usr.sbin/bgpctl/json.c @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <err.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "json.h" + +#define JSON_MAX_STACK 16 + +enum json_type { + NONE, + START, + ARRAY, + OBJECT +}; + +struct json_stack { + const char *name; + unsigned int count; + enum json_type type; +} stack[JSON_MAX_STACK]; + +char indent[JSON_MAX_STACK + 1]; +int level; + +static void +do_comma_indent(void) +{ + if (stack[level].count++ > 0) + printf(",\n"); + printf("\t%.*s", level, indent); +} + +static void +do_name(const char *name) +{ + if (stack[level].type == ARRAY) + return; + printf("\"%s\": ", name); +} + +static int +do_find(enum json_type type, const char *name) +{ + int i; + + for (i = level; i > 0; i--) + if (type == stack[i].type && + strcmp(name, stack[i].name) == 0) + return i; + + /* not found */ + return -1; +} + +void +json_do_start(void) +{ + memset(indent, '\t', JSON_MAX_STACK); + memset(stack, 0, sizeof(stack)); + level = 0; + stack[level].type = START; + + printf("{\n"); +} + +void +json_do_finish(void) +{ + while (level > 0) + json_do_end(); + printf("\n}\n"); +} + +void +json_do_array(const char *name) +{ + int i, l; + + if ((l = do_find(ARRAY, name)) > 0) { + /* array already in use, close element and move on */ + for (i = level - l; i > 0; i--) + json_do_end(); + return; + } + /* Do not stack arrays, while allowed this is not needed */ + if (stack[level].type == ARRAY) + json_do_end(); + + do_comma_indent(); + do_name(name); + printf("[\n"); + + if (++level >= JSON_MAX_STACK) + errx(1, "json stack too deep"); + + stack[level].name = name; + stack[level].type = ARRAY; + stack[level].count = 0; +} + +void +json_do_object(const char *name) +{ + int i, l; + + if ((l = do_find(OBJECT, name)) > 0) { + /* roll back to that object and close it */ + for (i = level - l; i >= 0; i--) + json_do_end(); + } + + do_comma_indent(); + do_name(name); + printf("{\n"); + + if (++level >= JSON_MAX_STACK) + errx(1, "json stack too deep"); + + stack[level].name = name; + stack[level].type = OBJECT; + stack[level].count = 0; +} + +void +json_do_end(void) +{ + if (stack[level].type == ARRAY) + printf("\n%.*s]", level, indent); + else if (stack[level].type == OBJECT) + printf("\n%.*s}", level, indent); + else + errx(1, "json bad stack state"); + + stack[level].name = NULL; + stack[level].type = NONE; + stack[level].count = 0; + + if (level-- <= 0) + errx(1, "json stack underflow"); + + stack[level].count++; +} + +void +json_do_printf(const char *name, const char *fmt, ...) +{ + va_list ap; + + do_comma_indent(); + + do_name(name); + printf("\""); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf("\""); +} + +void +json_do_hexdump(const char *name, void *buf, size_t len) +{ + uint8_t *data = buf; + size_t i; + + do_comma_indent(); + do_name(name); + printf("\""); + for (i=0; i < len; i++) + printf("%02x", *(data+i)); + printf("\""); +} + +void +json_do_bool(const char *name, int v) +{ + do_comma_indent(); + do_name(name); + if (v) + printf("true"); + else + printf("false"); +} + +void +json_do_uint(const char *name, uint64_t v) +{ + do_comma_indent(); + do_name(name); + printf("%llu", v); +} + +void +json_do_int(const char *name, int64_t v) +{ + do_comma_indent(); + do_name(name); + printf("%lld", v); +} + +void +json_do_double(const char *name, double v) +{ + do_comma_indent(); + do_name(name); + printf("%f", v); +} diff --git a/usr.sbin/bgpctl/json.h b/usr.sbin/bgpctl/json.h new file mode 100644 index 00000000000..ec9d0adbfa5 --- /dev/null +++ b/usr.sbin/bgpctl/json.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdarg.h> +#include <sys/cdefs.h> + +void json_do_start(void); +void json_do_finish(void); +void json_do_array(const char *); +void json_do_object(const char *); +void json_do_end(void); +void json_do_printf(const char *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void json_do_hexdump(const char *, void *, size_t); +void json_do_bool(const char *, int); +void json_do_uint(const char *, uint64_t); +void json_do_int(const char *, int64_t); +void json_do_double(const char *, double); diff --git a/usr.sbin/bgpctl/output_json.c b/usr.sbin/bgpctl/output_json.c new file mode 100644 index 00000000000..ec439efae88 --- /dev/null +++ b/usr.sbin/bgpctl/output_json.c @@ -0,0 +1,954 @@ +/* $OpenBSD: output_json.c,v 1.1 2020/05/02 14:33:33 claudio Exp $ */ + +/* + * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <err.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bgpd.h" +#include "session.h" +#include "rde.h" + +#include "bgpctl.h" +#include "parser.h" +#include "json.h" + +static void +json_head(struct parse_result *res) +{ + json_do_start(); +} + +static void +json_neighbor_capabilities(struct capabilities *capa) +{ + int hascapamp; + uint8_t i; + + for (i = 0; i < AID_MAX; i++) + if (capa->mp[i]) + hascapamp = 1; + if (!hascapamp && !capa->refresh && !capa->grestart.restart && + !capa->as4byte) + return; + + json_do_object("capabilities"); + json_do_bool("as4byte", capa->as4byte); + json_do_bool("refresh", capa->refresh); + + if (hascapamp) { + json_do_array("multiprotocol"); + for (i = 0; i < AID_MAX; i++) + if (capa->mp[i]) + json_do_printf("mp", "%s", aid2str(i)); + json_do_end(); + } + if (capa->grestart.restart) { + int restarted = 0, present = 0; + + for (i = 0; i < AID_MAX; i++) + if (capa->grestart.flags[i] & CAPA_GR_PRESENT) { + present = 1; + if (capa->grestart.flags[i] & CAPA_GR_RESTART) + restarted = 1; + break; + } + json_do_object("graceful_restart"); + json_do_bool("eor", 1); + json_do_bool("restart", restarted); + + if (capa->grestart.timeout) + json_do_uint("timeout", capa->grestart.timeout); + + if (present) { + json_do_array("protocols"); + for (i = 0; i < AID_MAX; i++) + if (capa->grestart.flags[i] & CAPA_GR_PRESENT) { + json_do_object("family"); + json_do_printf("family", "%s", + aid2str(i)); + json_do_bool("preserved", + capa->grestart.flags[i] & + CAPA_GR_FORWARD); + json_do_end(); + } + json_do_end(); + } + + json_do_end(); + } + + json_do_end(); +} + +static void +json_neighbor_stats(struct peer *p) +{ + json_do_object("stats"); + json_do_printf("last_read", "%s", fmt_monotime(p->stats.last_read)); + json_do_printf("last_write", "%s", fmt_monotime(p->stats.last_write)); + + json_do_object("prefixes"); + json_do_uint("sent", p->stats.prefix_out_cnt); + json_do_uint("received", p->stats.prefix_cnt); + json_do_end(); + + json_do_object("message"); + + json_do_object("sent"); + json_do_uint("open", p->stats.msg_sent_open); + json_do_uint("notifications", p->stats.msg_sent_notification); + json_do_uint("updates", p->stats.msg_sent_update); + json_do_uint("keepalives", p->stats.msg_sent_keepalive); + json_do_uint("route_refresh", p->stats.msg_sent_rrefresh); + json_do_uint("total", + p->stats.msg_sent_open + p->stats.msg_sent_notification + + p->stats.msg_sent_update + p->stats.msg_sent_keepalive + + p->stats.msg_sent_rrefresh); + json_do_end(); + + json_do_object("received"); + json_do_uint("open", p->stats.msg_rcvd_open); + json_do_uint("notifications", p->stats.msg_rcvd_notification); + json_do_uint("updates", p->stats.msg_rcvd_update); + json_do_uint("keepalives", p->stats.msg_rcvd_keepalive); + json_do_uint("route_refresh", p->stats.msg_rcvd_rrefresh); + json_do_uint("total", + p->stats.msg_rcvd_open + p->stats.msg_rcvd_notification + + p->stats.msg_rcvd_update + p->stats.msg_rcvd_keepalive + + p->stats.msg_rcvd_rrefresh); + json_do_end(); + + json_do_end(); + + json_do_object("update"); + + json_do_object("sent"); + json_do_uint("updates", p->stats.prefix_sent_update); + json_do_uint("withdraws", p->stats.prefix_sent_withdraw); + json_do_uint("eor", p->stats.prefix_sent_eor); + json_do_end(); + + json_do_object("received"); + json_do_uint("updates", p->stats.prefix_rcvd_update); + json_do_uint("withdraws", p->stats.prefix_rcvd_withdraw); + json_do_uint("eor", p->stats.prefix_rcvd_eor); + json_do_end(); + + json_do_end(); + + json_do_end(); +} + +static void +json_neighbor_full(struct peer *p) +{ + const char *errstr; + + /* config */ + json_do_object("config"); + json_do_bool("template", p->conf.template); + json_do_bool("cloned", p->template != NULL); + json_do_bool("passive", p->conf.passive); + json_do_bool("down", p->conf.down); + json_do_bool("multihop", p->conf.ebgp && p->conf.distance > 1); + if (p->conf.ebgp && p->conf.distance > 1) + json_do_uint("multihop_distance", p->conf.distance); + if (p->conf.max_prefix) { + json_do_uint("max_prefix", p->conf.max_prefix); + if (p->conf.max_prefix_restart) + json_do_uint("max_prefix_restart", + p->conf.max_prefix_restart); + } + if (p->conf.max_out_prefix) { + json_do_uint("max_prefix", p->conf.max_out_prefix); + if (p->conf.max_out_prefix_restart) + json_do_uint("max_out_prefix_restart", + p->conf.max_out_prefix_restart); + } + if (p->auth.method != AUTH_NONE) + json_do_printf("authentication", "%s", + fmt_auth_method(p->auth.method)); + json_do_bool("ttl_security", p->conf.ttlsec); + json_do_uint("holdtime", p->conf.holdtime); + json_do_uint("min_holdtime", p->conf.min_holdtime); + + /* capabilities */ + json_do_bool("announce_capabilities", p->conf.announce_capa); + json_neighbor_capabilities(&p->conf.capabilities); + + json_do_end(); + + + /* stats */ + json_neighbor_stats(p); + + /* errors */ + if (*(p->conf.shutcomm)) + json_do_printf("my_shutdown_reason", "%s", + log_shutcomm(p->conf.shutcomm)); + if (*(p->stats.last_shutcomm)) + json_do_printf("last_shutdown_reason", "%s", + log_shutcomm(p->stats.last_shutcomm)); + errstr = fmt_errstr(p->stats.last_sent_errcode, + p->stats.last_sent_suberr); + if (errstr) + json_do_printf("last_error_sent", "%s", errstr); + errstr = fmt_errstr(p->stats.last_rcvd_errcode, + p->stats.last_rcvd_suberr); + if (errstr) + json_do_printf("last_error_received", "%s", errstr); + + /* connection info */ + if (p->state >= STATE_OPENSENT) { + json_do_object("session"); + json_do_uint("holdtime", p->holdtime); + json_do_uint("keepalive", p->holdtime / 3); + + json_do_object("local"); + json_do_printf("address", "%s", log_addr(&p->local)); + json_do_uint("port", p->local_port); + json_neighbor_capabilities(&p->capa.ann); + json_do_end(); + + json_do_object("remote"); + json_do_printf("address", "%s", log_addr(&p->remote)); + json_do_uint("port", p->remote_port); + json_neighbor_capabilities(&p->capa.peer); + json_do_end(); + + /* capabilities */ + json_neighbor_capabilities(&p->capa.neg); + + json_do_end(); + } +} + +static void +json_neighbor(struct peer *p, struct parse_result *res) +{ + json_do_array("neighbors"); + + json_do_object("neighbor"); + + json_do_printf("remote_as", "%s", log_as(p->conf.remote_as)); + if (p->conf.descr[0]) + json_do_printf("description", "%s", p->conf.descr); + if (!p->conf.template) + json_do_printf("remote_addr", "%s", + log_addr(&p->conf.remote_addr)); + else + json_do_printf("remote_addr", "%s/%u", + log_addr(&p->conf.remote_addr), p->conf.remote_masklen); + if (p->state == STATE_ESTABLISHED) { + struct in_addr ina; + ina.s_addr = p->remote_bgpid; + json_do_printf("bgpid", "%s", inet_ntoa(ina)); + } + json_do_printf("state", "%s", statenames[p->state]); + json_do_printf("last_updown", "%s", fmt_monotime(p->stats.last_updown)); + + switch (res->action) { + case SHOW: + case SHOW_SUMMARY: + case SHOW_SUMMARY_TERSE: + /* only show basic data */ + break; + case SHOW_NEIGHBOR: + case SHOW_NEIGHBOR_TIMERS: + case SHOW_NEIGHBOR_TERSE: + json_neighbor_full(p); + break; + default: + break; + } + + /* keep the object open in case there are timers */ +} + +static void +json_timer(struct ctl_timer *t) +{ + json_do_array("timers"); + + json_do_object("timer"); + json_do_printf("name", "%s", timernames[t->type]); + json_do_int("due", t->val); + json_do_end(); +} + +static void +json_fib(struct kroute_full *kf) +{ + const char *origin; + + json_do_array("fib"); + + json_do_object("fib_entry"); + + json_do_printf("prefix", "%s/%u", log_addr(&kf->prefix), kf->prefixlen); + json_do_uint("priority", kf->priority); + json_do_bool("up", !(kf->flags & F_DOWN)); + if (kf->flags & F_BGPD_INSERTED) + origin = "bgp"; + else if (kf->flags & F_CONNECTED) + origin = "connected"; + else if (kf->flags & F_STATIC) + origin = "static"; + else if (kf->flags & F_DYNAMIC) + origin = "dynamic"; + else + origin = "unknown"; + json_do_printf("origin", "%s", origin); + json_do_bool("used_by_nexthop", kf->flags & F_NEXTHOP); + json_do_bool("blackhole", kf->flags & F_BLACKHOLE); + json_do_bool("reject", kf->flags & F_REJECT); + + if (kf->flags & F_CONNECTED) + json_do_printf("nexthop", "link#%u", kf->ifindex); + else + json_do_printf("nexthop", "%s", log_addr(&kf->nexthop)); + + json_do_end(); +} + +static void +json_fib_table(struct ktable *kt) +{ + json_do_array("fibtables"); + + json_do_object("fibtable"); + json_do_uint("rtableid", kt->rtableid); + json_do_printf("description", "%s", kt->descr); + json_do_bool("coupled", kt->fib_sync); + json_do_bool("admin_change", kt->fib_sync != kt->fib_conf); + json_do_end(); +} + +static void +json_do_interface(struct ctl_show_interface *iface) +{ + json_do_object("interface"); + + json_do_printf("name", "%s", iface->ifname); + json_do_uint("rdomain", iface->rdomain); + json_do_bool("is_up", iface->is_up); + json_do_bool("nh_reachable", iface->nh_reachable); + + if (iface->media[0]) + json_do_printf("media", "%s", iface->media); + + json_do_printf("linkstate", "%s", iface->linkstate); + if (iface->baudrate > 0) + json_do_uint("baudrate", iface->baudrate); + + json_do_end(); +} + +static void +json_nexthop(struct ctl_show_nexthop *nh) +{ + struct kroute *k; + struct kroute6 *k6; + + json_do_array("nexthops"); + + json_do_object("nexthop"); + + json_do_printf("address", "%s", log_addr(&nh->addr)); + json_do_bool("valid", nh->valid); + + if (!nh->krvalid) + goto done; + + switch (nh->addr.aid) { + case AID_INET: + k = &nh->kr.kr4; + json_do_printf("prefix", "%s/%u", inet_ntoa(k->prefix), + k->prefixlen); + json_do_uint("priority", k->priority); + json_do_bool("connected", k->flags & F_CONNECTED); + json_do_printf("nexthop", "%s", inet_ntoa(k->nexthop)); + break; + case AID_INET6: + k6 = &nh->kr.kr6; + json_do_printf("prefix", "%s/%u", log_in6addr(&k6->prefix), + k6->prefixlen); + json_do_uint("priority", k6->priority); + json_do_bool("connected", k6->flags & F_CONNECTED); + json_do_printf("nexthop", "%s", log_in6addr(&k6->nexthop)); + break; + default: + warnx("nexthop: unknown address family"); + goto done; + } + if (nh->iface.ifname[0]) + json_do_interface(&nh->iface); +done: + json_do_end(); + /* keep array open */ +} + +static void +json_interface(struct ctl_show_interface *iface) +{ + json_do_array("interfaces"); + json_do_interface(iface); +} + +static void +json_communities(u_char *data, size_t len, struct parse_result *res) +{ + struct community c; + size_t i; + uint64_t ext; + + if (len % sizeof(c)) { + warnx("communities: bad size"); + return; + } + + for (i = 0; i < len; i += sizeof(c)) { + memcpy(&c, data + i, sizeof(c)); + + switch (c.flags) { + case COMMUNITY_TYPE_BASIC: + json_do_array("communities"); + json_do_printf("community", "%s", + fmt_community(c.data1, c.data2)); + break; + case COMMUNITY_TYPE_LARGE: + json_do_array("large_communities"); + json_do_printf("community", "%s", + fmt_large_community(c.data1, c.data2, c.data3)); + break; + case COMMUNITY_TYPE_EXT: + ext = (uint64_t)c.data3 << 48; + switch (c.data3 >> 8) { + case EXT_COMMUNITY_TRANS_TWO_AS: + case EXT_COMMUNITY_TRANS_OPAQUE: + case EXT_COMMUNITY_TRANS_EVPN: + case EXT_COMMUNITY_NON_TRANS_OPAQUE: + ext |= ((uint64_t)c.data1 & 0xffff) << 32; + ext |= (uint64_t)c.data2; + break; + case EXT_COMMUNITY_TRANS_FOUR_AS: + case EXT_COMMUNITY_TRANS_IPV4: + ext |= (uint64_t)c.data1 << 16; + ext |= (uint64_t)c.data2 & 0xffff; + break; + } + ext = htobe64(ext); + + json_do_array("extended_communities"); + json_do_printf("community", "%s", + fmt_ext_community((void *)&ext)); + break; + } + } +} + +static void +json_do_community(u_char *data, uint16_t len) +{ + uint16_t a, v, i; + + if (len & 0x3) { + json_do_printf("error", "bad length"); + return; + } + + json_do_array("communities"); + + for (i = 0; i < len; i += 4) { + memcpy(&a, data + i, sizeof(a)); + memcpy(&v, data + i + 2, sizeof(v)); + a = ntohs(a); + v = ntohs(v); + json_do_printf("community", "%s", fmt_community(a, v)); + } + + json_do_end(); +} + +static void +json_do_large_community(u_char *data, uint16_t len) +{ + uint32_t a, l1, l2; + u_int16_t i; + + if (len % 12) { + json_do_printf("error", "bad length"); + return; + } + + json_do_array("large_communities"); + + for (i = 0; i < len; i += 12) { + memcpy(&a, data + i, sizeof(a)); + memcpy(&l1, data + i + 4, sizeof(l1)); + memcpy(&l2, data + i + 8, sizeof(l2)); + a = ntohl(a); + l1 = ntohl(l1); + l2 = ntohl(l2); + + json_do_printf("community", "%s", + fmt_large_community(a, l1, l2)); + } + + json_do_end(); +} + +static void +json_do_ext_community(u_char *data, uint16_t len) +{ + uint16_t i; + + if (len & 0x7) { + json_do_printf("error", "bad length"); + return; + } + + json_do_array("extended_communities"); + + for (i = 0; i < len; i += 8) + json_do_printf("community", "%s", fmt_ext_community(data + i)); + + json_do_end(); +} + +static void +json_attr(u_char *data, size_t len, struct parse_result *res) +{ + struct bgpd_addr prefix; + struct in_addr id; + char *aspath; + u_char *path; + uint32_t as; + uint16_t alen, afi, off, short_as; + uint8_t flags, type, safi, aid, prefixlen; + int e4, e2, pos; + + if (len < 3) { + warnx("Too short BGP attrbute"); + return; + } + + flags = data[0]; + type = data[1]; + if (flags & ATTR_EXTLEN) { + if (len < 4) { + warnx("Too short BGP attrbute"); + return; + } + memcpy(&alen, data+2, sizeof(uint16_t)); + alen = ntohs(alen); + data += 4; + len -= 4; + } else { + alen = data[2]; + data += 3; + len -= 3; + } + + /* bad imsg len how can that happen!? */ + if (alen > len) { + warnx("Bad BGP attrbute length"); + return; + } + + json_do_array("attributes"); + + json_do_object("attribute"); + json_do_printf("type", "%s", fmt_attr(type, -1)); + json_do_uint("length", alen); + json_do_object("flags"); + json_do_bool("partial", flags & ATTR_PARTIAL); + json_do_bool("transitive", flags & ATTR_TRANSITIVE); + json_do_bool("optional", flags & ATTR_OPTIONAL); + json_do_end(); + + switch (type) { + case ATTR_ORIGIN: + if (alen == 1) + json_do_printf("origin", "%s", fmt_origin(*data, 0)); + else + json_do_printf("error", "bad length"); + break; + case ATTR_ASPATH: + case ATTR_AS4_PATH: + /* prefer 4-byte AS here */ + e4 = aspath_verify(data, alen, 1); + e2 = aspath_verify(data, alen, 0); + if (e4 == 0 || e4 == AS_ERR_SOFT) { + path = data; + } else if (e2 == 0 || e2 == AS_ERR_SOFT) { + path = aspath_inflate(data, alen, &alen); + if (path == NULL) + errx(1, "aspath_inflate failed"); + } else { + json_do_printf("error", "bad AS-Path"); + break; + } + if (aspath_asprint(&aspath, path, alen) == -1) + err(1, NULL); + json_do_printf("aspath", "%s", aspath); + free(aspath); + if (path != data) + free(path); + break; + case ATTR_NEXTHOP: + if (alen == 4) { + memcpy(&id, data, sizeof(id)); + json_do_printf("nexthop", "%s", inet_ntoa(id)); + } else + json_do_printf("error", "bad length"); + break; + case ATTR_MED: + case ATTR_LOCALPREF: + if (alen == 4) { + uint32_t val; + memcpy(&val, data, sizeof(val)); + json_do_uint("metric", ntohl(val)); + } else + json_do_printf("error", "bad length"); + break; + case ATTR_AGGREGATOR: + case ATTR_AS4_AGGREGATOR: + if (alen == 8) { + memcpy(&as, data, sizeof(as)); + memcpy(&id, data + sizeof(as), sizeof(id)); + as = ntohl(as); + } else if (alen == 6) { + memcpy(&short_as, data, sizeof(short_as)); + memcpy(&id, data + sizeof(short_as), sizeof(id)); + as = ntohs(short_as); + } else { + json_do_printf("error", "bad AS-Path"); + break; + } + json_do_uint("AS", as); + json_do_printf("router_id", "%s", inet_ntoa(id)); + break; + case ATTR_COMMUNITIES: + json_do_community(data, alen); + break; + case ATTR_ORIGINATOR_ID: + if (alen == 4) { + memcpy(&id, data, sizeof(id)); + json_do_printf("originator", "%s", inet_ntoa(id)); + } else + json_do_printf("error", "bad length"); + break; + case ATTR_CLUSTER_LIST: + json_do_array("cluster_list"); + for (off = 0; off + sizeof(id) <= alen; + off += sizeof(id)) { + memcpy(&id, data + off, sizeof(id)); + json_do_printf("cluster_id", "%s", inet_ntoa(id)); + } + json_do_end(); + break; + case ATTR_MP_REACH_NLRI: + case ATTR_MP_UNREACH_NLRI: + if (alen < 3) { +bad_len: + json_do_printf("error", "bad length"); + break; + } + memcpy(&afi, data, 2); + data += 2; + alen -= 2; + afi = ntohs(afi); + safi = *data++; + alen--; + + if (afi2aid(afi, safi, &aid) == -1) { + json_do_printf("error", "bad AFI/SAFI pair: %d/%d", + afi, safi); + break; + } + json_do_printf("family", "%s", aid2str(aid)); + + if (type == ATTR_MP_REACH_NLRI) { + struct bgpd_addr nexthop; + uint8_t nhlen; + if (len == 0) + goto bad_len; + nhlen = *data++; + alen--; + if (nhlen > len) + goto bad_len; + memset(&nexthop, 0, sizeof(nexthop)); + switch (aid) { + case AID_INET6: + nexthop.aid = aid; + if (nhlen != 16 && nhlen != 32) + goto bad_len; + memcpy(&nexthop.v6.s6_addr, data, 16); + break; + case AID_VPN_IPv4: + if (nhlen != 12) + goto bad_len; + nexthop.aid = AID_INET; + memcpy(&nexthop.v4, data + sizeof(uint64_t), + sizeof(nexthop.v4)); + break; + case AID_VPN_IPv6: + if (nhlen != 24) + goto bad_len; + nexthop.aid = AID_INET6; + memcpy(&nexthop.v6, data + sizeof(uint64_t), + sizeof(nexthop.v6)); + break; + default: + json_do_printf("error", "unhandled AID: %d", + aid); + return; + } + /* ignore reserved (old SNPA) field as per RFC4760 */ + data += nhlen + 1; + alen -= nhlen + 1; + + json_do_printf("nexthop", "%s", log_addr(&nexthop)); + } + + json_do_array("NLRI"); + while (alen > 0) { + switch (aid) { + case AID_INET6: + pos = nlri_get_prefix6(data, alen, &prefix, + &prefixlen); + break; + case AID_VPN_IPv4: + pos = nlri_get_vpn4(data, alen, &prefix, + &prefixlen, 1); + break; + case AID_VPN_IPv6: + pos = nlri_get_vpn6(data, alen, &prefix, + &prefixlen, 1); + break; + default: + json_do_printf("error", "unhandled AID: %d", + aid); + return; + } + if (pos == -1) { + json_do_printf("error", "bad %s prefix", + aid2str(aid)); + break; + } + json_do_printf("prefix", "%s/%u", log_addr(&prefix), + prefixlen); + data += pos; + alen -= pos; + } + break; + case ATTR_EXT_COMMUNITIES: + json_do_ext_community(data, alen); + break; + case ATTR_LARGE_COMMUNITIES: + json_do_large_community(data, alen); + break; + case ATTR_ATOMIC_AGGREGATE: + default: + if (alen) + json_do_hexdump("data", data, alen); + break; + } +} + +static void +json_rib(struct ctl_show_rib *r, u_char *asdata, size_t aslen, + struct parse_result *res) +{ + struct in_addr id; + char *aspath; + + json_do_array("rib"); + + json_do_object("rib_entry"); + + json_do_printf("prefix", "%s/%u", log_addr(&r->prefix), r->prefixlen); + + if (aspath_asprint(&aspath, asdata, aslen) == -1) + err(1, NULL); + json_do_printf("aspath", "%s", aspath); + free(aspath); + + json_do_printf("exit_nexthop", "%s", log_addr(&r->exit_nexthop)); + json_do_printf("true_nexthop", "%s", log_addr(&r->true_nexthop)); + + json_do_object("neighbor"); + if (r->descr[0]) + json_do_printf("description", "%s", r->descr); + json_do_printf("remote_addr", "%s", log_addr(&r->remote_addr)); + id.s_addr = htonl(r->remote_id); + json_do_printf("bgp_id", "%s", inet_ntoa(id)); + json_do_end(); + + /* flags */ + json_do_bool("valid", r->flags & F_PREF_ELIGIBLE); + if (r->flags & F_PREF_ACTIVE) + json_do_bool("best", 1); + if (r->flags & F_PREF_INTERNAL) + json_do_printf("source", "%s", "internal"); + else + json_do_printf("source", "%s", "external"); + if (r->flags & F_PREF_STALE) + json_do_bool("stale", 1); + if (r->flags & F_PREF_ANNOUNCE) + json_do_bool("announced", 1); + + /* various attribibutes */ + json_do_printf("ovs", "%s", fmt_ovs(r->validation_state, 0)); + json_do_printf("origin", "%s", fmt_origin(r->origin, 0)); + json_do_uint("metric", r->med); + json_do_uint("localpref", r->local_pref); + json_do_uint("weight", r->weight); + json_do_printf("last_update", "%s", fmt_timeframe(r->age)); + + /* keep the object open for communities and attribuites */ +} + +static void +json_rib_mem_element(const char *name, uint64_t count, uint64_t size, + uint64_t refs) +{ + json_do_object(name); + if (count != UINT64_MAX) + json_do_uint("count", count); + if (size != UINT64_MAX) + json_do_uint("size", size); + if (refs != UINT64_MAX) + json_do_uint("references", refs); + json_do_end(); +} + +static void +json_rib_mem(struct rde_memstats *stats) +{ + size_t pts = 0; + int i; + + json_do_object("memory"); + for (i = 0; i < AID_MAX; i++) { + if (stats->pt_cnt[i] == 0) + continue; + pts += stats->pt_cnt[i] * pt_sizes[i]; + json_rib_mem_element(aid_vals[i].name, stats->pt_cnt[i], + stats->pt_cnt[i] * pt_sizes[i], UINT64_MAX); + } + json_rib_mem_element("rib", stats->rib_cnt, + stats->rib_cnt * sizeof(struct rib_entry), UINT64_MAX); + json_rib_mem_element("prefix", stats->prefix_cnt, + stats->prefix_cnt * sizeof(struct prefix), UINT64_MAX); + json_rib_mem_element("rde_aspath", stats->path_cnt, + stats->path_cnt * sizeof(struct rde_aspath), + stats->path_refs); + json_rib_mem_element("aspath", stats->aspath_cnt, + stats->aspath_size, stats->aspath_refs); + json_rib_mem_element("community_entries", stats->comm_cnt, + stats->comm_cnt * sizeof(struct rde_community), UINT64_MAX); + json_rib_mem_element("community", stats->comm_nmemb, + stats->comm_size * sizeof(struct community), stats->comm_refs); + json_rib_mem_element("attributes_entries", stats->attr_cnt, + stats->attr_cnt * sizeof(struct attr), stats->attr_refs); + json_rib_mem_element("attributes", stats->attr_dcnt, + stats->attr_data, UINT64_MAX); + json_rib_mem_element("total", UINT64_MAX, + pts + stats->prefix_cnt * sizeof(struct prefix) + + stats->rib_cnt * sizeof(struct rib_entry) + + stats->path_cnt * sizeof(struct rde_aspath) + + stats->aspath_size + stats->attr_cnt * sizeof(struct attr) + + stats->attr_data, UINT64_MAX); + json_do_end(); + + json_do_object("sets"); + json_rib_mem_element("as_set", stats->aset_nmemb, + stats->aset_size, UINT64_MAX); + json_rib_mem_element("as_set_tables", stats->aset_cnt, UINT64_MAX, + UINT64_MAX); + json_rib_mem_element("prefix_set", stats->pset_cnt, stats->pset_size, + UINT64_MAX); + json_rib_mem_element("total", UINT64_MAX, + stats->aset_size + stats->pset_size, UINT64_MAX); + json_do_end(); +} + +static void +json_rib_hash(struct rde_hashstats *hash) +{ + double avg, dev; + + json_do_array("hashtables"); + + avg = (double)hash->sum / (double)hash->num; + dev = sqrt(fmax(0, hash->sumq / hash->num - avg * avg)); + + json_do_object("hashtable"); + + json_do_printf("name", "%s", hash->name); + json_do_uint("size", hash->num); + json_do_uint("entries", hash->sum); + json_do_uint("min", hash->min); + json_do_uint("max", hash->max); + json_do_double("avg", avg); + json_do_double("std_dev", dev); + json_do_end(); +} + +static void +json_result(u_int rescode) +{ + if (rescode == 0) + json_do_printf("status", "OK"); + else if (rescode > + sizeof(ctl_res_strerror)/sizeof(ctl_res_strerror[0])) { + json_do_printf("status", "FAILED"); + json_do_printf("error", "unknown error %d", rescode); + } else { + json_do_printf("status", "FAILED"); + json_do_printf("error", "%s", ctl_res_strerror[rescode]); + } +} + +static void +json_tail(void) +{ + json_do_finish(); +} + +const struct output json_output = { + .head = json_head, + .neighbor = json_neighbor, + .timer = json_timer, + .fib = json_fib, + .fib_table = json_fib_table, + .nexthop = json_nexthop, + .interface = json_interface, + .communities = json_communities, + .attr = json_attr, + .rib = json_rib, + .rib_mem = json_rib_mem, + .rib_hash = json_rib_hash, + .result = json_result, + .tail = json_tail +}; |