929 lines
27 KiB
C++
929 lines
27 KiB
C++
#include "stdafx.h"
|
|
#include "tunsafe_types.h"
|
|
#include "netapi.h"
|
|
#include "crypto/curve25519/curve25519-donna.h"
|
|
#include "util.h"
|
|
#include "wireguard_proto.h"
|
|
#include <string.h>
|
|
#include <algorithm>
|
|
|
|
#if defined(OS_WIN)
|
|
#include "util_win32.h"
|
|
#include "service_pipe_win32.h"
|
|
#include "service_win32_constants.h"
|
|
#endif // defined(OS_WIN)
|
|
|
|
#if defined(OS_POSIX)
|
|
#include <sys/stat.h>
|
|
#include <sys/un.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#endif // defined(OS_WIN)
|
|
|
|
|
|
#pragma comment(lib, "ws2_32.lib")
|
|
|
|
#define ANSI_RESET "\x1b[0m"
|
|
#define ANSI_BOLD "\x1b[1m"
|
|
#define ANSI_FG_BLACK "\x1b[30m"
|
|
#define ANSI_FG_RED "\x1b[31m"
|
|
#define ANSI_FG_GREEN "\x1b[32m"
|
|
#define ANSI_FG_YELLOW "\x1b[33m"
|
|
#define ANSI_FG_BLUE "\x1b[34m"
|
|
#define ANSI_FG_MAGENTA "\x1b[35m"
|
|
#define ANSI_FG_CYAN "\x1b[36m"
|
|
#define ANSI_FG_WHITE "\x1b[37m"
|
|
|
|
#if defined(OS_WIN)
|
|
#define EXENAME "tunsafe"
|
|
|
|
static bool SendMessageToService(HANDLE pipe, int message, const void *data, size_t data_size) {
|
|
uint8 *temp = new uint8[data_size + 5];
|
|
*(uint32*)temp = (uint32)(data_size + 1);
|
|
temp[4] = (uint8)message;
|
|
memcpy(temp + 5, data, data_size);
|
|
// Write the whole thing
|
|
DWORD pos = 0, bytes_to_write = (DWORD)(data_size + 5), bytes_written;
|
|
do {
|
|
if (!WriteFile(pipe, temp + pos, bytes_to_write, &bytes_written, NULL)) {
|
|
fprintf(stderr, "Error writing to service pipe, error = %d\n", GetLastError());
|
|
break;
|
|
}
|
|
pos += bytes_written;
|
|
bytes_to_write -= bytes_written;
|
|
} while (bytes_to_write != 0);
|
|
delete[] temp;
|
|
return (bytes_to_write == 0);
|
|
}
|
|
|
|
static bool ReadExactBytesFromPipe(HANDLE pipe, const void *data, DWORD bytes_to_read) {
|
|
DWORD pos = 0, n;
|
|
do {
|
|
if (!ReadFile(pipe, (uint8*)data + pos, bytes_to_read, &n, NULL))
|
|
return false;
|
|
if (n == 0)
|
|
return false; // premature eof..
|
|
pos += n;
|
|
bytes_to_read -= n;
|
|
} while (bytes_to_read != 0);
|
|
return true;
|
|
}
|
|
|
|
static bool ReadMessageFromService(HANDLE pipe, int *message, std::string *data) {
|
|
uint8 header[5];
|
|
uint32 message_size;
|
|
|
|
if (!ReadExactBytesFromPipe(pipe, header, 5) || (message_size = *(uint32*)header) == 0) {
|
|
fprintf(stderr, "Error reading from service pipe, error = %d\n", GetLastError());
|
|
return false;
|
|
}
|
|
*message = header[4];
|
|
data->resize(message_size - 1);
|
|
if (message_size - 1 != 0 && !ReadExactBytesFromPipe(pipe, data->data(), message_size - 1)) {
|
|
fprintf(stderr, "Error reading from service pipe, error = %d\n", GetLastError());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
struct ServiceLoginMessage {
|
|
uint64 version;
|
|
char interfac[kTsMaxDevnameSize];
|
|
bool want_state_updates;
|
|
bool want_create_interface;
|
|
};
|
|
|
|
static std::vector<GuidAndDevName> g_tap_adapters;
|
|
static bool g_did_get_adapters;
|
|
static const std::vector<GuidAndDevName> &GetTapAdapterInfo() {
|
|
if (!g_did_get_adapters) {
|
|
g_did_get_adapters = true;
|
|
GetTapAdapterInfo(&g_tap_adapters);
|
|
}
|
|
return g_tap_adapters;
|
|
}
|
|
|
|
static const char *GetGuidFromInterfaceName(const char *name) {
|
|
for (const GuidAndDevName &e : GetTapAdapterInfo())
|
|
if (strcmp(e.name, name) == 0)
|
|
return e.guid;
|
|
return NULL;
|
|
}
|
|
|
|
static const char *GetInterfaceNameFromGuid(const char *guid) {
|
|
for (const GuidAndDevName &e : GetTapAdapterInfo())
|
|
if (strcmp(e.guid, guid) == 0)
|
|
return e.name;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static HANDLE ConnectToService(const char *devname, bool want_updates, bool want_create = false) {
|
|
ServiceLoginMessage msg = {0};
|
|
msg.version = TUNSAFE_SERVICE_PROTOCOL_VERSION;
|
|
msg.want_state_updates = want_updates;
|
|
msg.want_create_interface = want_create;
|
|
|
|
// Rename devname to a guid
|
|
if (devname) {
|
|
const char *guid = (devname[0] == '{' || devname[0] == 0) ? devname : GetGuidFromInterfaceName(devname);
|
|
if (!guid) {
|
|
fprintf(stderr, "Interface '%s' not found\n", devname);
|
|
return NULL;
|
|
}
|
|
my_strlcpy(msg.interfac, sizeof(msg.interfac), guid);
|
|
}
|
|
|
|
for (;;) {
|
|
HANDLE pipe = CreateFile(TUNSAFE_PIPE_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL,
|
|
OPEN_EXISTING, 0, NULL);
|
|
if (pipe != INVALID_HANDLE_VALUE) {
|
|
if (!SendMessageToService(pipe, TS_SERVICE_REQ_LOGIN, &msg, sizeof(msg))) {
|
|
CloseHandle(pipe);
|
|
pipe = NULL;
|
|
}
|
|
return pipe;
|
|
}
|
|
DWORD error = GetLastError();
|
|
if (error != ERROR_PIPE_BUSY) {
|
|
fprintf(stderr, "Error connecting to TunSafe service, error = %d\n", error);
|
|
if (error == ERROR_FILE_NOT_FOUND)
|
|
fprintf(stderr, "Please check that the TunSafe service is started\n");
|
|
return NULL;
|
|
}
|
|
if (!WaitNamedPipe(TUNSAFE_PIPE_NAME, 10000)) {
|
|
fprintf(stderr, "Error connecting to TunSafe service, timed out.\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool CommunicateWithService(const char *devname, const std::string &query, std::string *reply) {
|
|
HANDLE pipe = ConnectToService(devname, false);
|
|
int message_code;
|
|
bool rv = false;
|
|
|
|
if (pipe != NULL &&
|
|
SendMessageToService(pipe, TS_SERVICE_REQ_TEXT_PROTOCOL, query.data(), query.size()) &&
|
|
ReadMessageFromService(pipe, &message_code, reply)) {
|
|
if (message_code == TS_SERVICE_REQ_TEXT_PROTOCOL_REPLY) {
|
|
rv = true;
|
|
} else {
|
|
if (message_code == TS_SERVICE_MSG_ERROR_REPLY) {
|
|
fprintf(stderr, "Error: %s\n", reply->c_str());
|
|
} else {
|
|
fprintf(stderr, "Unknown reply (%d) from TunSafe service.\n", message_code);
|
|
}
|
|
}
|
|
}
|
|
CloseHandle(pipe);
|
|
return rv;
|
|
}
|
|
|
|
static bool GetInterfaceList(std::string *result) {
|
|
HANDLE pipe = ConnectToService(NULL, false);
|
|
int message_code;
|
|
bool rv = false;
|
|
|
|
if (pipe != NULL &&
|
|
SendMessageToService(pipe, TS_SERVICE_REQ_GETINTERFACES, NULL, 0) &&
|
|
ReadMessageFromService(pipe, &message_code, result)) {
|
|
if (message_code == TS_SERVICE_REQ_GETINTERFACES_REPLY) {
|
|
rv = true;
|
|
} else {
|
|
fprintf(stderr, "GetInterfaceList: bad reply\n");
|
|
}
|
|
}
|
|
CloseHandle(pipe);
|
|
return rv;
|
|
}
|
|
|
|
// Supports stripping ansi colors
|
|
static bool g_supports_ansi_color;
|
|
static void ansi_printf(const char *s, ...) {
|
|
va_list va;
|
|
va_start(va, s);
|
|
if (g_supports_ansi_color) {
|
|
vprintf(s, va);
|
|
} else {
|
|
char buf[1024];
|
|
vsnprintf(buf, sizeof(buf), s, va);
|
|
char *s = buf, *d = s, c;
|
|
for (; (c = *s) != 0;) {
|
|
if (c == '\x1b' && s[1] == '[') {
|
|
s += 2;
|
|
while ((c = *s) >= '0' && c <= '9')
|
|
s++;
|
|
if (c == 'm')
|
|
s++;
|
|
} else {
|
|
*d++ = c;
|
|
s++;
|
|
}
|
|
}
|
|
*d = 0;
|
|
fputs(buf, stdout);
|
|
}
|
|
va_end(va);
|
|
}
|
|
|
|
#endif // defined(OS_WIN)
|
|
|
|
#if defined(OS_POSIX)
|
|
#define EXENAME "tunsafe"
|
|
#define ansi_printf printf
|
|
|
|
static const char *GetGuidFromInterfaceName(const char *name) {
|
|
return name;
|
|
}
|
|
|
|
static const char *GetInterfaceNameFromGuid(const char *guid) {
|
|
return guid;
|
|
}
|
|
|
|
|
|
static int OpenUserspaceInterface(const char *iface) {
|
|
struct stat st;
|
|
struct sockaddr_un un = { 0 };
|
|
int fd = -1, rv;
|
|
|
|
if (strchr(iface, '/') != NULL) {
|
|
fprintf(stderr, "Unable to open usermode socket: No such device\n");
|
|
goto getout;
|
|
}
|
|
|
|
snprintf(un.sun_path, sizeof(un.sun_path), "/var/run/wireguard/%s.sock", iface);
|
|
if (stat(un.sun_path, &st) < 0) {
|
|
perror("Unable to open usermode socket");
|
|
goto getout;
|
|
}
|
|
|
|
if (!S_ISSOCK(st.st_mode)) {
|
|
fprintf(stderr, "Unable to open usermode socket: No such device\n");
|
|
goto getout;
|
|
}
|
|
|
|
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (fd < 0)
|
|
goto getout;
|
|
|
|
un.sun_family = AF_UNIX;
|
|
if (connect(fd, (struct sockaddr *)&un, sizeof(un)) < 0) {
|
|
if (errno == ECONNREFUSED)
|
|
unlink(un.sun_path);
|
|
else
|
|
perror("Error opening wireguard usermode interface socket");
|
|
goto getout;
|
|
}
|
|
return fd;
|
|
|
|
getout:
|
|
if (fd >= 0)
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
|
|
static bool GetInterfaceList(std::string *result) {
|
|
struct dirent *dent;
|
|
|
|
DIR *dir = opendir("/var/run/wireguard/");
|
|
if (!dir)
|
|
return errno == ENOENT;
|
|
|
|
while ((dent = readdir(dir)) != NULL) {
|
|
size_t len = strlen(dent->d_name);
|
|
static const char kSuffix[6] = ".sock";
|
|
if (len >= sizeof(kSuffix) - 1 &&
|
|
memcmp(&dent->d_name[len - (sizeof(kSuffix) - 1)], kSuffix, sizeof(kSuffix) - 1) == 0) {
|
|
dent->d_name[len - (sizeof(kSuffix) - 1)] = '\n';
|
|
result->append(dent->d_name, len - (sizeof(kSuffix) - 1) + 1);
|
|
}
|
|
}
|
|
closedir(dir);
|
|
return true;
|
|
}
|
|
|
|
static bool CommunicateWithService(const char *devname, const std::string &query, std::string *reply) {
|
|
ssize_t n;
|
|
char buf[4096];
|
|
bool rv = false;
|
|
|
|
reply->clear();
|
|
|
|
int fd = OpenUserspaceInterface(devname);
|
|
if (fd == -1)
|
|
return false;
|
|
|
|
for(size_t pos = 0; query.size() - pos; pos += n) {
|
|
n = write(fd, query.data() + pos, query.size() - pos);
|
|
if (n <= 0) {
|
|
perror("Error writing to service pipe");
|
|
goto getout;
|
|
}
|
|
}
|
|
|
|
for(;;) {
|
|
n = read(fd, buf, sizeof(buf));
|
|
if (n <= 0) {
|
|
if (n == 0) {
|
|
// ensure that it ends with \n\n
|
|
if (reply->size() >= 2 && (*reply)[reply->size() - 1] == '\n' && (*reply)[reply->size() - 2] == '\n') {
|
|
rv = true;
|
|
} else {
|
|
fprintf(stderr, "Bad reply from service pipe\n");
|
|
}
|
|
} else {
|
|
perror("Error reading from service pipe");
|
|
}
|
|
break;
|
|
}
|
|
reply->append(buf, n);
|
|
}
|
|
|
|
getout:
|
|
close(fd);
|
|
return rv;
|
|
}
|
|
|
|
static int HandleStopCommand(int argc, char **argv) {
|
|
if (argc != 1) {
|
|
fprintf(stderr, "Usage: " EXENAME " stop <interface>\n");
|
|
return 1;
|
|
}
|
|
struct sockaddr_un un;
|
|
struct stat st;
|
|
const char *iface = argv[0];
|
|
if (strchr(iface, '/')) {
|
|
fprintf(stderr, "No such interface\n");
|
|
return 1;
|
|
}
|
|
snprintf(un.sun_path, sizeof(un.sun_path), "/var/run/wireguard/%s.sock", iface);
|
|
if (unlink(un.sun_path) == -1) {
|
|
perror("unlink");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif // defined(OS_POSIX)
|
|
|
|
void ShowHelp() {
|
|
fprintf(stderr,
|
|
"Usage: " EXENAME " <cmd> [<args>]\n\n"
|
|
#if defined(OS_POSIX)
|
|
" " EXENAME " filename.conf\n\n"
|
|
#endif // defined(OS_POSIX)
|
|
"Available subcommands:\n"
|
|
" show: Shows the configuration and status of the interfaces\n"
|
|
" set: Change the configuration or the peer list\n"
|
|
" start: Start TunSafe on an interface\n"
|
|
" stop: Stop TunSafe on an interface\n"
|
|
#if defined(OS_WIN)
|
|
" log: Display recent log entries\n"
|
|
#endif // defined(OS_WIN)
|
|
" genkey: Writes a new private key to stdout\n"
|
|
" genpsk: Writes a new preshared key to stdout\n"
|
|
" pubkey: Reads a private key from stdin and writes its public key to stdout\n"
|
|
"To see more help about a subcommand, pass --help to it\n");
|
|
}
|
|
|
|
|
|
|
|
static bool ParseHexKeyToBase64(const char *key, char base64key[WG_PUBLIC_KEY_LEN_BASE64 + 1]) {
|
|
uint8 keybuf[32];
|
|
if (!ParseHexString(key, keybuf, 32))
|
|
return false;
|
|
return base64_encode(keybuf, 32, base64key, WG_PUBLIC_KEY_LEN_BASE64 + 1, NULL) != NULL;
|
|
}
|
|
|
|
static char *FormatTransferPart(char *buf, size_t bufsize, uint64 n) {
|
|
if (n < 1024)
|
|
snprintf(buf, bufsize, "%u " ANSI_FG_CYAN "B" ANSI_RESET, (unsigned)n);
|
|
else if (n < 1024 * 1024)
|
|
snprintf(buf, bufsize, "%.2f " ANSI_FG_CYAN "KiB" ANSI_RESET, (double)n * (1.0 / 1024));
|
|
else if (n < 1024 * 1024 * 1024)
|
|
snprintf(buf, bufsize, "%.2f " ANSI_FG_CYAN "MiB" ANSI_RESET, (double)n * (1.0 / 1024 / 1024));
|
|
else if (n < 1024ull * 1024 * 1024 * 1024)
|
|
snprintf(buf, bufsize, "%.2f " ANSI_FG_CYAN "GiB" ANSI_RESET, (double)n * (1.0 / 1024 / 1024 / 1024));
|
|
else
|
|
snprintf(buf, bufsize, "%.2f " ANSI_FG_CYAN "TiB" ANSI_RESET, (double)n * (1.0 / 1024 / 1024 / 1024 / 1024));
|
|
return buf;
|
|
}
|
|
|
|
static size_t PrintTime(char *buf, size_t bufsize, uint64 n) {
|
|
size_t pos = 0;
|
|
uint64 years = n / (365 * 24 * 60 * 60);
|
|
uint32 n32 = n % (365 * 24 * 60 * 60);
|
|
if (years)
|
|
pos += snprintf(buf + pos, bufsize - pos, "%llu " ANSI_FG_CYAN "year%s" ANSI_RESET ", ", (unsigned long long)years, (years == 1) ? "" : "s");
|
|
uint32 days = n32 / (24 * 60 * 60);
|
|
n32 %= (24 * 60 * 60);
|
|
if (days)
|
|
pos += snprintf(buf + pos, bufsize - pos, "%u " ANSI_FG_CYAN "day%s" ANSI_RESET ", ", days, (days == 1) ? "" : "s");
|
|
uint32 hours = n32 / (60 * 60);
|
|
n32 %= (60 * 60);
|
|
if (hours)
|
|
pos += snprintf(buf + pos, bufsize - pos, "%u " ANSI_FG_CYAN "hour%s" ANSI_RESET ", ", hours, (hours == 1) ? "" : "s");
|
|
uint32 minutes = n32 / 60;
|
|
if (minutes)
|
|
pos += snprintf(buf + pos, bufsize - pos, "%u " ANSI_FG_CYAN "minute%s" ANSI_RESET ", ", minutes, (minutes == 1) ? "" : "s");
|
|
uint32 seconds = n32 % 60;
|
|
if (seconds)
|
|
pos += snprintf(buf + pos, bufsize - pos, "%u " ANSI_FG_CYAN "second%s" ANSI_RESET ", ", seconds, (seconds == 1) ? "" : "s");
|
|
if (pos)
|
|
buf[pos -= 2] = '\0';
|
|
return pos;
|
|
}
|
|
|
|
static char *PrintHandshake(char *buf, size_t bufsize, uint64 secs) {
|
|
time_t now = time(NULL);
|
|
if (now == secs) {
|
|
snprintf(buf, bufsize, "Now");
|
|
} else if (now < (int64)secs) {
|
|
snprintf(buf, bufsize, ANSI_FG_RED "System clock going backwards" ANSI_RESET);
|
|
} else {
|
|
size_t pos = PrintTime(buf, bufsize - 4, now - secs);
|
|
memcpy(buf + pos, " ago", 5);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
static void AppendIpToString(const char *value, std::string *result) {
|
|
if (!result->empty())
|
|
(*result) += ", ";
|
|
const char *slash = strchr(value, '/');
|
|
if (slash) {
|
|
result->append(value, slash - value);
|
|
result->append(ANSI_FG_CYAN "/" ANSI_RESET);
|
|
result->append(slash + 1);
|
|
} else {
|
|
result->append(value);
|
|
}
|
|
}
|
|
|
|
static int ShowUserFriendlyForDevice(char *devname) {
|
|
std::string reply;
|
|
std::vector<std::pair<char*, char*>> kv;
|
|
std::string ips;
|
|
|
|
if (!CommunicateWithService(devname, "get=1\n\n", &reply))
|
|
return 1;
|
|
|
|
if (!ParseConfigKeyValue(&reply[0], &kv)) {
|
|
getout_fail:
|
|
fprintf(stderr, "Unable to parse response");
|
|
return 1;
|
|
}
|
|
|
|
size_t i = 0;
|
|
char base64key[WG_PUBLIC_KEY_LEN_BASE64 + 1];
|
|
char base64psk[WG_PUBLIC_KEY_LEN_BASE64 + 1];
|
|
int listen_port = 0;
|
|
base64key[0] = 0;
|
|
|
|
// Parse all interface level keys
|
|
for (; i < kv.size(); i++) {
|
|
char *key = kv[i].first, *value = kv[i].second;
|
|
if (strcmp(key, "private_key") == 0) {
|
|
uint8 binkey[32];
|
|
if (!ParseHexString(value, binkey, sizeof(binkey)))
|
|
goto getout_fail;
|
|
if (!IsOnlyZeros(binkey, 32)) {
|
|
curve25519_donna(binkey, binkey, kCurve25519Basepoint);
|
|
base64_encode(binkey, sizeof(binkey), base64key, sizeof(base64key), NULL);
|
|
}
|
|
} else if (strcmp(key, "address") == 0) {
|
|
AppendIpToString(value, &ips);
|
|
} else if (strcmp(key, "listen_port") == 0) {
|
|
listen_port = atoi(value);
|
|
} else if (strcmp(key, "public_key") == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
const char *interfacename = (devname[0] == '{') ? GetInterfaceNameFromGuid(devname) : devname;
|
|
|
|
ansi_printf(ANSI_RESET ANSI_FG_GREEN ANSI_BOLD "interface" ANSI_RESET ": " ANSI_FG_GREEN "%s" ANSI_RESET "\n",
|
|
interfacename);
|
|
if (base64key[0]) {
|
|
ansi_printf(" " ANSI_BOLD "public key" ANSI_RESET ": %s\n"
|
|
" " ANSI_BOLD "private key" ANSI_RESET ": (hidden)\n", base64key);
|
|
}
|
|
if (listen_port)
|
|
ansi_printf(" " ANSI_BOLD "listening port" ANSI_RESET ": %d\n", listen_port);
|
|
if (ips.size())
|
|
ansi_printf(" " ANSI_BOLD "address" ANSI_RESET ": %s\n", ips.c_str());
|
|
|
|
const char *endpoint = NULL;
|
|
uint64 rx_bytes, tx_bytes, last_handshake_time_sec;
|
|
int persistent_keepalive;
|
|
char text[256];
|
|
bool clear_state = true;
|
|
|
|
// Parse peer level keys
|
|
for (; i < kv.size(); i++) {
|
|
char *key = kv[i].first, *value = kv[i].second;
|
|
|
|
if (clear_state) {
|
|
base64key[0] = base64psk[0] = 0;
|
|
endpoint = NULL;
|
|
ips.clear();
|
|
persistent_keepalive = 0;
|
|
last_handshake_time_sec = tx_bytes = rx_bytes = 0;
|
|
clear_state = false;
|
|
}
|
|
if (strcmp(key, "public_key") == 0) {
|
|
if (!ParseHexKeyToBase64(value, base64key))
|
|
goto getout_fail;
|
|
} else if (strcmp(key, "preshared_key") == 0) {
|
|
if (!ParseHexKeyToBase64(value, base64psk))
|
|
goto getout_fail;
|
|
} else if (strcmp(key, "tx_bytes") == 0) {
|
|
tx_bytes = strtoull(value, NULL, 0);
|
|
} else if (strcmp(key, "rx_bytes") == 0) {
|
|
rx_bytes = strtoull(value, NULL, 0);
|
|
} else if (strcmp(key, "allowed_ip") == 0) {
|
|
AppendIpToString(value, &ips);
|
|
} else if (strcmp(key, "persistent_keepalive_interval") == 0) {
|
|
persistent_keepalive = atoi(value);
|
|
} else if (strcmp(key, "endpoint") == 0) {
|
|
endpoint = value;
|
|
} else if (strcmp(key, "last_handshake_time_sec") == 0) {
|
|
last_handshake_time_sec = strtoull(value, NULL, 0);
|
|
}
|
|
if (i == kv.size() - 1 || strcmp(kv[i + 1].first, "public_key") == 0) {
|
|
if (!base64key[0])
|
|
goto getout_fail;
|
|
ansi_printf("\n" ANSI_FG_YELLOW ANSI_BOLD "peer" ANSI_RESET ": " ANSI_FG_YELLOW "%s" ANSI_RESET "\n", base64key);
|
|
if (base64psk[0])
|
|
ansi_printf(" " ANSI_BOLD "preshared key" ANSI_RESET ": (hidden)\n");
|
|
if (endpoint)
|
|
ansi_printf(" " ANSI_BOLD "endpoint" ANSI_RESET ": %s\n", endpoint);
|
|
ansi_printf(" " ANSI_BOLD "allowed ips" ANSI_RESET ": %s\n", ips.size() ? ips.c_str() : "(none)");
|
|
if (last_handshake_time_sec)
|
|
ansi_printf(" " ANSI_BOLD "latest handshake" ANSI_RESET ": %s\n", PrintHandshake(text, sizeof(text), last_handshake_time_sec));
|
|
if (tx_bytes | rx_bytes) {
|
|
ansi_printf(" " ANSI_BOLD "transfer" ANSI_RESET ": %s received, ", FormatTransferPart(text, sizeof(text), rx_bytes));
|
|
ansi_printf("%s sent\n", FormatTransferPart(text, sizeof(text), tx_bytes));
|
|
}
|
|
if (persistent_keepalive) {
|
|
PrintTime(text, sizeof(text), persistent_keepalive);
|
|
ansi_printf(" " ANSI_BOLD "persistent keepalive" ANSI_RESET ": every %s\n", text);
|
|
}
|
|
clear_state = true;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int HandleShowCommand(int argc, char **argv) {
|
|
if (argc != 0 && strcmp(argv[0], "--help") == 0) {
|
|
fprintf(stderr, "Usage: ts show { <interface> | all | interfaces }\n");
|
|
return 0;
|
|
}
|
|
|
|
std::vector<char*> interfaces;
|
|
std::string interfaces_str;
|
|
|
|
if (argc == 0 || strcmp(argv[0], "all") == 0) {
|
|
if (!GetInterfaceList(&interfaces_str))
|
|
return 1;
|
|
SplitString(&interfaces_str[0], '\n', &interfaces);
|
|
|
|
bool want_newline = false;
|
|
for (char *interfac : interfaces) {
|
|
if (want_newline)
|
|
ansi_printf("\n");
|
|
want_newline = true;
|
|
if (ShowUserFriendlyForDevice(interfac))
|
|
return 1;
|
|
}
|
|
} else if (strcmp(argv[0], "interfaces") == 0) {
|
|
if (!GetInterfaceList(&interfaces_str))
|
|
return 1;
|
|
SplitString(&interfaces_str[0], '\n', &interfaces);
|
|
|
|
for (char *interfac : interfaces) {
|
|
const char *name = GetInterfaceNameFromGuid(interfac);
|
|
if (name)
|
|
ansi_printf("%s\n", name);
|
|
}
|
|
} else {
|
|
return ShowUserFriendlyForDevice(argv[0]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void AppendCommand(std::string *result, const char *tag, const char *value) {
|
|
result->append(tag);
|
|
result->append("=");
|
|
result->append(value);
|
|
result->append("\n");
|
|
}
|
|
|
|
static bool ConvertBase64KeyToHex(const char *s, char key[65]) {
|
|
uint8 tmp[32];
|
|
size_t size = 32;
|
|
if (!base64_decode((uint8*)s, strlen(s), tmp, &size) || size != 32)
|
|
return false;
|
|
PrintHexString(tmp, 32, key);
|
|
return true;
|
|
}
|
|
|
|
static int HandleSetCommand(int argc, char **argv) {
|
|
std::string command, reply;
|
|
std::vector<char*> ss;
|
|
char hexkey[65];
|
|
|
|
if (argc == 0) {
|
|
fprintf(stderr, "Usage: " EXENAME " set <interface> [address <address>] [listen-port <port>] [private-key <file path>] "
|
|
"[peer <base64 public key> [remove] [preshared-key <file path>] [endpoint <ip>:<port>] "
|
|
"[persistent-keepalive <interval seconds>] [allowed-ips <ip1>/<cidr1>[,<ip2>/<cidr2>]] ]");
|
|
return 1;
|
|
}
|
|
char **argv_end = argv + argc;
|
|
const char *interfc = *argv++;
|
|
|
|
command = "set=1\n";
|
|
|
|
bool in_interface_section = true;
|
|
bool in_peer_section = false;
|
|
bool did_clear_allowed_ips = false;
|
|
|
|
while (argv != argv_end) {
|
|
const char *key = *argv++;
|
|
|
|
if (argv != argv_end) {
|
|
if (in_interface_section) {
|
|
if (strcmp(key, "listen-port") == 0) {
|
|
AppendCommand(&command, "listen_port", *argv++);
|
|
continue;
|
|
} else if (strcmp(key, "address") == 0) {
|
|
AppendCommand(&command, "address", *argv++);
|
|
continue;
|
|
} else if (strcmp(key, "private-key") == 0) {
|
|
if (!ConvertBase64KeyToHex(*argv++, hexkey))
|
|
goto invalid_key_format;
|
|
AppendCommand(&command, "private_key", hexkey);
|
|
continue;
|
|
}
|
|
}
|
|
if (strcmp(key, "peer") == 0) {
|
|
in_interface_section = false;
|
|
in_peer_section = true;
|
|
did_clear_allowed_ips = false;
|
|
if (!ConvertBase64KeyToHex(*argv++, hexkey))
|
|
goto invalid_key_format;
|
|
AppendCommand(&command, "public_key", hexkey);
|
|
|
|
continue;
|
|
}
|
|
if (in_peer_section) {
|
|
if (strcmp(key, "preshared-key") == 0) {
|
|
if (!ConvertBase64KeyToHex(*argv++, hexkey))
|
|
goto invalid_key_format;
|
|
AppendCommand(&command, "preshared_key", hexkey);
|
|
continue;
|
|
} else if (strcmp(key, "endpoint") == 0) {
|
|
AppendCommand(&command, "endpoint", *argv++);
|
|
continue;
|
|
} else if (strcmp(key, "persistent-keepalive") == 0) {
|
|
AppendCommand(&command, "persistent_keepalive_interval", *argv++);
|
|
continue;
|
|
} else if (strcmp(key, "allowed-ips") == 0) {
|
|
if (!did_clear_allowed_ips) {
|
|
AppendCommand(&command, "replace_allowed_ips", "true");
|
|
did_clear_allowed_ips = true;
|
|
}
|
|
SplitString(*argv++, ',', &ss);
|
|
for (char *x : ss)
|
|
AppendCommand(&command, "allowed_ip", x);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (in_peer_section) {
|
|
if (strcmp(key, "remove") == 0) {
|
|
in_peer_section = false;
|
|
AppendCommand(&command, "remove", "true");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "Invalid argument: %s\n", key);
|
|
return 1;
|
|
|
|
invalid_key_format:
|
|
fprintf(stderr, "Key is not in the correct format: '%s'\n", argv[-1]);
|
|
return 1;
|
|
}
|
|
|
|
command.append("\n");
|
|
|
|
if (!CommunicateWithService(interfc, command, &reply))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(OS_WIN)
|
|
static int HandleLogCommand() {
|
|
HANDLE pipe = ConnectToService(NULL, true);
|
|
|
|
int message_code;
|
|
std::string reply;
|
|
|
|
while (pipe != NULL && ReadMessageFromService(pipe, &message_code, &reply) && message_code == TS_SERVICE_MSG_LOGLINE)
|
|
ansi_printf("%s\n", reply.c_str());
|
|
|
|
CloseHandle(pipe);
|
|
return 0;
|
|
}
|
|
|
|
static int HandleStartCommand(int argc, char **argv) {
|
|
if (argc < 1 || argc > 2 || strcmp(argv[0], "--help") == 0) {
|
|
fprintf(stderr, "Usage: " EXENAME " start <interface> [<filename>]\n");
|
|
return 1;
|
|
}
|
|
|
|
const char *devname = argv[0];
|
|
HANDLE pipe = ConnectToService(devname, false, true);
|
|
int message_code;
|
|
std::string reply;
|
|
|
|
const char *path = (argc == 1) ? "" : argv[1];
|
|
|
|
// Tell the server to startup a new interface
|
|
if (pipe == NULL ||
|
|
!SendMessageToService(pipe, TS_SERVICE_REQ_START, path, strlen(path) + 1) ||
|
|
!ReadMessageFromService(pipe, &message_code, &reply))
|
|
return 1;
|
|
|
|
if (message_code == TS_SERVICE_MSG_ERROR_REPLY) {
|
|
fprintf(stderr, "%s\n", reply.c_str());
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int HandleStopCommand(int argc, char **argv) {
|
|
if (argc != 1) {
|
|
fprintf(stderr, "Usage: " EXENAME " stop <interface>\n");
|
|
return 1;
|
|
}
|
|
|
|
const char *devname = argv[0];
|
|
HANDLE pipe = ConnectToService(devname, false);
|
|
|
|
// Tell the server to stop the interface
|
|
if (pipe == NULL ||
|
|
!SendMessageToService(pipe, TS_SERVICE_REQ_STOP, NULL, 0))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
#endif // defined(OS_WIN)
|
|
|
|
|
|
// Returns -1 on invalid subcommand
|
|
int HandleCommandLine(int argc, char **argv, CommandLineOutput *output) {
|
|
uint8 key[32];
|
|
char base64buf[WG_PUBLIC_KEY_LEN_BASE64 + 1];
|
|
|
|
if (argc == 1) {
|
|
ShowHelp();
|
|
return 1;
|
|
}
|
|
|
|
const char *subcommand = argv[1];
|
|
argv += 2;
|
|
argc -= 2;
|
|
|
|
if (!strcmp(subcommand, "show")) {
|
|
return HandleShowCommand(argc, argv);
|
|
|
|
} else if (!strcmp(subcommand, "set")) {
|
|
return HandleSetCommand(argc, argv);
|
|
|
|
#if defined(OS_WIN)
|
|
} else if (!strcmp(subcommand, "log")) {
|
|
if (argc != 0) {
|
|
fprintf(stderr, "Usage: " EXENAME " log\n");
|
|
return 1;
|
|
}
|
|
return HandleLogCommand();
|
|
|
|
} else if (!strcmp(subcommand, "start")) {
|
|
return HandleStartCommand(argc, argv);
|
|
|
|
#else
|
|
} else if (!strcmp(subcommand, "start") && output) {
|
|
if (argc != 0 && !strcmp(argv[0], "--help")) {
|
|
start_usage:
|
|
fprintf(stderr, "Usage: " EXENAME " start [-d/--daemon] [-n <interface-name>] [<filename>]\n");
|
|
return 0;
|
|
}
|
|
for (; argc; argc--, argv++) {
|
|
char *arg = argv[0];
|
|
if (strcmp(arg, "-d") == 0 || strcmp(arg, "--daemon") == 0) {
|
|
output->daemon = true;
|
|
continue;
|
|
}
|
|
if (strcmp(arg, "-n") == 0) {
|
|
if (argc < 2) goto start_usage;
|
|
output->interface_name = argv[1];
|
|
argc--,argv++;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (argc > 1) goto start_usage;
|
|
output->filename_to_load = (argc == 0) ? "" : argv[0];
|
|
return 0;
|
|
#endif // defined(OS_WIN)
|
|
} else if (!strcmp(subcommand, "stop")) {
|
|
return HandleStopCommand(argc, argv);
|
|
} else if(!strcmp(subcommand, "genkey")) {
|
|
if (argc != 0) {
|
|
fprintf(stderr, "Usage: " EXENAME " genkey\n");
|
|
return 1;
|
|
}
|
|
OsGetRandomBytes(key, 32);
|
|
curve25519_normalize(key);
|
|
ansi_printf("%s\n", base64_encode(key, 32, base64buf, sizeof(base64buf), NULL));
|
|
|
|
} else if (!strcmp(subcommand, "genpsk")) {
|
|
if (argc != 0) {
|
|
fprintf(stderr, "Usage: " EXENAME " genpsk\n");
|
|
return 1;
|
|
}
|
|
OsGetRandomBytes(key, 32);
|
|
ansi_printf("%s\n", base64_encode(key, 32, base64buf, sizeof(base64buf), NULL));
|
|
} else if (!strcmp(subcommand, "pubkey")) {
|
|
char base64[WG_PUBLIC_KEY_LEN_BASE64 + 2];
|
|
size_t n = fread(base64, 1, sizeof(base64), stdin);
|
|
if (n < sizeof(base64) - 2 || n >= sizeof(base64) ||
|
|
(n == sizeof(base64) - 1 && (base64[WG_PUBLIC_KEY_LEN_BASE64] != ' ' && base64[WG_PUBLIC_KEY_LEN_BASE64] != '\n'))) {
|
|
fprintf(stderr, EXENAME ": Incorrect key format\n");
|
|
return 1;
|
|
}
|
|
size_t size = 32;
|
|
if (!base64_decode((uint8*)base64, n, key, &size) || size != 32) {
|
|
fprintf(stderr, EXENAME ": Incorrect key format\n");
|
|
return 1;
|
|
}
|
|
curve25519_donna(key, key, kCurve25519Basepoint);
|
|
ansi_printf("%s\n", base64_encode(key, 32, base64buf, sizeof(base64buf), NULL));
|
|
} else if (!strcmp(subcommand, "--help")) {
|
|
ShowHelp();
|
|
} else if (!strcmp(subcommand, "--version")) {
|
|
ansi_printf("%s\n", TUNSAFE_VERSION_STRING);
|
|
} else {
|
|
if (argc == 0) {
|
|
if (output)
|
|
output->filename_to_load = subcommand;
|
|
} else {
|
|
ShowHelp();
|
|
}
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if defined(OS_WIN)
|
|
|
|
// This is ugly but all 3rd party terminals I found hide cmd.exe
|
|
static bool ConsoleSupportsColorCodes() {
|
|
HWND wnd = GetConsoleWindow();
|
|
return wnd && !(GetWindowLong(wnd, GWL_STYLE) & WS_VISIBLE);
|
|
}
|
|
|
|
// This is integrated into the main tunsafe binary on posix systems
|
|
int main(int argc, char **argv) {
|
|
|
|
// Enable color codes on Windows 10+
|
|
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
DWORD dwMode = 0;
|
|
GetConsoleMode(hOut, &dwMode);
|
|
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
SetConsoleMode(hOut, dwMode);
|
|
|
|
// Use colors depending on TUNSAFE_COLOR
|
|
const char *env = getenv("TUNSAFE_COLOR");
|
|
if (env) {
|
|
g_supports_ansi_color = atoi(env) != 0;
|
|
} else {
|
|
g_supports_ansi_color = GetConsoleMode(hOut, &dwMode) && (dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) || ConsoleSupportsColorCodes();
|
|
}
|
|
|
|
int rv = HandleCommandLine(argc, argv, NULL);
|
|
if (rv == -1) {
|
|
fprintf(stderr, "Invalid subcommand '%s'\n", argv[1]);
|
|
ShowHelp();
|
|
return 1;
|
|
}
|
|
return rv;
|
|
}
|
|
#endif // defined(OS_WIN)
|