This patch adds a new option to `wg show` called json. When specified it'll output the WG interface in json output. It works with both given an interface or all. # wg show wg1 json [ { "interface": "wg1", "public_key": "Yh0kKjoqnJsxbCsTkQ/3uncEhdqa+EtJXCYcVzMdugs=", "private_key": "MKVTz8NCXL0uaE77MJfgIWSSy1AfqSmLFRx16oxmrmk=", "port": "51831", "fwmark": null, "peers": [ { "peer": "GzY59HlXkCkfXl9uSkEFTHzOtBsxQFKu3KWGFH5P9Qc=", "preshared_key": null, "endpoint": "172.16.3.104:51834", "allowed_ips": [ "10.0.3.0/24", "224.0.0.0/8", "172.16.0.0/16" ], "latest_handshake": 1581909869, "transfer": { "received": 3949216, "sent": 4531428 }, "persistent_keepalive": 30 } ] } ] Note however, that this will print out the private key. Signed-off-by: Matthew Oliver <matt@oliver.net.au> --- src/show.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/src/show.c b/src/show.c index e772339..f8c7543 100644 --- a/src/show.c +++ b/src/show.c @@ -202,7 +202,62 @@ static char *bytes(uint64_t b) static const char *COMMAND_NAME; static void show_usage(void) { - fprintf(stderr, "Usage: %s %s { <interface> | all | interfaces } [public-key | private-key | listen-port | fwmark | peers | preshared-keys | endpoints | allowed-ips | latest-handshakes | transfer | persistent-keepalive | dump]\n", PROG_NAME, COMMAND_NAME); + fprintf(stderr, "Usage: %s %s { <interface> | all | interfaces } [public-key | private-key | listen-port | fwmark | peers | preshared-keys | endpoints | allowed-ips | latest-handshakes | transfer | persistent-keepalive | dump | json]\n", PROG_NAME, COMMAND_NAME); +} + +static void json_print(struct wgdevice *device) +{ + struct wgpeer *peer; + struct wgallowedip *allowedip; + terminal_printf(TERMINAL_RESET); + terminal_printf(" {\n"); + terminal_printf(" \"interface\": \"%s\",\n", device->name); + terminal_printf(" \"public_key\": \"%s\",\n", key(device->public_key)); + terminal_printf(" \"private_key\": \"%s\",\n", key(device->private_key)); + terminal_printf(" \"port\": \"%d\",\n", device->listen_port); + if (device->fwmark) + terminal_printf(" \"fwmark\": \"0x%x\",\n", device->fwmark); + else + terminal_printf(" \"fwmark\": null,\n"); + terminal_printf(" \"peers\": [\n"); + sort_peers(device); + for_each_wgpeer(device, peer) { + terminal_printf(" {\n"); + terminal_printf(" \"peer\": \"%s\",\n", key(peer->public_key)); + if (peer->flags & WGPEER_HAS_PRESHARED_KEY) + terminal_printf(" \"preshared_key\": \"%s\",\n", key(peer->preshared_key)); + else + terminal_printf(" \"preshared_key\": null,\n"); + if (peer->endpoint.addr.sa_family == AF_INET || peer->endpoint.addr.sa_family == AF_INET6) + terminal_printf(" \"endpoint\": \"%s\",\n", endpoint(&peer->endpoint.addr)); + else + terminal_printf(" \"endpoint\": null,\n"); + terminal_printf(" \"allowed_ips\": [\n"); + if (peer->first_allowedip) { + terminal_printf(" "); + for_each_wgallowedip(peer, allowedip) + terminal_printf("\"%s/%u\"%s", ip(allowedip), allowedip->cidr, allowedip->next_allowedip ? ", " : "\n"); + } + terminal_printf(" ],\n"); + if (peer->last_handshake_time.tv_sec) + terminal_printf(" \"latest_handshake\": %ld,\n", peer->last_handshake_time.tv_sec); + else + terminal_printf(" \"latest_handshake\": null,\n"); + terminal_printf(" \"transfer\": {\n"); + terminal_printf(" \"received\": %ld,\n", peer->rx_bytes); + terminal_printf(" \"sent\": %ld\n", peer->tx_bytes); + terminal_printf(" },\n"); + if (peer->persistent_keepalive_interval) + terminal_printf(" \"persistent_keepalive\": %d\n", peer->persistent_keepalive_interval); + else + terminal_printf(" \"persistent_keepalive\": null\n"); + if (peer->next_peer) + terminal_printf(" },\n"); + else + terminal_printf(" }\n"); + } + terminal_printf(" ]\n"); + terminal_printf(" }"); } static void pretty_print(struct wgdevice *device) @@ -396,6 +451,8 @@ int show_main(int argc, char *argv[]) } ret = !!*interfaces; interface = interfaces; + if (argc == 3 && !strcmp(argv[2], "json")) + terminal_printf("[\n"); for (size_t len = 0; (len = strlen(interface)); interface += len + 1) { struct wgdevice *device = NULL; @@ -404,7 +461,13 @@ int show_main(int argc, char *argv[]) continue; } if (argc == 3) { - if (!ugly_print(device, argv[2], true)) { + if (!strcmp(argv[2], "json")) { + json_print(device); + if (strlen(interface + len + 1)) + terminal_printf(",\n"); + else + terminal_printf("\n"); + } else if (!ugly_print(device, argv[2], true)) { ret = 1; free_wgdevice(device); break; @@ -417,6 +480,8 @@ int show_main(int argc, char *argv[]) free_wgdevice(device); ret = 0; } + if (argc == 3 && !strcmp(argv[2], "json")) + terminal_printf("]\n"); free(interfaces); } else if (!strcmp(argv[1], "interfaces")) { char *interfaces, *interface; @@ -444,7 +509,11 @@ int show_main(int argc, char *argv[]) return 1; } if (argc == 3) { - if (!ugly_print(device, argv[2], false)) + if (!strcmp(argv[2], "json")) { + terminal_printf("[\n"); + json_print(device); + terminal_printf("\n]\n"); + } else if (!ugly_print(device, argv[2], false)) ret = 1; } else pretty_print(device); -- 2.22.0 _______________________________________________ WireGuard mailing list WireGuard@lists.zx2c4.com https://lists.zx2c4.com/mailman/listinfo/wireguard
[-- Attachment #1.1: Type: text/plain, Size: 7819 bytes --] Someone asked about this in the IRC channel, so quickly wrote up a patch to add it. No pressure if you do/don't want to merge it :) Matt On Mon, Feb 17, 2020 at 2:57 PM Matthew Oliver <matt@oliver.net.au> wrote: > This patch adds a new option to `wg show` called json. When > specified it'll output the WG interface in json output. > > It works with both given an interface or all. > > # wg show wg1 json > [ > { > "interface": "wg1", > "public_key": "Yh0kKjoqnJsxbCsTkQ/3uncEhdqa+EtJXCYcVzMdugs=", > "private_key": "MKVTz8NCXL0uaE77MJfgIWSSy1AfqSmLFRx16oxmrmk=", > "port": "51831", > "fwmark": null, > "peers": [ > { > "peer": "GzY59HlXkCkfXl9uSkEFTHzOtBsxQFKu3KWGFH5P9Qc=", > "preshared_key": null, > "endpoint": "172.16.3.104:51834", > "allowed_ips": [ > "10.0.3.0/24", "224.0.0.0/8", "172.16.0.0/16" > ], > "latest_handshake": 1581909869, > "transfer": { > "received": 3949216, > "sent": 4531428 > }, > "persistent_keepalive": 30 > } > ] > } > ] > > Note however, that this will print out the private key. > > Signed-off-by: Matthew Oliver <matt@oliver.net.au> > --- > src/show.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++--- > 1 file changed, 72 insertions(+), 3 deletions(-) > > diff --git a/src/show.c b/src/show.c > index e772339..f8c7543 100644 > --- a/src/show.c > +++ b/src/show.c > @@ -202,7 +202,62 @@ static char *bytes(uint64_t b) > static const char *COMMAND_NAME; > static void show_usage(void) > { > - fprintf(stderr, "Usage: %s %s { <interface> | all | interfaces } > [public-key | private-key | listen-port | fwmark | peers | preshared-keys | > endpoints | allowed-ips | latest-handshakes | transfer | > persistent-keepalive | dump]\n", PROG_NAME, COMMAND_NAME); > + fprintf(stderr, "Usage: %s %s { <interface> | all | interfaces } > [public-key | private-key | listen-port | fwmark | peers | preshared-keys | > endpoints | allowed-ips | latest-handshakes | transfer | > persistent-keepalive | dump | json]\n", PROG_NAME, COMMAND_NAME); > +} > + > +static void json_print(struct wgdevice *device) > +{ > + struct wgpeer *peer; > + struct wgallowedip *allowedip; > + terminal_printf(TERMINAL_RESET); > + terminal_printf(" {\n"); > + terminal_printf(" \"interface\": \"%s\",\n", device->name); > + terminal_printf(" \"public_key\": \"%s\",\n", > key(device->public_key)); > + terminal_printf(" \"private_key\": \"%s\",\n", > key(device->private_key)); > + terminal_printf(" \"port\": \"%d\",\n", device->listen_port); > + if (device->fwmark) > + terminal_printf(" \"fwmark\": \"0x%x\",\n", > device->fwmark); > + else > + terminal_printf(" \"fwmark\": null,\n"); > + terminal_printf(" \"peers\": [\n"); > + sort_peers(device); > + for_each_wgpeer(device, peer) { > + terminal_printf(" {\n"); > + terminal_printf(" \"peer\": \"%s\",\n", > key(peer->public_key)); > + if (peer->flags & WGPEER_HAS_PRESHARED_KEY) > + terminal_printf(" \"preshared_key\": > \"%s\",\n", key(peer->preshared_key)); > + else > + terminal_printf(" \"preshared_key\": > null,\n"); > + if (peer->endpoint.addr.sa_family == AF_INET || > peer->endpoint.addr.sa_family == AF_INET6) > + terminal_printf(" \"endpoint\": > \"%s\",\n", endpoint(&peer->endpoint.addr)); > + else > + terminal_printf(" \"endpoint\": null,\n"); > + terminal_printf(" \"allowed_ips\": [\n"); > + if (peer->first_allowedip) { > + terminal_printf(" "); > + for_each_wgallowedip(peer, allowedip) > + terminal_printf("\"%s/%u\"%s", > ip(allowedip), allowedip->cidr, allowedip->next_allowedip ? ", " : "\n"); > + } > + terminal_printf(" ],\n"); > + if (peer->last_handshake_time.tv_sec) > + terminal_printf(" \"latest_handshake\": > %ld,\n", peer->last_handshake_time.tv_sec); > + else > + terminal_printf(" \"latest_handshake\": > null,\n"); > + terminal_printf(" \"transfer\": {\n"); > + terminal_printf(" \"received\": %ld,\n", > peer->rx_bytes); > + terminal_printf(" \"sent\": %ld\n", > peer->tx_bytes); > + terminal_printf(" },\n"); > + if (peer->persistent_keepalive_interval) > + terminal_printf(" > \"persistent_keepalive\": %d\n", peer->persistent_keepalive_interval); > + else > + terminal_printf(" > \"persistent_keepalive\": null\n"); > + if (peer->next_peer) > + terminal_printf(" },\n"); > + else > + terminal_printf(" }\n"); > + } > + terminal_printf(" ]\n"); > + terminal_printf(" }"); > } > > static void pretty_print(struct wgdevice *device) > @@ -396,6 +451,8 @@ int show_main(int argc, char *argv[]) > } > ret = !!*interfaces; > interface = interfaces; > + if (argc == 3 && !strcmp(argv[2], "json")) > + terminal_printf("[\n"); > for (size_t len = 0; (len = strlen(interface)); interface > += len + 1) { > struct wgdevice *device = NULL; > > @@ -404,7 +461,13 @@ int show_main(int argc, char *argv[]) > continue; > } > if (argc == 3) { > - if (!ugly_print(device, argv[2], true)) { > + if (!strcmp(argv[2], "json")) { > + json_print(device); > + if (strlen(interface + len + 1)) > + terminal_printf(",\n"); > + else > + terminal_printf("\n"); > + } else if (!ugly_print(device, argv[2], > true)) { > ret = 1; > free_wgdevice(device); > break; > @@ -417,6 +480,8 @@ int show_main(int argc, char *argv[]) > free_wgdevice(device); > ret = 0; > } > + if (argc == 3 && !strcmp(argv[2], "json")) > + terminal_printf("]\n"); > free(interfaces); > } else if (!strcmp(argv[1], "interfaces")) { > char *interfaces, *interface; > @@ -444,7 +509,11 @@ int show_main(int argc, char *argv[]) > return 1; > } > if (argc == 3) { > - if (!ugly_print(device, argv[2], false)) > + if (!strcmp(argv[2], "json")) { > + terminal_printf("[\n"); > + json_print(device); > + terminal_printf("\n]\n"); > + } else if (!ugly_print(device, argv[2], false)) > ret = 1; > } else > pretty_print(device); > -- > 2.22.0 > > [-- Attachment #1.2: Type: text/html, Size: 11166 bytes --] [-- Attachment #2: Type: text/plain, Size: 148 bytes --] _______________________________________________ WireGuard mailing list WireGuard@lists.zx2c4.com https://lists.zx2c4.com/mailman/listinfo/wireguard
[-- Attachment #1.1: Type: text/plain, Size: 8399 bytes --] wg(8) is a unix text utility. However, we have a script in contrib for accomplishing this same thing: https://git.zx2c4.com/wireguard-tools/tree/contrib/json On Sun, Feb 23, 2020, 11:36 Matthew Oliver <matt@oliver.net.au> wrote: > Someone asked about this in the IRC channel, so quickly wrote up a patch > to add it. No pressure if you do/don't want to merge it :) > > Matt > > On Mon, Feb 17, 2020 at 2:57 PM Matthew Oliver <matt@oliver.net.au> wrote: > >> This patch adds a new option to `wg show` called json. When >> specified it'll output the WG interface in json output. >> >> It works with both given an interface or all. >> >> # wg show wg1 json >> [ >> { >> "interface": "wg1", >> "public_key": "Yh0kKjoqnJsxbCsTkQ/3uncEhdqa+EtJXCYcVzMdugs=", >> "private_key": "MKVTz8NCXL0uaE77MJfgIWSSy1AfqSmLFRx16oxmrmk=", >> "port": "51831", >> "fwmark": null, >> "peers": [ >> { >> "peer": "GzY59HlXkCkfXl9uSkEFTHzOtBsxQFKu3KWGFH5P9Qc=", >> "preshared_key": null, >> "endpoint": "172.16.3.104:51834", >> "allowed_ips": [ >> "10.0.3.0/24", "224.0.0.0/8", "172.16.0.0/16" >> ], >> "latest_handshake": 1581909869, >> "transfer": { >> "received": 3949216, >> "sent": 4531428 >> }, >> "persistent_keepalive": 30 >> } >> ] >> } >> ] >> >> Note however, that this will print out the private key. >> >> Signed-off-by: Matthew Oliver <matt@oliver.net.au> >> --- >> src/show.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++--- >> 1 file changed, 72 insertions(+), 3 deletions(-) >> >> diff --git a/src/show.c b/src/show.c >> index e772339..f8c7543 100644 >> --- a/src/show.c >> +++ b/src/show.c >> @@ -202,7 +202,62 @@ static char *bytes(uint64_t b) >> static const char *COMMAND_NAME; >> static void show_usage(void) >> { >> - fprintf(stderr, "Usage: %s %s { <interface> | all | interfaces } >> [public-key | private-key | listen-port | fwmark | peers | preshared-keys | >> endpoints | allowed-ips | latest-handshakes | transfer | >> persistent-keepalive | dump]\n", PROG_NAME, COMMAND_NAME); >> + fprintf(stderr, "Usage: %s %s { <interface> | all | interfaces } >> [public-key | private-key | listen-port | fwmark | peers | preshared-keys | >> endpoints | allowed-ips | latest-handshakes | transfer | >> persistent-keepalive | dump | json]\n", PROG_NAME, COMMAND_NAME); >> +} >> + >> +static void json_print(struct wgdevice *device) >> +{ >> + struct wgpeer *peer; >> + struct wgallowedip *allowedip; >> + terminal_printf(TERMINAL_RESET); >> + terminal_printf(" {\n"); >> + terminal_printf(" \"interface\": \"%s\",\n", device->name); >> + terminal_printf(" \"public_key\": \"%s\",\n", >> key(device->public_key)); >> + terminal_printf(" \"private_key\": \"%s\",\n", >> key(device->private_key)); >> + terminal_printf(" \"port\": \"%d\",\n", device->listen_port); >> + if (device->fwmark) >> + terminal_printf(" \"fwmark\": \"0x%x\",\n", >> device->fwmark); >> + else >> + terminal_printf(" \"fwmark\": null,\n"); >> + terminal_printf(" \"peers\": [\n"); >> + sort_peers(device); >> + for_each_wgpeer(device, peer) { >> + terminal_printf(" {\n"); >> + terminal_printf(" \"peer\": \"%s\",\n", >> key(peer->public_key)); >> + if (peer->flags & WGPEER_HAS_PRESHARED_KEY) >> + terminal_printf(" \"preshared_key\": >> \"%s\",\n", key(peer->preshared_key)); >> + else >> + terminal_printf(" \"preshared_key\": >> null,\n"); >> + if (peer->endpoint.addr.sa_family == AF_INET || >> peer->endpoint.addr.sa_family == AF_INET6) >> + terminal_printf(" \"endpoint\": >> \"%s\",\n", endpoint(&peer->endpoint.addr)); >> + else >> + terminal_printf(" \"endpoint\": null,\n"); >> + terminal_printf(" \"allowed_ips\": [\n"); >> + if (peer->first_allowedip) { >> + terminal_printf(" "); >> + for_each_wgallowedip(peer, allowedip) >> + terminal_printf("\"%s/%u\"%s", >> ip(allowedip), allowedip->cidr, allowedip->next_allowedip ? ", " : "\n"); >> + } >> + terminal_printf(" ],\n"); >> + if (peer->last_handshake_time.tv_sec) >> + terminal_printf(" \"latest_handshake\": >> %ld,\n", peer->last_handshake_time.tv_sec); >> + else >> + terminal_printf(" \"latest_handshake\": >> null,\n"); >> + terminal_printf(" \"transfer\": {\n"); >> + terminal_printf(" \"received\": %ld,\n", >> peer->rx_bytes); >> + terminal_printf(" \"sent\": %ld\n", >> peer->tx_bytes); >> + terminal_printf(" },\n"); >> + if (peer->persistent_keepalive_interval) >> + terminal_printf(" >> \"persistent_keepalive\": %d\n", peer->persistent_keepalive_interval); >> + else >> + terminal_printf(" >> \"persistent_keepalive\": null\n"); >> + if (peer->next_peer) >> + terminal_printf(" },\n"); >> + else >> + terminal_printf(" }\n"); >> + } >> + terminal_printf(" ]\n"); >> + terminal_printf(" }"); >> } >> >> static void pretty_print(struct wgdevice *device) >> @@ -396,6 +451,8 @@ int show_main(int argc, char *argv[]) >> } >> ret = !!*interfaces; >> interface = interfaces; >> + if (argc == 3 && !strcmp(argv[2], "json")) >> + terminal_printf("[\n"); >> for (size_t len = 0; (len = strlen(interface)); interface >> += len + 1) { >> struct wgdevice *device = NULL; >> >> @@ -404,7 +461,13 @@ int show_main(int argc, char *argv[]) >> continue; >> } >> if (argc == 3) { >> - if (!ugly_print(device, argv[2], true)) { >> + if (!strcmp(argv[2], "json")) { >> + json_print(device); >> + if (strlen(interface + len + 1)) >> + terminal_printf(",\n"); >> + else >> + terminal_printf("\n"); >> + } else if (!ugly_print(device, argv[2], >> true)) { >> ret = 1; >> free_wgdevice(device); >> break; >> @@ -417,6 +480,8 @@ int show_main(int argc, char *argv[]) >> free_wgdevice(device); >> ret = 0; >> } >> + if (argc == 3 && !strcmp(argv[2], "json")) >> + terminal_printf("]\n"); >> free(interfaces); >> } else if (!strcmp(argv[1], "interfaces")) { >> char *interfaces, *interface; >> @@ -444,7 +509,11 @@ int show_main(int argc, char *argv[]) >> return 1; >> } >> if (argc == 3) { >> - if (!ugly_print(device, argv[2], false)) >> + if (!strcmp(argv[2], "json")) { >> + terminal_printf("[\n"); >> + json_print(device); >> + terminal_printf("\n]\n"); >> + } else if (!ugly_print(device, argv[2], false)) >> ret = 1; >> } else >> pretty_print(device); >> -- >> 2.22.0 >> >> _______________________________________________ > WireGuard mailing list > WireGuard@lists.zx2c4.com > https://lists.zx2c4.com/mailman/listinfo/wireguard > [-- Attachment #1.2: Type: text/html, Size: 12276 bytes --] [-- Attachment #2: Type: text/plain, Size: 148 bytes --] _______________________________________________ WireGuard mailing list WireGuard@lists.zx2c4.com https://lists.zx2c4.com/mailman/listinfo/wireguard
On E, 2020-02-17 at 15:47 +1100, Matthew Oliver wrote: > Someone asked about this in the IRC channel, so quickly wrote up a > patch to add it. No pressure if you do/don't want to merge it :) If you have wireguard-tools package installed then it should be possible to use the incldued wg-json script to do the same sudo /usr/share/wireguard-tools/examples/json/wg-json _______________________________________________ WireGuard mailing list WireGuard@lists.zx2c4.com https://lists.zx2c4.com/mailman/listinfo/wireguard
> On 23 Feb 2020, at 12:45, Arti Zirk <arti.zirk@gmail.com> wrote: > > On E, 2020-02-17 at 15:47 +1100, Matthew Oliver wrote: >> Someone asked about this in the IRC channel, so quickly wrote up a >> patch to add it. No pressure if you do/don't want to merge it :) > > If you have wireguard-tools package installed then it should be > possible to use the incldued wg-json script to do the same > > sudo /usr/share/wireguard-tools/examples/json/wg-json When I built my tools in python on top of the wg command I was please to see a machine interface, but find the \t separated list of values odd to find in a modern app. I think it would be a good addition to have the JSON output without the need to use a contrib script. Many text commands output in JSON or XML for ease of interfacing. The JSON would also provide a nice way to add more attributes in the future. Barry _______________________________________________ WireGuard mailing list WireGuard@lists.zx2c4.com https://lists.zx2c4.com/mailman/listinfo/wireguard
Hi Barry, Please read the reply below with a smile, it is just friendly sarcasm underlining my personal view. On Tue, Feb 25, 2020 at 1:50 AM Barry Scott <barry@barrys-emacs.org> wrote: > > On 23 Feb 2020, at 12:45, Arti Zirk <arti.zirk@gmail.com> wrote: > > > > On E, 2020-02-17 at 15:47 +1100, Matthew Oliver wrote: > >> Someone asked about this in the IRC channel, so quickly wrote up a > >> patch to add it. No pressure if you do/don't want to merge it :) > > > > If you have wireguard-tools package installed then it should be > > possible to use the incldued wg-json script to do the same > > > > sudo /usr/share/wireguard-tools/examples/json/wg-json > > When I built my tools in python on top of the wg command I was > please to see a machine interface, but find the \t separated list > of values odd to find in a modern app. > ... and it doesn't support dark-mode for the matter! Neither has it autoupdate script! And no PDF documentation generated by doxygen! And no JNI ! May be it is just a simple & easy, robust tool that does one thing and does it good and fast? > I think it would be a good addition to have the JSON output > without the need to use a contrib script. Many text commands > output in JSON or XML for ease of interfacing. > So, sarcasm aside, I really think keeping it simple is better. If you need JSON for some reason, use the script in contrib and patch if it doesn't work as needed, or suggest another script or way, without strapping a kitchen sink to the core. And for tighter integration, use the Netlink API (uapi/wireguard.h) directly. > The JSON would also provide a nice way to add more attributes > in the future. > Adding "more attributes" is definitely not on the todo list, AFAIK, and given that in the remote case of adding a secondary output format (json) it may need to be backward compatible with the default text output, this is not a reason. In the sense that if we add a plugin for Alexa to explain a wg output, auto-magically translating it of course, there is a high chance of it being only a secondary priority (to the simple text output). Cheers, Kalin. _______________________________________________ WireGuard mailing list WireGuard@lists.zx2c4.com https://lists.zx2c4.com/mailman/listinfo/wireguard
On 23/02/2020 12.00, Jason A. Donenfeld wrote: > wg(8) is a unix text utility. ip(8) supports --json since 2017. _______________________________________________ WireGuard mailing list WireGuard@lists.zx2c4.com https://lists.zx2c4.com/mailman/listinfo/wireguard