[PATCH v0 8/9] vpn: Add WireGuard support

Daniel Wagner wagi at monom.org
Fri Jul 19 04:21:37 PDT 2019


Implement a bare minimum for WireGuard support. The parser only
understands minium, e.g. only one peer. To create and manage the
WireGuard devices the upstream example code is used.
---
 vpn/plugins/wireguard.c | 328 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 327 insertions(+), 1 deletion(-)

diff --git a/vpn/plugins/wireguard.c b/vpn/plugins/wireguard.c
index e110eb545943..b28434fe6079 100644
--- a/vpn/plugins/wireguard.c
+++ b/vpn/plugins/wireguard.c
@@ -21,24 +21,350 @@
 #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 <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 wg_init(void)
+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;
+	char **tokens;
+	int i, err;
+
+	curaip = NULL;
+	tokens = g_strsplit(allowed_ips, ", ", -1);
+	for (i = 0; tokens[i]; i++) {
+		struct wg_allowedip *allowedip;
+		char buf[INET6_ADDRSTRLEN];
+		char **toks;
+		char *send;
+
+		toks = g_strsplit(tokens[i], "/", -1);
+		if (g_strv_length(toks) != 2) {
+			g_strfreev(toks);
+			continue;
+		}
+
+		allowedip = g_malloc0(sizeof(*allowedip));
+
+		err = inet_pton(AF_INET, toks[0], buf);
+		if (err == 1) {
+			allowedip->family = AF_INET;
+			memcpy(&allowedip->ip4, buf,
+				sizeof(allowedip->ip4));
+		} else {
+			err = inet_pton(AF_INET6, toks[0], buf);
+			if (err == 1) {
+				allowedip->family = AF_INET6;
+				memcpy(&allowedip->ip6, buf,
+					sizeof(allowedip->ip6));
+			}
+		}
+
+		allowedip->cidr = g_ascii_strtoull(toks[1], &send, 10);
+
+		if (err != 1) {
+			g_strfreev(toks);
+			g_free(allowedip);
+			continue;
+		}
+
+		if (curaip == NULL)
+			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 *endpoint, wg_peer *peer, char **gateway)
+{
+	char **tokens, *end;
+	int err;
+
+	if (endpoint && endpoint[0] == '[') {
+		/*
+		 * IPv6 addresses should be in port notification, e.g
+		 * "[IPv6 Address]:port"
+		 */
+		tokens = g_strsplit(endpoint, "[]:", -1);
+		*gateway = g_strdup(tokens[0]);
+
+		peer->endpoint.addr.sa_family = AF_INET6;
+
+		err = inet_pton(AF_INET6, tokens[0],
+				&peer->endpoint.addr6.sin6_addr);
+		peer->endpoint.addr6.sin6_port =
+			htons(g_ascii_strtoull(tokens[1], &end, 10));
+	} else {
+		/* "IPv4Address:port" */
+		tokens = g_strsplit(endpoint, ":", -1);
+		*gateway = g_strdup(tokens[0]);
+
+		peer->endpoint.addr.sa_family = AF_INET;
+
+		err = inet_pton(AF_INET, tokens[0],
+				&peer->endpoint.addr4.sin_addr);
+		peer->endpoint.addr4.sin_port =
+			htons(g_ascii_strtoull(tokens[1], &end, 10));
+	}
+	g_strfreev(tokens);
+	if (!err)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int parse_address(const char *address, char *gateway,
+		struct connman_ipaddress **ipaddress)
+{
+	char buf[INET6_ADDRSTRLEN];
+	unsigned char prefixlen;
+	char **tokens;
+	char *end;
+	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);
+
+	err = inet_pton(AF_INET, tokens[0], buf);
+	if (err == 1) {
+		char *netmask;
+		*ipaddress = connman_ipaddress_alloc(AF_INET);
+
+		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);
+
+		connman_ipaddress_set_ipv4(*ipaddress, tokens[0],
+					netmask, gateway);
+		g_free(netmask);
+	} else {
+		err = inet_pton(AF_INET6, tokens[0], buf);
+		if (err == 1) {
+			*ipaddress = connman_ipaddress_alloc(AF_INET6);
+			connman_ipaddress_set_ipv6(*ipaddress, tokens[0],
+						prefixlen, gateway);
+		}
+	}
+
+	return 0;
+}
+
+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 wireguard_info *info;
+	struct connman_ipaddress *ipaddress;
+	const char *option;
+	char *gateway, *ifname;
+	int err;
+
+	info = g_malloc(sizeof(struct wireguard_info));
+	info->peer.flags = WGPEER_HAS_PUBLIC_KEY | WGPEER_REPLACE_ALLOWEDIPS;
+	info->device.flags = WGDEVICE_HAS_PRIVATE_KEY | WGDEVICE_HAS_LISTEN_PORT;
+	info->device.first_peer = &info->peer;
+	info->device.last_peer = &info->peer;
+
+	option = vpn_provider_get_string(provider, "WireGuard.ListPort");
+	if (option) {
+		char *end;
+		info->device.listen_port = g_ascii_strtoull(option, &end, 10);
+	}
+
+	option = vpn_provider_get_string(provider, "WireGuard.PrivateKey");
+	if (option) {
+		err = parse_key(option, info->device.private_key);
+		if (err)
+			return -EINVAL;
+	}
+
+	option = vpn_provider_get_string(provider, "WireGuard.PublicKey");
+	if (option) {
+		err = parse_key(option, info->peer.public_key);
+		if (err)
+			return -EINVAL;
+	}
+
+	option = vpn_provider_get_string(provider, "WireGuard.AllowedIPs");
+	if (option) {
+		err = parse_allowed_ips(option, &info->peer);
+		if (err)
+			return -EINVAL;
+	}
+
+	gateway = NULL;
+	option = vpn_provider_get_string(provider, "Wireguard.Endpoint");
+	if (option) {
+		err = parse_endpoint(option, &info->peer, &gateway);
+		if (err) {
+			g_free(gateway);
+			return -EINVAL;
+		}
+	}
+
+	option = vpn_provider_get_string(provider, "WireGuard.Address");
+	if (option) {
+		err = parse_address(option, gateway, &ipaddress);
+		if (err) {
+			g_free(gateway);
+			return -EINVAL;
+		}
+	}
+	g_free(gateway);
+
+	ifname = get_ifname();
+	if (!ifname) {
+		DBG("Failed to find an usable device name");
+		err = -ENOENT;
+		goto done;
+	}
+	memcpy(info->device.name, ifname, MIN(strlen(ifname), IFNAMSIZ));
+	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_provider_set_plugin_data(provider, info);
+	vpn_set_ifname(provider, info->device.name);
+	if (ipaddress) {
+		vpn_provider_set_ipaddress(provider, ipaddress);
+		connman_ipaddress_free(ipaddress);
+	}
+
+done:
+	if (cb)
+		cb(provider, user_data, err);
+
+	return 0;
+}
+
+static void wg_disconnect(struct vpn_provider *provider)
+{
+	struct wireguard_info *info;
+
+	info = vpn_provider_get_plugin_data(provider);
+	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, WIREGUARD);
+}
+
 static void wg_exit(void)
 {
+	vpn_unregister("wireguard");
 }
 
 CONNMAN_PLUGIN_DEFINE(wireguard, "WireGuard VPN plugin", VERSION,
-- 
2.20.1


More information about the connman mailing list