summaryrefslogtreecommitdiff
path: root/vpn/plugins/wireguard.c
diff options
context:
space:
mode:
Diffstat (limited to 'vpn/plugins/wireguard.c')
-rw-r--r--vpn/plugins/wireguard.c404
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)