diff options
Diffstat (limited to 'src/tools/show.c')
-rw-r--r-- | src/tools/show.c | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/src/tools/show.c b/src/tools/show.c new file mode 100644 index 0000000..1662751 --- /dev/null +++ b/src/tools/show.c @@ -0,0 +1,366 @@ +/* Copyright 2015-2016 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */ + +#include <arpa/inet.h> +#include <inttypes.h> +#include <netinet/in.h> +#include <net/if.h> +#include <resolv.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <netdb.h> + +#include "kernel.h" +#include "subcommands.h" +#include "terminal.h" +#include "base64.h" +#include "../uapi.h" + +static int peer_cmp(const void *first, const void *second) +{ + time_t diff; + const struct wgpeer *a = *(const void **)first, *b = *(const void **)second; + if (!a->last_handshake_time.tv_sec && !a->last_handshake_time.tv_usec && (b->last_handshake_time.tv_sec || b->last_handshake_time.tv_usec)) + return 1; + if (!b->last_handshake_time.tv_sec && !b->last_handshake_time.tv_usec && (a->last_handshake_time.tv_sec || a->last_handshake_time.tv_usec)) + return -1; + diff = a->last_handshake_time.tv_sec - b->last_handshake_time.tv_sec; + if (!diff) + diff = a->last_handshake_time.tv_usec - b->last_handshake_time.tv_usec; + if (diff < 0) + return 1; + if (diff > 0) + return -1; + return 0; +} + +static void sort_peers(struct wgdevice *device) +{ + uint8_t *new_device, *pos; + struct wgpeer **peers; + struct wgpeer *peer; + size_t i, len; + + peers = calloc(device->num_peers, sizeof(struct wgpeer *)); + if (!peers) + return; + + len = sizeof(struct wgdevice); + for_each_wgpeer(device, peer, i) + len += sizeof(struct wgpeer) + (peer->num_ipmasks * sizeof(struct wgipmask)); + pos = new_device = malloc(len); + if (!new_device) { + free(peers); + return; + } + + memcpy(pos, device, sizeof(struct wgdevice)); + pos += sizeof(struct wgdevice); + + for_each_wgpeer(device, peer, i) + peers[i] = peer; + + qsort(peers, device->num_peers, sizeof(struct wgpeer *), peer_cmp); + for (i = 0; i < device->num_peers; ++i) { + len = sizeof(struct wgpeer) + (peers[i]->num_ipmasks * sizeof(struct wgipmask)); + memcpy(pos, peers[i], len); + pos += len; + } + free(peers); + + memcpy(device, new_device, pos - new_device); + free(new_device); +} + +static const uint8_t zero[WG_KEY_LEN] = { 0 }; + +static char *key(const unsigned char key[WG_KEY_LEN]) +{ + static char b64[b64_len(WG_KEY_LEN)]; + if (!memcmp(key, zero, WG_KEY_LEN)) + return "(none)"; + memset(b64, 0, b64_len(WG_KEY_LEN)); + b64_ntop(key, WG_KEY_LEN, b64, b64_len(WG_KEY_LEN)); + return b64; +} + +static char *ip(const struct wgipmask *ip) +{ + static char buf[INET6_ADDRSTRLEN + 1]; + memset(buf, 0, INET6_ADDRSTRLEN + 1); + if (ip->family == AF_INET) + inet_ntop(AF_INET, &ip->ip4, buf, INET6_ADDRSTRLEN); + else if (ip->family == AF_INET6) + inet_ntop(AF_INET6, &ip->ip6, buf, INET6_ADDRSTRLEN); + return buf; +} + +static char *endpoint(const struct sockaddr_storage *addr) +{ + char host[4096 + 1]; + char service[512 + 1]; + static char buf[sizeof(host) + sizeof(service) + 4]; + int ret; + socklen_t addr_len = 0; + + memset(buf, 0, sizeof(buf)); + if (addr->ss_family == AF_INET) + addr_len = sizeof(struct sockaddr_in); + else if (addr->ss_family == AF_INET6) + addr_len = sizeof(struct sockaddr_in6); + + ret = getnameinfo((struct sockaddr *)addr, addr_len, host, sizeof(host), service, sizeof(service), NI_DGRAM | NI_NUMERICSERV | NI_NUMERICHOST); + if (ret) + strncpy(buf, gai_strerror(ret), sizeof(buf) - 1); + else + snprintf(buf, sizeof(buf) - 1, (addr->ss_family == AF_INET6 && strchr(host, ':')) ? "[%s]:%s" : "%s:%s", host, service); + return buf; +} + +static char *ago(const struct timeval *t) +{ + static char buf[1024]; + unsigned long long left, years, days, hours, minutes, seconds; + size_t offset = 0; + + left = time(NULL) - t->tv_sec; + years = left / (365 * 24 * 60 * 60); + left = left % (365 * 24 * 60 * 60); + days = left / (24 * 60 * 60); + left = left % (24 * 60 * 60); + hours = left / (60 * 60); + left = left % (60 * 60); + minutes = left / 60; + seconds = left % 60; + + if (years) + offset += snprintf(buf + offset, sizeof(buf) - offset, "%s%llu " TERMINAL_FG_CYAN "year%s" TERMINAL_RESET, offset ? ", " : "", years, years == 1 ? "" : "s"); + if (days) + offset += snprintf(buf + offset, sizeof(buf) - offset, "%s%llu " TERMINAL_FG_CYAN "day%s" TERMINAL_RESET, offset ? ", " : "", days, days == 1 ? "" : "s"); + if (hours) + offset += snprintf(buf + offset, sizeof(buf) - offset, "%s%llu " TERMINAL_FG_CYAN "hour%s" TERMINAL_RESET, offset ? ", " : "", hours, hours == 1 ? "" : "s"); + if (minutes) + offset += snprintf(buf + offset, sizeof(buf) - offset, "%s%llu " TERMINAL_FG_CYAN "minute%s" TERMINAL_RESET, offset ? ", " : "", minutes, minutes == 1 ? "" : "s"); + if (seconds) + offset += snprintf(buf + offset, sizeof(buf) - offset, "%s%llu " TERMINAL_FG_CYAN "second%s" TERMINAL_RESET, offset ? ", " : "", seconds, seconds == 1 ? "" : "s"); + if (offset) + snprintf(buf + offset, sizeof(buf) - offset, " ago"); + else + snprintf(buf, sizeof(buf), "Now"); + + return buf; +} + +static char *bytes(uint64_t b) +{ + static char buf[1024]; + + if (b < 1024ULL) + snprintf(buf, sizeof(buf), "%u " TERMINAL_FG_CYAN "B" TERMINAL_RESET, (unsigned)b); + else if (b < 1024ULL * 1024ULL) + snprintf(buf, sizeof(buf), "%.2f " TERMINAL_FG_CYAN "KiB" TERMINAL_RESET, (double)b / 1024); + else if (b < 1024ULL * 1024ULL * 1024ULL) + snprintf(buf, sizeof(buf), "%.2f " TERMINAL_FG_CYAN "MiB" TERMINAL_RESET, (double)b / (1024 * 1024)); + else if (b < 1024ULL * 1024ULL * 1024ULL * 1024ULL) + snprintf(buf, sizeof(buf), "%.2f " TERMINAL_FG_CYAN "GiB" TERMINAL_RESET, (double)b / (1024 * 1024 * 1024)); + else + snprintf(buf, sizeof(buf), "%.2f " TERMINAL_FG_CYAN "TiB" TERMINAL_RESET, (double)b / (1024 * 1024 * 1024) / 1024); + + return buf; +} + +static const char *COMMAND_NAME = NULL; +static void show_usage(void) +{ + fprintf(stderr, "Usage: %s %s { <interface> | all | interfaces } [public-key | private-key | preshared-key | listen-port | peers | endpoints | allowed-ips | latest-handshake | bandwidth]\n", PROG_NAME, COMMAND_NAME); +} + +static void pretty_print(struct wgdevice *device) +{ + size_t i, j; + struct wgpeer *peer; + struct wgipmask *ipmask; + + terminal_printf(TERMINAL_RESET); + terminal_printf(TERMINAL_FG_GREEN TERMINAL_BOLD "interface" TERMINAL_RESET ": " TERMINAL_FG_GREEN "%s" TERMINAL_RESET "\n", device->interface); + if (memcmp(device->public_key, zero, WG_KEY_LEN)) + terminal_printf(" " TERMINAL_BOLD "public key" TERMINAL_RESET ": %s\n", key(device->public_key)); + if (memcmp(device->private_key, zero, WG_KEY_LEN)) + terminal_printf(" " TERMINAL_BOLD "private key" TERMINAL_RESET ": %s\n", key(device->private_key)); + if (memcmp(device->preshared_key, zero, WG_KEY_LEN)) + terminal_printf(" " TERMINAL_BOLD "pre-shared key" TERMINAL_RESET ": %s\n", key(device->preshared_key)); + if (device->port) + terminal_printf(" " TERMINAL_BOLD "listening port" TERMINAL_RESET ": %u\n", device->port); + if (device->num_peers) { + sort_peers(device); + terminal_printf("\n"); + } + for_each_wgpeer(device, peer, i) { + terminal_printf(TERMINAL_FG_YELLOW TERMINAL_BOLD "peer" TERMINAL_RESET ": " TERMINAL_FG_YELLOW "%s" TERMINAL_RESET "\n", key(peer->public_key)); + if (peer->endpoint.ss_family == AF_INET || peer->endpoint.ss_family == AF_INET6) + terminal_printf(" " TERMINAL_BOLD "endpoint" TERMINAL_RESET ": %s\n", endpoint(&peer->endpoint)); + terminal_printf(" " TERMINAL_BOLD "allowed ips" TERMINAL_RESET ": "); + if (peer->num_ipmasks) { + for_each_wgipmask(peer, ipmask, j) + terminal_printf("%s" TERMINAL_FG_CYAN "/" TERMINAL_RESET "%u%s", ip(ipmask), ipmask->cidr, j == (size_t)peer->num_ipmasks - 1 ? "\n" : ", "); + } else + terminal_printf("(none)\n"); + if (peer->last_handshake_time.tv_sec) + terminal_printf(" " TERMINAL_BOLD "latest handshake" TERMINAL_RESET ": %s\n", ago(&peer->last_handshake_time)); + if (peer->rx_bytes || peer->tx_bytes) { + terminal_printf(" " TERMINAL_BOLD "bandwidth" TERMINAL_RESET ": "); + terminal_printf("%s received, ", bytes(peer->rx_bytes)); + terminal_printf("%s sent\n", bytes(peer->tx_bytes)); + } + if (i + 1 < device->num_peers) + terminal_printf("\n"); + } +} + +static bool ugly_print(struct wgdevice *device, const char *param, bool with_interface) +{ + size_t i, j; + struct wgpeer *peer; + struct wgipmask *ipmask; + if (!strcmp(param, "public-key")) { + if (with_interface) + printf("%s\t", device->interface); + printf("%s\n", key(device->public_key)); + } else if (!strcmp(param, "private-key")) { + if (with_interface) + printf("%s\t", device->interface); + printf("%s\n", key(device->private_key)); + } else if (!strcmp(param, "preshared-key")) { + if (with_interface) + printf("%s\t", device->interface); + printf("%s\n", key(device->preshared_key)); + } else if (!strcmp(param, "listen-port")) { + if (with_interface) + printf("%s\t", device->interface); + printf("%u\n", device->port); + } else if (!strcmp(param, "endpoints")) { + if (with_interface) + printf("%s\t", device->interface); + for_each_wgpeer(device, peer, i) { + printf("%s\t", key(peer->public_key)); + if (peer->endpoint.ss_family == AF_INET || peer->endpoint.ss_family == AF_INET6) + printf("%s\n", endpoint(&peer->endpoint)); + else + printf("(none)\n"); + } + } else if (!strcmp(param, "allowed-ips")) { + for_each_wgpeer(device, peer, i) { + if (with_interface) + printf("%s\t", device->interface); + printf("%s\t", key(peer->public_key)); + if (peer->num_ipmasks) { + for_each_wgipmask(peer, ipmask, j) + printf("%s/%u%s", ip(ipmask), ipmask->cidr, j == (size_t)peer->num_ipmasks - 1 ? "\n" : ", "); + } else + printf("(none)\n"); + } + } else if (!strcmp(param, "latest-handshakes")) { + for_each_wgpeer(device, peer, i) { + if (with_interface) + printf("%s\t", device->interface); + printf("%s\t%llu\n", key(peer->public_key), (unsigned long long)peer->last_handshake_time.tv_sec); + } + } else if (!strcmp(param, "bandwidth")) { + for_each_wgpeer(device, peer, i) { + if (with_interface) + printf("%s\t", device->interface); + printf("%s\t%" PRIu64 "\t%" PRIu64 "\n", key(peer->public_key), (uint64_t)peer->rx_bytes, (uint64_t)peer->tx_bytes); + } + } else if (!strcmp(param, "peers")) { + for_each_wgpeer(device, peer, i) { + if (with_interface) + printf("%s\t", device->interface); + printf("%s\n", key(peer->public_key)); + } + } else { + fprintf(stderr, "Invalid parameter: `%s`\n", param); + show_usage(); + return false; + } + return true; +} + +int show_main(int argc, char *argv[]) +{ + int ret = 0; + COMMAND_NAME = argv[0]; + + if (argc > 3) { + show_usage(); + return 1; + } + + if (argc == 1 || !strcmp(argv[1], "all")) { + char *interfaces = kernel_get_wireguard_interfaces(), *interface; + if (!interfaces) { + perror("Unable to get devices"); + return 1; + } + interface = interfaces; + for (size_t len = 0; (len = strlen(interface)); interface += len + 1) { + struct wgdevice *device = NULL; + if (kernel_get_device(&device, interface) < 0) { + perror("Unable to get device"); + continue; + } + if (argc == 3) { + if (!ugly_print(device, argv[2], true)) { + ret = 1; + free(device); + break; + } + } else { + pretty_print(device); + if (strlen(interface + len + 1)) + printf("\n"); + } + free(device); + } + free(interfaces); + } else if (!strcmp(argv[1], "interfaces")) { + char *interfaces, *interface; + if (argc > 2) { + show_usage(); + return 1; + } + interfaces = kernel_get_wireguard_interfaces(); + if (!interfaces) { + perror("Unable to get devices"); + return 1; + } + interface = interfaces; + for (size_t len = 0; (len = strlen(interface)); interface += len + 1) + printf("%s%c", interface, strlen(interface + len + 1) ? ' ' : '\n'); + free(interfaces); + } else if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help") || !strcmp(argv[1], "help"))) + show_usage(); + else { + struct wgdevice *device = NULL; + if (!kernel_has_wireguard_interface(argv[1])) { + fprintf(stderr, "`%s` is not a valid WireGuard interface\n", argv[1]); + show_usage(); + return 1; + } + if (kernel_get_device(&device, argv[1]) < 0) { + perror("Unable to get device"); + show_usage(); + return 1; + } + if (argc == 3) { + if (!ugly_print(device, argv[2], false)) + ret = 1; + } else + pretty_print(device); + free(device); + } + return ret; +} |