diff options
Diffstat (limited to 'vpn/plugins/wireguard.c')
-rw-r--r-- | vpn/plugins/wireguard.c | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/vpn/plugins/wireguard.c b/vpn/plugins/wireguard.c new file mode 100644 index 00000000..de2dbda3 --- /dev/null +++ b/vpn/plugins/wireguard.c @@ -0,0 +1,404 @@ +/* + * ConnMan VPN daemon + * + * Copyright (C) 2019 Daniel Wagner. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> + +#include <glib.h> + +#define CONNMAN_API_SUBJECT_TO_CHANGE +#include <connman/plugin.h> +#include <connman/log.h> +#include <connman/task.h> +#include <connman/ipconfig.h> +#include <connman/inet.h> +#include <connman/dbus.h> +#include <connman/setting.h> +#include <connman/vpn-dbus.h> + +#include "../vpn-provider.h" +#include "../vpn.h" + +#include "vpn.h" +#include "wireguard.h" + +static int parse_key(const char *str, wg_key key) +{ + unsigned char *buf; + size_t len; + + buf = g_base64_decode(str, &len); + + if (len != 32) { + g_free(buf); + return -EINVAL; + } + + memcpy(key, buf, 32); + + g_free(buf); + return 0; +} + +static int parse_allowed_ips(const char *allowed_ips, wg_peer *peer) +{ + struct wg_allowedip *curaip, *allowedip; + char buf[INET6_ADDRSTRLEN]; + char **tokens, **toks; + char *send; + int i; + + curaip = NULL; + tokens = g_strsplit(allowed_ips, ", ", -1); + for (i = 0; tokens[i]; i++) { + toks = g_strsplit(tokens[i], "/", -1); + if (g_strv_length(toks) != 2) { + DBG("Ignore AllowedIPs value %s", tokens[i]); + g_strfreev(toks); + continue; + } + + allowedip = g_malloc0(sizeof(*allowedip)); + + if (inet_pton(AF_INET, toks[0], buf) == 1) { + allowedip->family = AF_INET; + memcpy(&allowedip->ip4, buf, sizeof(allowedip->ip4)); + } else if (inet_pton(AF_INET6, toks[0], buf) == 1) { + allowedip->family = AF_INET6; + memcpy(&allowedip->ip6, buf, sizeof(allowedip->ip6)); + } else { + DBG("Ignore AllowedIPs value %s", tokens[i]); + g_free(allowedip); + g_strfreev(toks); + continue; + } + + allowedip->cidr = g_ascii_strtoull(toks[1], &send, 10); + + if (!curaip) + peer->first_allowedip = allowedip; + else + curaip->next_allowedip = allowedip; + + curaip = allowedip; + } + + peer->last_allowedip = curaip; + g_strfreev(tokens); + + return 0; +} + +static int parse_endpoint(const char *host, const char *port, wg_peer *peer) +{ + struct addrinfo hints; + struct addrinfo *result, *rp; + int sk; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + if (getaddrinfo(host, port, &hints, &result) < 0) { + DBG("Failed to resolve host address"); + return -EINVAL; + } + + for (rp = result; rp; rp = rp->ai_next) { + sk = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sk < 0) + continue; + if (connect(sk, rp->ai_addr, rp->ai_addrlen) != -1) { + /* success */ + close(sk); + break; + } + + close(sk); + } + + if (!rp) { + freeaddrinfo(result); + return -EINVAL; + } + + memcpy(&peer->endpoint.addr, rp->ai_addr, rp->ai_addrlen); + freeaddrinfo(result); + + return 0; +} + +static int parse_address(const char *address, const char *gateway, + struct connman_ipaddress **ipaddress) +{ + char buf[INET6_ADDRSTRLEN]; + unsigned char prefixlen; + char **tokens; + char *end, *netmask; + int err; + + tokens = g_strsplit(address, "/", -1); + if (g_strv_length(tokens) != 2) { + g_strfreev(tokens); + return -EINVAL; + } + + prefixlen = g_ascii_strtoull(tokens[1], &end, 10); + + if (inet_pton(AF_INET, tokens[0], buf) == 1) { + netmask = g_strdup_printf("%d.%d.%d.%d", + ((0xffffffff << (32 - prefixlen)) >> 24) & 0xff, + ((0xffffffff << (32 - prefixlen)) >> 16) & 0xff, + ((0xffffffff << (32 - prefixlen)) >> 8) & 0xff, + ((0xffffffff << (32 - prefixlen)) >> 0) & 0xff); + + *ipaddress = connman_ipaddress_alloc(AF_INET); + err = connman_ipaddress_set_ipv4(*ipaddress, tokens[0], + netmask, gateway); + g_free(netmask); + } else if (inet_pton(AF_INET6, tokens[0], buf) == 1) { + *ipaddress = connman_ipaddress_alloc(AF_INET6); + err = connman_ipaddress_set_ipv6(*ipaddress, tokens[0], + prefixlen, gateway); + } else { + DBG("Invalid Wireguard.Address value"); + err = -EINVAL; + } + + g_strfreev(tokens); + if (err) + connman_ipaddress_free(*ipaddress); + + return err; +} + +struct ifname_data { + char *ifname; + bool found; +}; + +static void ifname_check_cb(int index, void *user_data) +{ + struct ifname_data *data = (struct ifname_data *)user_data; + char *ifname; + + ifname = connman_inet_ifname(index); + + if (!g_strcmp0(ifname, data->ifname)) + data->found = true; +} + +static char *get_ifname(void) +{ + struct ifname_data data; + int i; + + for (i = 0; i < 256; i++) { + data.ifname = g_strdup_printf("wg%d", i); + data.found = false; + __vpn_ipconfig_foreach(ifname_check_cb, &data); + + if (!data.found) + return data.ifname; + + g_free(data.ifname); + } + + return NULL; +} + +struct wireguard_info { + struct wg_device device; + struct wg_peer peer; +}; + +static int wg_connect(struct vpn_provider *provider, + struct connman_task *task, const char *if_name, + vpn_provider_connect_cb_t cb, + const char *dbus_sender, void *user_data) +{ + struct connman_ipaddress *ipaddress = NULL; + struct wireguard_info *info; + const char *option, *gateway; + char *ifname; + int err = -EINVAL; + + info = g_malloc0(sizeof(struct wireguard_info)); + info->peer.flags = WGPEER_HAS_PUBLIC_KEY | WGPEER_REPLACE_ALLOWEDIPS; + info->device.flags = WGDEVICE_HAS_PRIVATE_KEY; + info->device.first_peer = &info->peer; + info->device.last_peer = &info->peer; + + vpn_provider_set_plugin_data(provider, info); + + option = vpn_provider_get_string(provider, "WireGuard.ListenPort"); + if (option) { + char *end; + info->device.listen_port = g_ascii_strtoull(option, &end, 10); + info->device.flags |= WGDEVICE_HAS_LISTEN_PORT; + } + + option = vpn_provider_get_string(provider, "WireGuard.DNS"); + if (option) { + err = vpn_provider_set_nameservers(provider, option); + if (err) + goto done; + } + + option = vpn_provider_get_string(provider, "WireGuard.PrivateKey"); + if (!option) { + DBG("WireGuard.PrivateKey is missing"); + goto done; + } + err = parse_key(option, info->device.private_key); + if (err) + goto done; + + option = vpn_provider_get_string(provider, "WireGuard.PublicKey"); + if (!option) { + DBG("WireGuard.PublicKey is missing"); + goto done; + } + err = parse_key(option, info->peer.public_key); + if (err) + goto done; + + option = vpn_provider_get_string(provider, "WireGuard.PresharedKey"); + if (option) { + info->peer.flags |= WGPEER_HAS_PRESHARED_KEY; + err = parse_key(option, info->peer.preshared_key); + if (err) + goto done; + } + + option = vpn_provider_get_string(provider, "WireGuard.AllowedIPs"); + if (!option) { + DBG("WireGuard.AllowedIPs is missing"); + goto done; + } + err = parse_allowed_ips(option, &info->peer); + if (err) + goto done; + + option = vpn_provider_get_string(provider, + "WireGuard.PersistentKeepalive"); + if (option) { + char *end; + info->peer.persistent_keepalive_interval = + g_ascii_strtoull(option, &end, 10); + info->peer.flags |= WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL; + } + + option = vpn_provider_get_string(provider, "WireGuard.EndpointPort"); + if (!option) + option = "51820"; + + gateway = vpn_provider_get_string(provider, "Host"); + err = parse_endpoint(gateway, option, &info->peer); + if (err) + goto done; + + option = vpn_provider_get_string(provider, "WireGuard.Address"); + if (!option) { + DBG("Missing WireGuard.Address configuration"); + goto done; + } + err = parse_address(option, gateway, &ipaddress); + if (err) + goto done; + + ifname = get_ifname(); + if (!ifname) { + DBG("Failed to find an usable device name"); + err = -ENOENT; + goto done; + } + stpncpy(info->device.name, ifname, sizeof(info->device.name)); + g_free(ifname); + + err = wg_add_device(info->device.name); + if (err) { + DBG("Failed to creating WireGuard device %s", info->device.name); + goto done; + } + + err = wg_set_device(&info->device); + if (err) { + DBG("Failed to configure WireGuard device %s", info->device.name); + wg_del_device(info->device.name); + } + + vpn_set_ifname(provider, info->device.name); + if (ipaddress) + vpn_provider_set_ipaddress(provider, ipaddress); + +done: + if (cb) + cb(provider, user_data, err); + + connman_ipaddress_free(ipaddress); + + return err; +} + +static void wg_disconnect(struct vpn_provider *provider) +{ + struct wireguard_info *info; + + info = vpn_provider_get_plugin_data(provider); + if (!info) + return; + vpn_provider_set_plugin_data(provider, NULL); + + wg_del_device(info->device.name); + + g_free(info); +} + +static struct vpn_driver vpn_driver = { + .flags = VPN_FLAG_NO_TUN | VPN_FLAG_NO_DAEMON, + .connect = wg_connect, + .disconnect = wg_disconnect, +}; + +static int wg_init(void) +{ + return vpn_register("wireguard", &vpn_driver, NULL); +} + +static void wg_exit(void) +{ + vpn_unregister("wireguard"); +} + +CONNMAN_PLUGIN_DEFINE(wireguard, "WireGuard VPN plugin", VERSION, + CONNMAN_PLUGIN_PRIORITY_DEFAULT, wg_init, wg_exit) |