[PATCH v2] [POC] plugins: Add WireGuard support

Daniel Wagner wagi at monom.org
Thu Jul 18 00:18:10 PDT 2019


Add WireGuard support via a plugin. A new service and device type is
introduced (CONNMAN_SERVICE_TYPE_WIREGUARD and
CONNMAN_DEVICE_TYPE_WIREGUARD).

The WireGuard plugin gets via the config subsystem a new configuration
which is parsed and a new WireGuard device is created and only the VPN
settings are configured.

Via the RTNL subsystem detects (note a WireGuard device is not
reported as ARPHDR_ETH and has no MAC address) and the IP settings are
applied.

The CONNMAN_SERVICE_TYPE_WIREGUARD is treated in the exact same way as
a CONNMAN_SERVICE_TYPE_VPN except it is not disconnected when the
underlaying transport goes away.

This is just a proof of concept to figure out how WireGuard could be
supported in ConnMan.

Here are some thoughs on this approach:

- There is a some workarounds needed to detect the WireGuard
  device. While this seems to work, it is not nice. In POC v1
  connman-vpn just tells ConnMan which device to use and
  configure. This is more consistent and no changes in ConnMan are
  needed.
- The setup of the WireGuard device is about the same complexity in
  both POCs. So there is argument doing it as plugin or as vpn plugin.
- The handling of a VPN service (routing!) is working great and we
  got a lot of bug fixes and improvents from Jolla. The WireGuard
  device is in the end nothing else than a VPN. So there is nothing
  really to be gained by introducing a new Service type. (Note we
  would also expose this to the user API).

The connman-vpn is currently a wrapper around external daemons and
WireGuard doesn't need any daemon running. But connmand and
connman-vpn have a good tested and stable API handling VPN
services. Adding additional routes, split routing etc., everything
works. Also changing the user API is something I don't like. Yes, we
could fake it by saying it is a VPN service again. But then, what's
the point?

After this POC v2 I rather go with the approach in v1 and add a new
feature to the VPN handling in ConnMan which allows it to stay online
even when the underlaying transport goes away. This should give the
fancy WireGuard roaming feature we all want :)

Comments?

libgw.c and libgw.h are from
https://github.com/WireGuard/WireGuard/tree/master/contrib/examples/embeddable-wg-library
---
 Makefile.plugins    |    5 +
 configure.ac        |    8 +-
 include/device.h    |    1 +
 include/network.h   |    1 +
 include/provision.h |   25 +-
 include/service.h   |    3 +-
 plugins/libwg.c     | 1771 +++++++++++++++++++++++++++++++++++++++++++
 plugins/libwg.h     |  103 +++
 plugins/wifi.c      |   10 +-
 plugins/wireguard.c |  509 +++++++++++++
 src/config.c        |  181 ++++-
 src/connection.c    |    3 +-
 src/detect.c        |    1 +
 src/device.c        |   14 +-
 src/network.c       |    7 +
 src/notifier.c      |    2 +
 src/rfkill.c        |    1 +
 src/rtnl.c          |    8 +-
 src/service.c       |   24 +
 src/session.c       |    4 +
 src/technology.c    |    4 +
 src/wispr.c         |    1 +
 22 files changed, 2644 insertions(+), 42 deletions(-)
 create mode 100644 plugins/libwg.c
 create mode 100644 plugins/libwg.h
 create mode 100644 plugins/wireguard.c

diff --git a/Makefile.plugins b/Makefile.plugins
index 14f2a782ae4e..761076863501 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -13,6 +13,11 @@ builtin_modules += ethernet
 builtin_sources += plugins/ethernet.c
 endif
 
+if WIREGUARD
+builtin_modules += wireguard
+builtin_sources += plugins/libwg.h plugins/libwg.c plugins/wireguard.c
+endif
+
 if GADGET
 builtin_modules += gadget
 builtin_sources += plugins/gadget.c
diff --git a/configure.ac b/configure.ac
index ee49a22c4ec8..298611dc0233 100644
--- a/configure.ac
+++ b/configure.ac
@@ -333,6 +333,11 @@ AC_ARG_ENABLE(ethernet, AC_HELP_STRING([--disable-ethernet],
 					[enable_ethernet=${enableval}])
 AM_CONDITIONAL(ETHERNET, test "${enable_ethernet}" != "no")
 
+AC_ARG_ENABLE(wireguard, AC_HELP_STRING([--disable-wireguard],
+				[disable Wireguard support]),
+					[enable_wireguard=${enableval}])
+AM_CONDITIONAL(WIREGUARD, test "${enable_wireguard}" != "no")
+
 AC_ARG_ENABLE(gadget, AC_HELP_STRING([--disable-gadget],
 				[disable USB Gadget support]),
 					[enable_gadget=${enableval}])
@@ -444,7 +449,8 @@ AM_CONDITIONAL(VPN, test "${enable_openconnect}" != "no" -o \
 			"${enable_openvpn}" != "no" -o \
 			"${enable_vpnc}" != "no" -o \
 			"${enable_l2tp}" != "no" -o \
-			"${enable_pptp}" != "no")
+			"${enable_pptp}" != "no" -o \
+			"${enable_wireguard}" != "no")
 
 AC_MSG_CHECKING(which DNS backend to use)
 AC_ARG_WITH(dns-backend, AC_HELP_STRING([--with-dns-backend=TYPE],
diff --git a/include/device.h b/include/device.h
index 0fc06bd00526..ac0b0759b100 100644
--- a/include/device.h
+++ b/include/device.h
@@ -43,6 +43,7 @@ enum connman_device_type {
 	CONNMAN_DEVICE_TYPE_CELLULAR  = 4,
 	CONNMAN_DEVICE_TYPE_GPS       = 5,
 	CONNMAN_DEVICE_TYPE_GADGET    = 6,
+	CONNMAN_DEVICE_TYPE_WIREGUARD = 7,
 	CONNMAN_DEVICE_TYPE_VENDOR    = 10000,
 };
 
diff --git a/include/network.h b/include/network.h
index 8f87d7c5da72..d70e0bab12ad 100644
--- a/include/network.h
+++ b/include/network.h
@@ -48,6 +48,7 @@ enum connman_network_type {
 	CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN = 9,
 	CONNMAN_NETWORK_TYPE_CELLULAR      = 10,
 	CONNMAN_NETWORK_TYPE_GADGET        = 11,
+	CONNMAN_NETWORK_TYPE_WIREGUARD     = 12,
 	CONNMAN_NETWORK_TYPE_VENDOR        = 10000,
 };
 
diff --git a/include/provision.h b/include/provision.h
index 3eb80a865a1a..cc82793e52c8 100644
--- a/include/provision.h
+++ b/include/provision.h
@@ -34,7 +34,14 @@ extern "C" {
  * @short_description: Functions for provision configuration handling
  */
 
-struct connman_config_entry {
+enum connman_config_type {
+	CONNMAN_CONFIG_TYPE_UNKNOWN	= 0,
+	CONNMAN_CONFIG_TYPE_WIFI	= 1,
+	CONNMAN_CONFIG_TYPE_WIREGUARD	= 2,
+};
+#define	MAX_CONNMAN_CONFIG_TYPES        3
+
+struct connman_config_wifi_entry {
 	char *ident;
 	char *name;
 	void *ssid;
@@ -42,6 +49,22 @@ struct connman_config_entry {
 	bool hidden;
 };
 
+struct connman_config_wireguard_entry {
+	unsigned short int listen_port;
+	char *private_key;
+	char *public_key;
+	char **allowed_ips;
+	char *endpoint;
+};
+
+struct connman_config_entry {
+	enum connman_config_type type;
+	union {
+		struct connman_config_wifi_entry wifi;
+		struct connman_config_wireguard_entry wireguard;
+	} u;
+};
+
 int connman_config_provision_mutable_service(GKeyFile *keyfile);
 struct connman_config_entry **connman_config_get_entries(const char *type);
 void connman_config_free_entries(struct connman_config_entry **entries);
diff --git a/include/service.h b/include/service.h
index 4a129b49315e..5d2f8c036681 100644
--- a/include/service.h
+++ b/include/service.h
@@ -45,8 +45,9 @@ enum connman_service_type {
 	CONNMAN_SERVICE_TYPE_VPN       = 7,
 	CONNMAN_SERVICE_TYPE_GADGET    = 8,
 	CONNMAN_SERVICE_TYPE_P2P       = 9,
+	CONNMAN_SERVICE_TYPE_WIREGUARD = 10,
 };
-#define	MAX_CONNMAN_SERVICE_TYPES        10
+#define	MAX_CONNMAN_SERVICE_TYPES        11
 
 
 enum connman_service_security {
diff --git a/plugins/libwg.c b/plugins/libwg.c
new file mode 100644
index 000000000000..661108c64c6c
--- /dev/null
+++ b/plugins/libwg.c
@@ -0,0 +1,1771 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2015-2019 Jason A. Donenfeld <Jason at zx2c4.com>. All Rights Reserved.
+ * Copyright (C) 2008-2012 Pablo Neira Ayuso <pablo at netfilter.org>.
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <linux/genetlink.h>
+#include <linux/if_link.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "libwg.h"
+
+/* wireguard.h netlink uapi: */
+
+#define WG_GENL_NAME "wireguard"
+#define WG_GENL_VERSION 1
+
+enum wg_cmd {
+	WG_CMD_GET_DEVICE,
+	WG_CMD_SET_DEVICE,
+	__WG_CMD_MAX
+};
+
+enum wgdevice_flag {
+	WGDEVICE_F_REPLACE_PEERS = 1U << 0
+};
+enum wgdevice_attribute {
+	WGDEVICE_A_UNSPEC,
+	WGDEVICE_A_IFINDEX,
+	WGDEVICE_A_IFNAME,
+	WGDEVICE_A_PRIVATE_KEY,
+	WGDEVICE_A_PUBLIC_KEY,
+	WGDEVICE_A_FLAGS,
+	WGDEVICE_A_LISTEN_PORT,
+	WGDEVICE_A_FWMARK,
+	WGDEVICE_A_PEERS,
+	__WGDEVICE_A_LAST
+};
+
+enum wgpeer_flag {
+	WGPEER_F_REMOVE_ME = 1U << 0,
+	WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1
+};
+enum wgpeer_attribute {
+	WGPEER_A_UNSPEC,
+	WGPEER_A_PUBLIC_KEY,
+	WGPEER_A_PRESHARED_KEY,
+	WGPEER_A_FLAGS,
+	WGPEER_A_ENDPOINT,
+	WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
+	WGPEER_A_LAST_HANDSHAKE_TIME,
+	WGPEER_A_RX_BYTES,
+	WGPEER_A_TX_BYTES,
+	WGPEER_A_ALLOWEDIPS,
+	WGPEER_A_PROTOCOL_VERSION,
+	__WGPEER_A_LAST
+};
+
+enum wgallowedip_attribute {
+	WGALLOWEDIP_A_UNSPEC,
+	WGALLOWEDIP_A_FAMILY,
+	WGALLOWEDIP_A_IPADDR,
+	WGALLOWEDIP_A_CIDR_MASK,
+	__WGALLOWEDIP_A_LAST
+};
+
+/* libmnl mini library: */
+
+#define MNL_SOCKET_AUTOPID 0
+#define MNL_SOCKET_BUFFER_SIZE (sysconf(_SC_PAGESIZE) < 8192L ? sysconf(_SC_PAGESIZE) : 8192L)
+#define MNL_ALIGNTO 4
+#define MNL_ALIGN(len) (((len)+MNL_ALIGNTO-1) & ~(MNL_ALIGNTO-1))
+#define MNL_NLMSG_HDRLEN MNL_ALIGN(sizeof(struct nlmsghdr))
+#define MNL_ATTR_HDRLEN MNL_ALIGN(sizeof(struct nlattr))
+
+enum mnl_attr_data_type {
+	MNL_TYPE_UNSPEC,
+	MNL_TYPE_U8,
+	MNL_TYPE_U16,
+	MNL_TYPE_U32,
+	MNL_TYPE_U64,
+	MNL_TYPE_STRING,
+	MNL_TYPE_FLAG,
+	MNL_TYPE_MSECS,
+	MNL_TYPE_NESTED,
+	MNL_TYPE_NESTED_COMPAT,
+	MNL_TYPE_NUL_STRING,
+	MNL_TYPE_BINARY,
+	MNL_TYPE_MAX,
+};
+
+#define mnl_attr_for_each(attr, nlh, offset) \
+	for ((attr) = mnl_nlmsg_get_payload_offset((nlh), (offset)); \
+	     mnl_attr_ok((attr), (char *)mnl_nlmsg_get_payload_tail(nlh) - (char *)(attr)); \
+	     (attr) = mnl_attr_next(attr))
+
+#define mnl_attr_for_each_nested(attr, nest) \
+	for ((attr) = mnl_attr_get_payload(nest); \
+	     mnl_attr_ok((attr), (char *)mnl_attr_get_payload(nest) + mnl_attr_get_payload_len(nest) - (char *)(attr)); \
+	     (attr) = mnl_attr_next(attr))
+
+#define mnl_attr_for_each_payload(payload, payload_size) \
+	for ((attr) = (payload); \
+	     mnl_attr_ok((attr), (char *)(payload) + payload_size - (char *)(attr)); \
+	     (attr) = mnl_attr_next(attr))
+
+#define MNL_CB_ERROR	-1
+#define MNL_CB_STOP	0
+#define MNL_CB_OK	1
+
+typedef int (*mnl_attr_cb_t)(const struct nlattr *attr, void *data);
+typedef int (*mnl_cb_t)(const struct nlmsghdr *nlh, void *data);
+
+#ifndef MNL_ARRAY_SIZE
+#define MNL_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
+#endif
+
+static size_t mnl_nlmsg_size(size_t len)
+{
+	return len + MNL_NLMSG_HDRLEN;
+}
+
+static struct nlmsghdr *mnl_nlmsg_put_header(void *buf)
+{
+	int len = MNL_ALIGN(sizeof(struct nlmsghdr));
+	struct nlmsghdr *nlh = buf;
+
+	memset(buf, 0, len);
+	nlh->nlmsg_len = len;
+	return nlh;
+}
+
+static void *mnl_nlmsg_put_extra_header(struct nlmsghdr *nlh, size_t size)
+{
+	char *ptr = (char *)nlh + nlh->nlmsg_len;
+	size_t len = MNL_ALIGN(size);
+	nlh->nlmsg_len += len;
+	memset(ptr, 0, len);
+	return ptr;
+}
+
+static void *mnl_nlmsg_get_payload(const struct nlmsghdr *nlh)
+{
+	return (void *)nlh + MNL_NLMSG_HDRLEN;
+}
+
+static void *mnl_nlmsg_get_payload_offset(const struct nlmsghdr *nlh, size_t offset)
+{
+	return (void *)nlh + MNL_NLMSG_HDRLEN + MNL_ALIGN(offset);
+}
+
+
+static bool mnl_nlmsg_ok(const struct nlmsghdr *nlh, int len)
+{
+	return len >= (int)sizeof(struct nlmsghdr) &&
+	       nlh->nlmsg_len >= sizeof(struct nlmsghdr) &&
+	       (int)nlh->nlmsg_len <= len;
+}
+
+static struct nlmsghdr *mnl_nlmsg_next(const struct nlmsghdr *nlh, int *len)
+{
+	*len -= MNL_ALIGN(nlh->nlmsg_len);
+	return (struct nlmsghdr *)((void *)nlh + MNL_ALIGN(nlh->nlmsg_len));
+}
+
+static void *mnl_nlmsg_get_payload_tail(const struct nlmsghdr *nlh)
+{
+	return (void *)nlh + MNL_ALIGN(nlh->nlmsg_len);
+}
+
+static bool mnl_nlmsg_seq_ok(const struct nlmsghdr *nlh, unsigned int seq)
+{
+	return nlh->nlmsg_seq && seq ? nlh->nlmsg_seq == seq : true;
+}
+
+static bool mnl_nlmsg_portid_ok(const struct nlmsghdr *nlh, unsigned int portid)
+{
+	return nlh->nlmsg_pid && portid ? nlh->nlmsg_pid == portid : true;
+}
+
+static uint16_t mnl_attr_get_type(const struct nlattr *attr)
+{
+	return attr->nla_type & NLA_TYPE_MASK;
+}
+
+static uint16_t mnl_attr_get_payload_len(const struct nlattr *attr)
+{
+	return attr->nla_len - MNL_ATTR_HDRLEN;
+}
+
+static void *mnl_attr_get_payload(const struct nlattr *attr)
+{
+	return (void *)attr + MNL_ATTR_HDRLEN;
+}
+
+static bool mnl_attr_ok(const struct nlattr *attr, int len)
+{
+	return len >= (int)sizeof(struct nlattr) &&
+	       attr->nla_len >= sizeof(struct nlattr) &&
+	       (int)attr->nla_len <= len;
+}
+
+static struct nlattr *mnl_attr_next(const struct nlattr *attr)
+{
+	return (struct nlattr *)((void *)attr + MNL_ALIGN(attr->nla_len));
+}
+
+static int mnl_attr_type_valid(const struct nlattr *attr, uint16_t max)
+{
+	if (mnl_attr_get_type(attr) > max) {
+		errno = EOPNOTSUPP;
+		return -1;
+	}
+	return 1;
+}
+
+static int __mnl_attr_validate(const struct nlattr *attr,
+			       enum mnl_attr_data_type type, size_t exp_len)
+{
+	uint16_t attr_len = mnl_attr_get_payload_len(attr);
+	const char *attr_data = mnl_attr_get_payload(attr);
+
+	if (attr_len < exp_len) {
+		errno = ERANGE;
+		return -1;
+	}
+	switch(type) {
+	case MNL_TYPE_FLAG:
+		if (attr_len > 0) {
+			errno = ERANGE;
+			return -1;
+		}
+		break;
+	case MNL_TYPE_NUL_STRING:
+		if (attr_len == 0) {
+			errno = ERANGE;
+			return -1;
+		}
+		if (attr_data[attr_len-1] != '\0') {
+			errno = EINVAL;
+			return -1;
+		}
+		break;
+	case MNL_TYPE_STRING:
+		if (attr_len == 0) {
+			errno = ERANGE;
+			return -1;
+		}
+		break;
+	case MNL_TYPE_NESTED:
+
+		if (attr_len == 0)
+			break;
+
+		if (attr_len < MNL_ATTR_HDRLEN) {
+			errno = ERANGE;
+			return -1;
+		}
+		break;
+	default:
+
+		break;
+	}
+	if (exp_len && attr_len > exp_len) {
+		errno = ERANGE;
+		return -1;
+	}
+	return 0;
+}
+
+static const size_t mnl_attr_data_type_len[MNL_TYPE_MAX] = {
+	[MNL_TYPE_U8]		= sizeof(uint8_t),
+	[MNL_TYPE_U16]		= sizeof(uint16_t),
+	[MNL_TYPE_U32]		= sizeof(uint32_t),
+	[MNL_TYPE_U64]		= sizeof(uint64_t),
+	[MNL_TYPE_MSECS]	= sizeof(uint64_t),
+};
+
+static int mnl_attr_validate(const struct nlattr *attr, enum mnl_attr_data_type type)
+{
+	int exp_len;
+
+	if (type >= MNL_TYPE_MAX) {
+		errno = EINVAL;
+		return -1;
+	}
+	exp_len = mnl_attr_data_type_len[type];
+	return __mnl_attr_validate(attr, type, exp_len);
+}
+
+static int mnl_attr_parse(const struct nlmsghdr *nlh, unsigned int offset,
+			  mnl_attr_cb_t cb, void *data)
+{
+	int ret = MNL_CB_OK;
+	const struct nlattr *attr;
+
+	mnl_attr_for_each(attr, nlh, offset)
+		if ((ret = cb(attr, data)) <= MNL_CB_STOP)
+			return ret;
+	return ret;
+}
+
+static int mnl_attr_parse_nested(const struct nlattr *nested, mnl_attr_cb_t cb,
+				 void *data)
+{
+	int ret = MNL_CB_OK;
+	const struct nlattr *attr;
+
+	mnl_attr_for_each_nested(attr, nested)
+		if ((ret = cb(attr, data)) <= MNL_CB_STOP)
+			return ret;
+	return ret;
+}
+
+static uint8_t mnl_attr_get_u8(const struct nlattr *attr)
+{
+	return *((uint8_t *)mnl_attr_get_payload(attr));
+}
+
+static uint16_t mnl_attr_get_u16(const struct nlattr *attr)
+{
+	return *((uint16_t *)mnl_attr_get_payload(attr));
+}
+
+static uint32_t mnl_attr_get_u32(const struct nlattr *attr)
+{
+	return *((uint32_t *)mnl_attr_get_payload(attr));
+}
+
+
+static uint64_t mnl_attr_get_u64(const struct nlattr *attr)
+{
+	uint64_t tmp;
+	memcpy(&tmp, mnl_attr_get_payload(attr), sizeof(tmp));
+	return tmp;
+}
+
+static const char *mnl_attr_get_str(const struct nlattr *attr)
+{
+	return mnl_attr_get_payload(attr);
+}
+
+static void mnl_attr_put(struct nlmsghdr *nlh, uint16_t type, size_t len,
+			 const void *data)
+{
+	struct nlattr *attr = mnl_nlmsg_get_payload_tail(nlh);
+	uint16_t payload_len = MNL_ALIGN(sizeof(struct nlattr)) + len;
+	int pad;
+
+	attr->nla_type = type;
+	attr->nla_len = payload_len;
+	memcpy(mnl_attr_get_payload(attr), data, len);
+	nlh->nlmsg_len += MNL_ALIGN(payload_len);
+	pad = MNL_ALIGN(len) - len;
+	if (pad > 0)
+		memset(mnl_attr_get_payload(attr) + len, 0, pad);
+}
+
+static void mnl_attr_put_u16(struct nlmsghdr *nlh, uint16_t type, uint16_t data)
+{
+	mnl_attr_put(nlh, type, sizeof(uint16_t), &data);
+}
+
+static void mnl_attr_put_u32(struct nlmsghdr *nlh, uint16_t type, uint32_t data)
+{
+	mnl_attr_put(nlh, type, sizeof(uint32_t), &data);
+}
+
+static void mnl_attr_put_strz(struct nlmsghdr *nlh, uint16_t type, const char *data)
+{
+	mnl_attr_put(nlh, type, strlen(data)+1, data);
+}
+
+static struct nlattr *mnl_attr_nest_start(struct nlmsghdr *nlh, uint16_t type)
+{
+	struct nlattr *start = mnl_nlmsg_get_payload_tail(nlh);
+
+	start->nla_type = NLA_F_NESTED | type;
+	nlh->nlmsg_len += MNL_ALIGN(sizeof(struct nlattr));
+	return start;
+}
+
+static bool mnl_attr_put_check(struct nlmsghdr *nlh, size_t buflen,
+			       uint16_t type, size_t len, const void *data)
+{
+	if (nlh->nlmsg_len + MNL_ATTR_HDRLEN + MNL_ALIGN(len) > buflen)
+		return false;
+	mnl_attr_put(nlh, type, len, data);
+	return true;
+}
+
+static bool mnl_attr_put_u8_check(struct nlmsghdr *nlh, size_t buflen,
+				  uint16_t type, uint8_t data)
+{
+	return mnl_attr_put_check(nlh, buflen, type, sizeof(uint8_t), &data);
+}
+
+
+static bool mnl_attr_put_u16_check(struct nlmsghdr *nlh, size_t buflen,
+				   uint16_t type, uint16_t data)
+{
+	return mnl_attr_put_check(nlh, buflen, type, sizeof(uint16_t), &data);
+}
+
+
+static bool mnl_attr_put_u32_check(struct nlmsghdr *nlh, size_t buflen,
+				   uint16_t type, uint32_t data)
+{
+	return mnl_attr_put_check(nlh, buflen, type, sizeof(uint32_t), &data);
+}
+
+static struct nlattr *mnl_attr_nest_start_check(struct nlmsghdr *nlh, size_t buflen,
+						uint16_t type)
+{
+	if (nlh->nlmsg_len + MNL_ATTR_HDRLEN > buflen)
+		return NULL;
+	return mnl_attr_nest_start(nlh, type);
+}
+
+static void mnl_attr_nest_end(struct nlmsghdr *nlh, struct nlattr *start)
+{
+	start->nla_len = mnl_nlmsg_get_payload_tail(nlh) - (void *)start;
+}
+
+static void mnl_attr_nest_cancel(struct nlmsghdr *nlh, struct nlattr *start)
+{
+	nlh->nlmsg_len -= mnl_nlmsg_get_payload_tail(nlh) - (void *)start;
+}
+
+static int mnl_cb_noop(const struct nlmsghdr *nlh, void *data)
+{
+	return MNL_CB_OK;
+}
+
+static int mnl_cb_error(const struct nlmsghdr *nlh, void *data)
+{
+	const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
+
+	if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) {
+		errno = EBADMSG;
+		return MNL_CB_ERROR;
+	}
+
+	if (err->error < 0)
+		errno = -err->error;
+	else
+		errno = err->error;
+
+	return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+}
+
+static int mnl_cb_stop(const struct nlmsghdr *nlh, void *data)
+{
+	return MNL_CB_STOP;
+}
+
+static const mnl_cb_t default_cb_array[NLMSG_MIN_TYPE] = {
+	[NLMSG_NOOP]	= mnl_cb_noop,
+	[NLMSG_ERROR]	= mnl_cb_error,
+	[NLMSG_DONE]	= mnl_cb_stop,
+	[NLMSG_OVERRUN]	= mnl_cb_noop,
+};
+
+static int __mnl_cb_run(const void *buf, size_t numbytes,
+			unsigned int seq, unsigned int portid,
+			mnl_cb_t cb_data, void *data,
+			const mnl_cb_t *cb_ctl_array,
+			unsigned int cb_ctl_array_len)
+{
+	int ret = MNL_CB_OK, len = numbytes;
+	const struct nlmsghdr *nlh = buf;
+
+	while (mnl_nlmsg_ok(nlh, len)) {
+
+		if (!mnl_nlmsg_portid_ok(nlh, portid)) {
+			errno = ESRCH;
+			return -1;
+		}
+
+		if (!mnl_nlmsg_seq_ok(nlh, seq)) {
+			errno = EPROTO;
+			return -1;
+		}
+
+
+		if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) {
+			errno = EINTR;
+			return -1;
+		}
+
+
+		if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) {
+			if (cb_data){
+				ret = cb_data(nlh, data);
+				if (ret <= MNL_CB_STOP)
+					goto out;
+			}
+		} else if (nlh->nlmsg_type < cb_ctl_array_len) {
+			if (cb_ctl_array && cb_ctl_array[nlh->nlmsg_type]) {
+				ret = cb_ctl_array[nlh->nlmsg_type](nlh, data);
+				if (ret <= MNL_CB_STOP)
+					goto out;
+			}
+		} else if (default_cb_array[nlh->nlmsg_type]) {
+			ret = default_cb_array[nlh->nlmsg_type](nlh, data);
+			if (ret <= MNL_CB_STOP)
+				goto out;
+		}
+		nlh = mnl_nlmsg_next(nlh, &len);
+	}
+out:
+	return ret;
+}
+
+static int mnl_cb_run2(const void *buf, size_t numbytes, unsigned int seq,
+		       unsigned int portid, mnl_cb_t cb_data, void *data,
+		       const mnl_cb_t *cb_ctl_array, unsigned int cb_ctl_array_len)
+{
+	return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data,
+			    cb_ctl_array, cb_ctl_array_len);
+}
+
+static int mnl_cb_run(const void *buf, size_t numbytes, unsigned int seq,
+		      unsigned int portid, mnl_cb_t cb_data, void *data)
+{
+	return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data, NULL, 0);
+}
+
+struct mnl_socket {
+	int 			fd;
+	struct sockaddr_nl	addr;
+};
+
+static unsigned int mnl_socket_get_portid(const struct mnl_socket *nl)
+{
+	return nl->addr.nl_pid;
+}
+
+static struct mnl_socket *__mnl_socket_open(int bus, int flags)
+{
+	struct mnl_socket *nl;
+
+	nl = calloc(1, sizeof(struct mnl_socket));
+	if (nl == NULL)
+		return NULL;
+
+	nl->fd = socket(AF_NETLINK, SOCK_RAW | flags, bus);
+	if (nl->fd == -1) {
+		free(nl);
+		return NULL;
+	}
+
+	return nl;
+}
+
+static struct mnl_socket *mnl_socket_open(int bus)
+{
+	return __mnl_socket_open(bus, 0);
+}
+
+
+static int mnl_socket_bind(struct mnl_socket *nl, unsigned int groups, pid_t pid)
+{
+	int ret;
+	socklen_t addr_len;
+
+	nl->addr.nl_family = AF_NETLINK;
+	nl->addr.nl_groups = groups;
+	nl->addr.nl_pid = pid;
+
+	ret = bind(nl->fd, (struct sockaddr *) &nl->addr, sizeof (nl->addr));
+	if (ret < 0)
+		return ret;
+
+	addr_len = sizeof(nl->addr);
+	ret = getsockname(nl->fd, (struct sockaddr *) &nl->addr, &addr_len);
+	if (ret < 0)
+		return ret;
+
+	if (addr_len != sizeof(nl->addr)) {
+		errno = EINVAL;
+		return -1;
+	}
+	if (nl->addr.nl_family != AF_NETLINK) {
+		errno = EINVAL;
+		return -1;
+	}
+	return 0;
+}
+
+
+static ssize_t mnl_socket_sendto(const struct mnl_socket *nl, const void *buf,
+				 size_t len)
+{
+	static const struct sockaddr_nl snl = {
+		.nl_family = AF_NETLINK
+	};
+	return sendto(nl->fd, buf, len, 0,
+		      (struct sockaddr *) &snl, sizeof(snl));
+}
+
+
+static ssize_t mnl_socket_recvfrom(const struct mnl_socket *nl, void *buf,
+				   size_t bufsiz)
+{
+	ssize_t ret;
+	struct sockaddr_nl addr;
+	struct iovec iov = {
+		.iov_base	= buf,
+		.iov_len	= bufsiz,
+	};
+	struct msghdr msg = {
+		.msg_name	= &addr,
+		.msg_namelen	= sizeof(struct sockaddr_nl),
+		.msg_iov	= &iov,
+		.msg_iovlen	= 1,
+		.msg_control	= NULL,
+		.msg_controllen	= 0,
+		.msg_flags	= 0,
+	};
+	ret = recvmsg(nl->fd, &msg, 0);
+	if (ret == -1)
+		return ret;
+
+	if (msg.msg_flags & MSG_TRUNC) {
+		errno = ENOSPC;
+		return -1;
+	}
+	if (msg.msg_namelen != sizeof(struct sockaddr_nl)) {
+		errno = EINVAL;
+		return -1;
+	}
+	return ret;
+}
+
+static int mnl_socket_close(struct mnl_socket *nl)
+{
+	int ret = close(nl->fd);
+	free(nl);
+	return ret;
+}
+
+/* mnlg mini library: */
+
+struct mnlg_socket {
+	struct mnl_socket *nl;
+	char *buf;
+	uint16_t id;
+	uint8_t version;
+	unsigned int seq;
+	unsigned int portid;
+};
+
+static struct nlmsghdr *__mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+					   uint16_t flags, uint16_t id,
+					   uint8_t version)
+{
+	struct nlmsghdr *nlh;
+	struct genlmsghdr *genl;
+
+	nlh = mnl_nlmsg_put_header(nlg->buf);
+	nlh->nlmsg_type	= id;
+	nlh->nlmsg_flags = flags;
+	nlg->seq = time(NULL);
+	nlh->nlmsg_seq = nlg->seq;
+
+	genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+	genl->cmd = cmd;
+	genl->version = version;
+
+	return nlh;
+}
+
+static struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd,
+					 uint16_t flags)
+{
+	return __mnlg_msg_prepare(nlg, cmd, flags, nlg->id, nlg->version);
+}
+
+static int mnlg_socket_send(struct mnlg_socket *nlg, const struct nlmsghdr *nlh)
+{
+	return mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len);
+}
+
+static int mnlg_cb_noop(const struct nlmsghdr *nlh, void *data)
+{
+	(void)nlh;
+	(void)data;
+	return MNL_CB_OK;
+}
+
+static int mnlg_cb_error(const struct nlmsghdr *nlh, void *data)
+{
+	const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh);
+	(void)data;
+
+	if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) {
+		errno = EBADMSG;
+		return MNL_CB_ERROR;
+	}
+	/* Netlink subsystems returns the errno value with different signess */
+	if (err->error < 0)
+		errno = -err->error;
+	else
+		errno = err->error;
+
+	return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+}
+
+static int mnlg_cb_stop(const struct nlmsghdr *nlh, void *data)
+{
+	(void)data;
+	if (nlh->nlmsg_flags & NLM_F_MULTI && nlh->nlmsg_len == mnl_nlmsg_size(sizeof(int))) {
+		int error = *(int *)mnl_nlmsg_get_payload(nlh);
+		/* Netlink subsystems returns the errno value with different signess */
+		if (error < 0)
+			errno = -error;
+		else
+			errno = error;
+
+		return error == 0 ? MNL_CB_STOP : MNL_CB_ERROR;
+	}
+	return MNL_CB_STOP;
+}
+
+static const mnl_cb_t mnlg_cb_array[] = {
+	[NLMSG_NOOP]	= mnlg_cb_noop,
+	[NLMSG_ERROR]	= mnlg_cb_error,
+	[NLMSG_DONE]	= mnlg_cb_stop,
+	[NLMSG_OVERRUN]	= mnlg_cb_noop,
+};
+
+static int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data)
+{
+	int err;
+
+	do {
+		err = mnl_socket_recvfrom(nlg->nl, nlg->buf,
+					  MNL_SOCKET_BUFFER_SIZE);
+		if (err <= 0)
+			break;
+		err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid,
+				  data_cb, data, mnlg_cb_array, MNL_ARRAY_SIZE(mnlg_cb_array));
+	} while (err > 0);
+
+	return err;
+}
+
+static int get_family_id_attr_cb(const struct nlattr *attr, void *data)
+{
+	const struct nlattr **tb = data;
+	int type = mnl_attr_get_type(attr);
+
+	if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+		return MNL_CB_ERROR;
+
+	if (type == CTRL_ATTR_FAMILY_ID &&
+	    mnl_attr_validate(attr, MNL_TYPE_U16) < 0)
+		return MNL_CB_ERROR;
+	tb[type] = attr;
+	return MNL_CB_OK;
+}
+
+static int get_family_id_cb(const struct nlmsghdr *nlh, void *data)
+{
+	uint16_t *p_id = data;
+	struct nlattr *tb[CTRL_ATTR_MAX + 1] = { 0 };
+
+	mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_family_id_attr_cb, tb);
+	if (!tb[CTRL_ATTR_FAMILY_ID])
+		return MNL_CB_ERROR;
+	*p_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
+	return MNL_CB_OK;
+}
+
+static struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version)
+{
+	struct mnlg_socket *nlg;
+	struct nlmsghdr *nlh;
+	int err;
+
+	nlg = malloc(sizeof(*nlg));
+	if (!nlg)
+		return NULL;
+
+	err = -ENOMEM;
+	nlg->buf = malloc(MNL_SOCKET_BUFFER_SIZE);
+	if (!nlg->buf)
+		goto err_buf_alloc;
+
+	nlg->nl = mnl_socket_open(NETLINK_GENERIC);
+	if (!nlg->nl) {
+		err = -errno;
+		goto err_mnl_socket_open;
+	}
+
+	if (mnl_socket_bind(nlg->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		err = -errno;
+		goto err_mnl_socket_bind;
+	}
+
+	nlg->portid = mnl_socket_get_portid(nlg->nl);
+
+	nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY,
+				 NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1);
+	mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name);
+
+	if (mnlg_socket_send(nlg, nlh) < 0) {
+		err = -errno;
+		goto err_mnlg_socket_send;
+	}
+
+	errno = 0;
+	if (mnlg_socket_recv_run(nlg, get_family_id_cb, &nlg->id) < 0) {
+		errno = errno == ENOENT ? EPROTONOSUPPORT : errno;
+		err = errno ? -errno : -ENOSYS;
+		goto err_mnlg_socket_recv_run;
+	}
+
+	nlg->version = version;
+	errno = 0;
+	return nlg;
+
+err_mnlg_socket_recv_run:
+err_mnlg_socket_send:
+err_mnl_socket_bind:
+	mnl_socket_close(nlg->nl);
+err_mnl_socket_open:
+	free(nlg->buf);
+err_buf_alloc:
+	free(nlg);
+	errno = -err;
+	return NULL;
+}
+
+static void mnlg_socket_close(struct mnlg_socket *nlg)
+{
+	mnl_socket_close(nlg->nl);
+	free(nlg->buf);
+	free(nlg);
+}
+
+/* wireguard-specific parts: */
+
+struct inflatable_buffer {
+	char *buffer;
+	char *next;
+	bool good;
+	size_t len;
+	size_t pos;
+};
+
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+static int add_next_to_inflatable_buffer(struct inflatable_buffer *buffer)
+{
+	size_t len, expand_to;
+	char *new_buffer;
+
+	if (!buffer->good || !buffer->next) {
+		free(buffer->next);
+		buffer->good = false;
+		return 0;
+	}
+
+	len = strlen(buffer->next) + 1;
+
+	if (len == 1) {
+		free(buffer->next);
+		buffer->good = false;
+		return 0;
+	}
+
+	if (buffer->len - buffer->pos <= len) {
+		expand_to = max(buffer->len * 2, buffer->len + len + 1);
+		new_buffer = realloc(buffer->buffer, expand_to);
+		if (!new_buffer) {
+			free(buffer->next);
+			buffer->good = false;
+			return -errno;
+		}
+		memset(&new_buffer[buffer->len], 0, expand_to - buffer->len);
+		buffer->buffer = new_buffer;
+		buffer->len = expand_to;
+	}
+	memcpy(&buffer->buffer[buffer->pos], buffer->next, len);
+	free(buffer->next);
+	buffer->good = false;
+	buffer->pos += len;
+	return 0;
+}
+
+static int parse_linkinfo(const struct nlattr *attr, void *data)
+{
+	struct inflatable_buffer *buffer = data;
+
+	if (mnl_attr_get_type(attr) == IFLA_INFO_KIND && !strcmp(WG_GENL_NAME, mnl_attr_get_str(attr)))
+		buffer->good = true;
+	return MNL_CB_OK;
+}
+
+static int parse_infomsg(const struct nlattr *attr, void *data)
+{
+	struct inflatable_buffer *buffer = data;
+
+	if (mnl_attr_get_type(attr) == IFLA_LINKINFO)
+		return mnl_attr_parse_nested(attr, parse_linkinfo, data);
+	else if (mnl_attr_get_type(attr) == IFLA_IFNAME)
+		buffer->next = strdup(mnl_attr_get_str(attr));
+	return MNL_CB_OK;
+}
+
+static int read_devices_cb(const struct nlmsghdr *nlh, void *data)
+{
+	struct inflatable_buffer *buffer = data;
+	int ret;
+
+	buffer->good = false;
+	buffer->next = NULL;
+	ret = mnl_attr_parse(nlh, sizeof(struct ifinfomsg), parse_infomsg, data);
+	if (ret != MNL_CB_OK)
+		return ret;
+	ret = add_next_to_inflatable_buffer(buffer);
+	if (ret < 0)
+		return ret;
+	if (nlh->nlmsg_type != NLMSG_DONE)
+		return MNL_CB_OK + 1;
+	return MNL_CB_OK;
+}
+
+static int fetch_device_names(struct inflatable_buffer *buffer)
+{
+	struct mnl_socket *nl = NULL;
+	char *rtnl_buffer = NULL;
+	size_t message_len;
+	unsigned int portid, seq;
+	ssize_t len;
+	int ret = 0;
+	struct nlmsghdr *nlh;
+	struct ifinfomsg *ifm;
+
+	ret = -ENOMEM;
+	rtnl_buffer = calloc(MNL_SOCKET_BUFFER_SIZE, 1);
+	if (!rtnl_buffer)
+		goto cleanup;
+
+	nl = mnl_socket_open(NETLINK_ROUTE);
+	if (!nl) {
+		ret = -errno;
+		goto cleanup;
+	}
+
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		ret = -errno;
+		goto cleanup;
+	}
+
+	seq = time(NULL);
+	portid = mnl_socket_get_portid(nl);
+	nlh = mnl_nlmsg_put_header(rtnl_buffer);
+	nlh->nlmsg_type = RTM_GETLINK;
+	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP;
+	nlh->nlmsg_seq = seq;
+	ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+	ifm->ifi_family = AF_UNSPEC;
+	message_len = nlh->nlmsg_len;
+
+	if (mnl_socket_sendto(nl, rtnl_buffer, message_len) < 0) {
+		ret = -errno;
+		goto cleanup;
+	}
+
+another:
+	if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, MNL_SOCKET_BUFFER_SIZE)) < 0) {
+		ret = -errno;
+		goto cleanup;
+	}
+	if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, buffer)) < 0) {
+		/* Netlink returns NLM_F_DUMP_INTR if the set of all tunnels changed
+		 * during the dump. That's unfortunate, but is pretty common on busy
+		 * systems that are adding and removing tunnels all the time. Rather
+		 * than retrying, potentially indefinitely, we just work with the
+		 * partial results. */
+		if (errno != EINTR) {
+			ret = -errno;
+			goto cleanup;
+		}
+	}
+	if (len == MNL_CB_OK + 1)
+		goto another;
+	ret = 0;
+
+cleanup:
+	free(rtnl_buffer);
+	if (nl)
+		mnl_socket_close(nl);
+	return ret;
+}
+
+static int add_del_iface(const char *ifname, bool add)
+{
+	struct mnl_socket *nl = NULL;
+	char *rtnl_buffer;
+	ssize_t len;
+	int ret;
+	struct nlmsghdr *nlh;
+	struct ifinfomsg *ifm;
+	struct nlattr *nest;
+
+	rtnl_buffer = calloc(MNL_SOCKET_BUFFER_SIZE, 1);
+	if (!rtnl_buffer) {
+		ret = -ENOMEM;
+		goto cleanup;
+	}
+
+	nl = mnl_socket_open(NETLINK_ROUTE);
+	if (!nl) {
+		ret = -errno;
+		goto cleanup;
+	}
+
+	if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
+		ret = -errno;
+		goto cleanup;
+	}
+
+	nlh = mnl_nlmsg_put_header(rtnl_buffer);
+	nlh->nlmsg_type = add ? RTM_NEWLINK : RTM_DELLINK;
+	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | (add ? NLM_F_CREATE | NLM_F_EXCL : 0);
+	nlh->nlmsg_seq = time(NULL);
+	ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+	ifm->ifi_family = AF_UNSPEC;
+	mnl_attr_put_strz(nlh, IFLA_IFNAME, ifname);
+	nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO);
+	mnl_attr_put_strz(nlh, IFLA_INFO_KIND, WG_GENL_NAME);
+	mnl_attr_nest_end(nlh, nest);
+
+	if (mnl_socket_sendto(nl, rtnl_buffer, nlh->nlmsg_len) < 0) {
+		ret = -errno;
+		goto cleanup;
+	}
+	if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, MNL_SOCKET_BUFFER_SIZE)) < 0) {
+		ret = -errno;
+		goto cleanup;
+	}
+	if (mnl_cb_run(rtnl_buffer, len, nlh->nlmsg_seq, mnl_socket_get_portid(nl), NULL, NULL) < 0) {
+		ret = -errno;
+		goto cleanup;
+	}
+	ret = 0;
+
+cleanup:
+	free(rtnl_buffer);
+	if (nl)
+		mnl_socket_close(nl);
+	return ret;
+}
+
+int wg_set_device(wg_device *dev)
+{
+	int ret = 0;
+	wg_peer *peer = NULL;
+	wg_allowedip *allowedip = NULL;
+	struct nlattr *peers_nest, *peer_nest, *allowedips_nest, *allowedip_nest;
+	struct nlmsghdr *nlh;
+	struct mnlg_socket *nlg;
+
+	nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
+	if (!nlg)
+		return -errno;
+
+again:
+	nlh = mnlg_msg_prepare(nlg, WG_CMD_SET_DEVICE, NLM_F_REQUEST | NLM_F_ACK);
+	mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, dev->name);
+
+	if (!peer) {
+		uint32_t flags = 0;
+
+		if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY)
+			mnl_attr_put(nlh, WGDEVICE_A_PRIVATE_KEY, sizeof(dev->private_key), dev->private_key);
+		if (dev->flags & WGDEVICE_HAS_LISTEN_PORT)
+			mnl_attr_put_u16(nlh, WGDEVICE_A_LISTEN_PORT, dev->listen_port);
+		if (dev->flags & WGDEVICE_HAS_FWMARK)
+			mnl_attr_put_u32(nlh, WGDEVICE_A_FWMARK, dev->fwmark);
+		if (dev->flags & WGDEVICE_REPLACE_PEERS)
+			flags |= WGDEVICE_F_REPLACE_PEERS;
+		if (flags)
+			mnl_attr_put_u32(nlh, WGDEVICE_A_FLAGS, flags);
+	}
+	if (!dev->first_peer)
+		goto send;
+	peers_nest = peer_nest = allowedips_nest = allowedip_nest = NULL;
+	peers_nest = mnl_attr_nest_start(nlh, WGDEVICE_A_PEERS);
+	for (peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) {
+		uint32_t flags = 0;
+
+		peer_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, 0);
+		if (!peer_nest)
+			goto toobig_peers;
+		if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PUBLIC_KEY, sizeof(peer->public_key), peer->public_key))
+			goto toobig_peers;
+		if (peer->flags & WGPEER_REMOVE_ME)
+			flags |= WGPEER_F_REMOVE_ME;
+		if (!allowedip) {
+			if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS)
+				flags |= WGPEER_F_REPLACE_ALLOWEDIPS;
+			if (peer->flags & WGPEER_HAS_PRESHARED_KEY) {
+				if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PRESHARED_KEY, sizeof(peer->preshared_key), peer->preshared_key))
+					goto toobig_peers;
+			}
+			if (peer->endpoint.addr.sa_family == AF_INET) {
+				if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr4), &peer->endpoint.addr4))
+					goto toobig_peers;
+			} else if (peer->endpoint.addr.sa_family == AF_INET6) {
+				if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr6), &peer->endpoint.addr6))
+					goto toobig_peers;
+			}
+			if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) {
+				if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval))
+					goto toobig_peers;
+			}
+		}
+		if (flags) {
+			if (!mnl_attr_put_u32_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_FLAGS, flags))
+				goto toobig_peers;
+		}
+		if (peer->first_allowedip) {
+			if (!allowedip)
+				allowedip = peer->first_allowedip;
+			allowedips_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGPEER_A_ALLOWEDIPS);
+			if (!allowedips_nest)
+				goto toobig_allowedips;
+			for (; allowedip; allowedip = allowedip->next_allowedip) {
+				allowedip_nest = mnl_attr_nest_start_check(nlh, MNL_SOCKET_BUFFER_SIZE, 0);
+				if (!allowedip_nest)
+					goto toobig_allowedips;
+				if (!mnl_attr_put_u16_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_FAMILY, allowedip->family))
+					goto toobig_allowedips;
+				if (allowedip->family == AF_INET) {
+					if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip4), &allowedip->ip4))
+						goto toobig_allowedips;
+				} else if (allowedip->family == AF_INET6) {
+					if (!mnl_attr_put_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), &allowedip->ip6))
+						goto toobig_allowedips;
+				}
+				if (!mnl_attr_put_u8_check(nlh, MNL_SOCKET_BUFFER_SIZE, WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr))
+					goto toobig_allowedips;
+				mnl_attr_nest_end(nlh, allowedip_nest);
+				allowedip_nest = NULL;
+			}
+			mnl_attr_nest_end(nlh, allowedips_nest);
+			allowedips_nest = NULL;
+		}
+
+		mnl_attr_nest_end(nlh, peer_nest);
+		peer_nest = NULL;
+	}
+	mnl_attr_nest_end(nlh, peers_nest);
+	peers_nest = NULL;
+	goto send;
+toobig_allowedips:
+	if (allowedip_nest)
+		mnl_attr_nest_cancel(nlh, allowedip_nest);
+	if (allowedips_nest)
+		mnl_attr_nest_end(nlh, allowedips_nest);
+	mnl_attr_nest_end(nlh, peer_nest);
+	mnl_attr_nest_end(nlh, peers_nest);
+	goto send;
+toobig_peers:
+	if (peer_nest)
+		mnl_attr_nest_cancel(nlh, peer_nest);
+	mnl_attr_nest_end(nlh, peers_nest);
+	goto send;
+send:
+	if (mnlg_socket_send(nlg, nlh) < 0) {
+		ret = -errno;
+		goto out;
+	}
+	errno = 0;
+	if (mnlg_socket_recv_run(nlg, NULL, NULL) < 0) {
+		ret = errno ? -errno : -EINVAL;
+		goto out;
+	}
+	if (peer)
+		goto again;
+
+out:
+	mnlg_socket_close(nlg);
+	errno = -ret;
+	return ret;
+}
+
+static int parse_allowedip(const struct nlattr *attr, void *data)
+{
+	wg_allowedip *allowedip = data;
+
+	switch (mnl_attr_get_type(attr)) {
+	case WGALLOWEDIP_A_UNSPEC:
+		break;
+	case WGALLOWEDIP_A_FAMILY:
+		if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+			allowedip->family = mnl_attr_get_u16(attr);
+		break;
+	case WGALLOWEDIP_A_IPADDR:
+		if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip4))
+			memcpy(&allowedip->ip4, mnl_attr_get_payload(attr), sizeof(allowedip->ip4));
+		else if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip6))
+			memcpy(&allowedip->ip6, mnl_attr_get_payload(attr), sizeof(allowedip->ip6));
+		break;
+	case WGALLOWEDIP_A_CIDR_MASK:
+		if (!mnl_attr_validate(attr, MNL_TYPE_U8))
+			allowedip->cidr = mnl_attr_get_u8(attr);
+		break;
+	}
+
+	return MNL_CB_OK;
+}
+
+static int parse_allowedips(const struct nlattr *attr, void *data)
+{
+	wg_peer *peer = data;
+	wg_allowedip *new_allowedip = calloc(1, sizeof(wg_allowedip));
+	int ret;
+
+	if (!new_allowedip)
+		return MNL_CB_ERROR;
+	if (!peer->first_allowedip)
+		peer->first_allowedip = peer->last_allowedip = new_allowedip;
+	else {
+		peer->last_allowedip->next_allowedip = new_allowedip;
+		peer->last_allowedip = new_allowedip;
+	}
+	ret = mnl_attr_parse_nested(attr, parse_allowedip, new_allowedip);
+	if (!ret)
+		return ret;
+	if (!((new_allowedip->family == AF_INET && new_allowedip->cidr <= 32) || (new_allowedip->family == AF_INET6 && new_allowedip->cidr <= 128))) {
+		errno = EAFNOSUPPORT;
+		return MNL_CB_ERROR;
+	}
+	return MNL_CB_OK;
+}
+
+bool wg_key_is_zero(const wg_key key)
+{
+	volatile uint8_t acc = 0;
+	unsigned int i;
+
+	for (i = 0; i < sizeof(wg_key); ++i) {
+		acc |= key[i];
+		__asm__ ("" : "=r" (acc) : "0" (acc));
+	}
+	return 1 & ((acc - 1) >> 8);
+}
+
+static int parse_peer(const struct nlattr *attr, void *data)
+{
+	wg_peer *peer = data;
+
+	switch (mnl_attr_get_type(attr)) {
+	case WGPEER_A_UNSPEC:
+		break;
+	case WGPEER_A_PUBLIC_KEY:
+		if (mnl_attr_get_payload_len(attr) == sizeof(peer->public_key)) {
+			memcpy(peer->public_key, mnl_attr_get_payload(attr), sizeof(peer->public_key));
+			peer->flags |= WGPEER_HAS_PUBLIC_KEY;
+		}
+		break;
+	case WGPEER_A_PRESHARED_KEY:
+		if (mnl_attr_get_payload_len(attr) == sizeof(peer->preshared_key)) {
+			memcpy(peer->preshared_key, mnl_attr_get_payload(attr), sizeof(peer->preshared_key));
+			if (!wg_key_is_zero(peer->preshared_key))
+				peer->flags |= WGPEER_HAS_PRESHARED_KEY;
+		}
+		break;
+	case WGPEER_A_ENDPOINT: {
+		struct sockaddr *addr;
+
+		if (mnl_attr_get_payload_len(attr) < sizeof(*addr))
+			break;
+		addr = mnl_attr_get_payload(attr);
+		if (addr->sa_family == AF_INET && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr4))
+			memcpy(&peer->endpoint.addr4, addr, sizeof(peer->endpoint.addr4));
+		else if (addr->sa_family == AF_INET6 && mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr6))
+			memcpy(&peer->endpoint.addr6, addr, sizeof(peer->endpoint.addr6));
+		break;
+	}
+	case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL:
+		if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+			peer->persistent_keepalive_interval = mnl_attr_get_u16(attr);
+		break;
+	case WGPEER_A_LAST_HANDSHAKE_TIME:
+		if (mnl_attr_get_payload_len(attr) == sizeof(peer->last_handshake_time))
+			memcpy(&peer->last_handshake_time, mnl_attr_get_payload(attr), sizeof(peer->last_handshake_time));
+		break;
+	case WGPEER_A_RX_BYTES:
+		if (!mnl_attr_validate(attr, MNL_TYPE_U64))
+			peer->rx_bytes = mnl_attr_get_u64(attr);
+		break;
+	case WGPEER_A_TX_BYTES:
+		if (!mnl_attr_validate(attr, MNL_TYPE_U64))
+			peer->tx_bytes = mnl_attr_get_u64(attr);
+		break;
+	case WGPEER_A_ALLOWEDIPS:
+		return mnl_attr_parse_nested(attr, parse_allowedips, peer);
+	}
+
+	return MNL_CB_OK;
+}
+
+static int parse_peers(const struct nlattr *attr, void *data)
+{
+	wg_device *device = data;
+	wg_peer *new_peer = calloc(1, sizeof(wg_peer));
+	int ret;
+
+	if (!new_peer)
+		return MNL_CB_ERROR;
+	if (!device->first_peer)
+		device->first_peer = device->last_peer = new_peer;
+	else {
+		device->last_peer->next_peer = new_peer;
+		device->last_peer = new_peer;
+	}
+	ret = mnl_attr_parse_nested(attr, parse_peer, new_peer);
+	if (!ret)
+		return ret;
+	if (!(new_peer->flags & WGPEER_HAS_PUBLIC_KEY)) {
+		errno = ENXIO;
+		return MNL_CB_ERROR;
+	}
+	return MNL_CB_OK;
+}
+
+static int parse_device(const struct nlattr *attr, void *data)
+{
+	wg_device *device = data;
+
+	switch (mnl_attr_get_type(attr)) {
+	case WGDEVICE_A_UNSPEC:
+		break;
+	case WGDEVICE_A_IFINDEX:
+		if (!mnl_attr_validate(attr, MNL_TYPE_U32))
+			device->ifindex = mnl_attr_get_u32(attr);
+		break;
+	case WGDEVICE_A_IFNAME:
+		if (!mnl_attr_validate(attr, MNL_TYPE_STRING)) {
+			strncpy(device->name, mnl_attr_get_str(attr), sizeof(device->name) - 1);
+			device->name[sizeof(device->name) - 1] = '\0';
+		}
+		break;
+	case WGDEVICE_A_PRIVATE_KEY:
+		if (mnl_attr_get_payload_len(attr) == sizeof(device->private_key)) {
+			memcpy(device->private_key, mnl_attr_get_payload(attr), sizeof(device->private_key));
+			device->flags |= WGDEVICE_HAS_PRIVATE_KEY;
+		}
+		break;
+	case WGDEVICE_A_PUBLIC_KEY:
+		if (mnl_attr_get_payload_len(attr) == sizeof(device->public_key)) {
+			memcpy(device->public_key, mnl_attr_get_payload(attr), sizeof(device->public_key));
+			device->flags |= WGDEVICE_HAS_PUBLIC_KEY;
+		}
+		break;
+	case WGDEVICE_A_LISTEN_PORT:
+		if (!mnl_attr_validate(attr, MNL_TYPE_U16))
+			device->listen_port = mnl_attr_get_u16(attr);
+		break;
+	case WGDEVICE_A_FWMARK:
+		if (!mnl_attr_validate(attr, MNL_TYPE_U32))
+			device->fwmark = mnl_attr_get_u32(attr);
+		break;
+	case WGDEVICE_A_PEERS:
+		return mnl_attr_parse_nested(attr, parse_peers, device);
+	}
+
+	return MNL_CB_OK;
+}
+
+static int read_device_cb(const struct nlmsghdr *nlh, void *data)
+{
+	return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_device, data);
+}
+
+static void coalesce_peers(wg_device *device)
+{
+	wg_peer *old_next_peer, *peer = device->first_peer;
+
+	while (peer && peer->next_peer) {
+		if (memcmp(peer->public_key, peer->next_peer->public_key, sizeof(wg_key))) {
+			peer = peer->next_peer;
+			continue;
+		}
+		if (!peer->first_allowedip) {
+			peer->first_allowedip = peer->next_peer->first_allowedip;
+			peer->last_allowedip = peer->next_peer->last_allowedip;
+		} else {
+			peer->last_allowedip->next_allowedip = peer->next_peer->first_allowedip;
+			peer->last_allowedip = peer->next_peer->last_allowedip;
+		}
+		old_next_peer = peer->next_peer;
+		peer->next_peer = old_next_peer->next_peer;
+		free(old_next_peer);
+	}
+}
+
+int wg_get_device(wg_device **device, const char *device_name)
+{
+	int ret = 0;
+	struct nlmsghdr *nlh;
+	struct mnlg_socket *nlg;
+
+try_again:
+	*device = calloc(1, sizeof(wg_device));
+	if (!*device)
+		return -errno;
+
+	nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION);
+	if (!nlg) {
+		wg_free_device(*device);
+		*device = NULL;
+		return -errno;
+	}
+
+	nlh = mnlg_msg_prepare(nlg, WG_CMD_GET_DEVICE, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP);
+	mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, device_name);
+	if (mnlg_socket_send(nlg, nlh) < 0) {
+		ret = -errno;
+		goto out;
+	}
+	errno = 0;
+	if (mnlg_socket_recv_run(nlg, read_device_cb, *device) < 0) {
+		ret = errno ? -errno : -EINVAL;
+		goto out;
+	}
+	coalesce_peers(*device);
+
+out:
+	if (nlg)
+		mnlg_socket_close(nlg);
+	if (ret) {
+		wg_free_device(*device);
+		if (ret == -EINTR)
+			goto try_again;
+		*device = NULL;
+	}
+	errno = -ret;
+	return ret;
+}
+
+/* first\0second\0third\0forth\0last\0\0 */
+char *wg_list_device_names(void)
+{
+	struct inflatable_buffer buffer = { .len = MNL_SOCKET_BUFFER_SIZE };
+	int ret;
+
+	ret = -ENOMEM;
+	buffer.buffer = calloc(1, buffer.len);
+	if (!buffer.buffer)
+		goto err;
+
+	ret = fetch_device_names(&buffer);
+err:
+	errno = -ret;
+	if (errno) {
+		free(buffer.buffer);
+		return NULL;
+	}
+	return buffer.buffer;
+}
+
+int wg_add_device(const char *device_name)
+{
+	return add_del_iface(device_name, true);
+}
+
+int wg_del_device(const char *device_name)
+{
+	return add_del_iface(device_name, false);
+}
+
+void wg_free_device(wg_device *dev)
+{
+	wg_peer *peer, *np;
+	wg_allowedip *allowedip, *na;
+
+	if (!dev)
+		return;
+	for (peer = dev->first_peer, np = peer ? peer->next_peer : NULL; peer; peer = np, np = peer ? peer->next_peer : NULL) {
+		for (allowedip = peer->first_allowedip, na = allowedip ? allowedip->next_allowedip : NULL; allowedip; allowedip = na, na = allowedip ? allowedip->next_allowedip : NULL)
+			free(allowedip);
+		free(peer);
+	}
+	free(dev);
+}
+
+static void encode_base64(char dest[static 4], const uint8_t src[static 3])
+{
+	const uint8_t input[] = { (src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63 };
+	unsigned int i;
+
+	for (i = 0; i < 4; ++i)
+		dest[i] = input[i] + 'A'
+			  + (((25 - input[i]) >> 8) & 6)
+			  - (((51 - input[i]) >> 8) & 75)
+			  - (((61 - input[i]) >> 8) & 15)
+			  + (((62 - input[i]) >> 8) & 3);
+
+}
+
+void wg_key_to_base64(wg_key_b64_string base64, const wg_key key)
+{
+	unsigned int i;
+
+	for (i = 0; i < 32 / 3; ++i)
+		encode_base64(&base64[i * 4], &key[i * 3]);
+	encode_base64(&base64[i * 4], (const uint8_t[]){ key[i * 3 + 0], key[i * 3 + 1], 0 });
+	base64[sizeof(wg_key_b64_string) - 2] = '=';
+	base64[sizeof(wg_key_b64_string) - 1] = '\0';
+}
+
+static int decode_base64(const char src[static 4])
+{
+	int val = 0;
+	unsigned int i;
+
+	for (i = 0; i < 4; ++i)
+		val |= (-1
+			    + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64))
+			    + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70))
+			    + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5))
+			    + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63)
+			    + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64)
+			) << (18 - 6 * i);
+	return val;
+}
+
+int wg_key_from_base64(wg_key key, const wg_key_b64_string base64)
+{
+	unsigned int i;
+	int val;
+	volatile uint8_t ret = 0;
+
+	if (strlen(base64) != sizeof(wg_key_b64_string) - 1 || base64[sizeof(wg_key_b64_string) - 2] != '=') {
+		errno = EINVAL;
+		goto out;
+	}
+
+	for (i = 0; i < 32 / 3; ++i) {
+		val = decode_base64(&base64[i * 4]);
+		ret |= (uint32_t)val >> 31;
+		key[i * 3 + 0] = (val >> 16) & 0xff;
+		key[i * 3 + 1] = (val >> 8) & 0xff;
+		key[i * 3 + 2] = val & 0xff;
+	}
+	val = decode_base64((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' });
+	ret |= ((uint32_t)val >> 31) | (val & 0xff);
+	key[i * 3 + 0] = (val >> 16) & 0xff;
+	key[i * 3 + 1] = (val >> 8) & 0xff;
+	errno = EINVAL & ~((ret - 1) >> 8);
+out:
+	return -errno;
+}
+
+typedef int64_t fe[16];
+
+static __attribute__((noinline)) void memzero_explicit(void *s, size_t count)
+{
+	memset(s, 0, count);
+	__asm__ __volatile__("": :"r"(s) :"memory");
+}
+
+static void carry(fe o)
+{
+	int i;
+
+	for (i = 0; i < 16; ++i) {
+		o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16);
+		o[i] &= 0xffff;
+	}
+}
+
+static void cswap(fe p, fe q, int b)
+{
+	int i;
+	int64_t t, c = ~(b - 1);
+
+	for (i = 0; i < 16; ++i) {
+		t = c & (p[i] ^ q[i]);
+		p[i] ^= t;
+		q[i] ^= t;
+	}
+
+	memzero_explicit(&t, sizeof(t));
+	memzero_explicit(&c, sizeof(c));
+	memzero_explicit(&b, sizeof(b));
+}
+
+static void pack(uint8_t *o, const fe n)
+{
+	int i, j, b;
+	fe m, t;
+
+	memcpy(t, n, sizeof(t));
+	carry(t);
+	carry(t);
+	carry(t);
+	for (j = 0; j < 2; ++j) {
+		m[0] = t[0] - 0xffed;
+		for (i = 1; i < 15; ++i) {
+			m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
+			m[i - 1] &= 0xffff;
+		}
+		m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
+		b = (m[15] >> 16) & 1;
+		m[14] &= 0xffff;
+		cswap(t, m, 1 - b);
+	}
+	for (i = 0; i < 16; ++i) {
+		o[2 * i] = t[i] & 0xff;
+		o[2 * i + 1] = t[i] >> 8;
+	}
+
+	memzero_explicit(m, sizeof(m));
+	memzero_explicit(t, sizeof(t));
+	memzero_explicit(&b, sizeof(b));
+}
+
+static void add(fe o, const fe a, const fe b)
+{
+	int i;
+
+	for (i = 0; i < 16; ++i)
+		o[i] = a[i] + b[i];
+}
+
+static void subtract(fe o, const fe a, const fe b)
+{
+	int i;
+
+	for (i = 0; i < 16; ++i)
+		o[i] = a[i] - b[i];
+}
+
+static void multmod(fe o, const fe a, const fe b)
+{
+	int i, j;
+	int64_t t[31] = { 0 };
+
+	for (i = 0; i < 16; ++i) {
+		for (j = 0; j < 16; ++j)
+			t[i + j] += a[i] * b[j];
+	}
+	for (i = 0; i < 15; ++i)
+		t[i] += 38 * t[i + 16];
+	memcpy(o, t, sizeof(fe));
+	carry(o);
+	carry(o);
+
+	memzero_explicit(t, sizeof(t));
+}
+
+static void invert(fe o, const fe i)
+{
+	fe c;
+	int a;
+
+	memcpy(c, i, sizeof(c));
+	for (a = 253; a >= 0; --a) {
+		multmod(c, c, c);
+		if (a != 2 && a != 4)
+			multmod(c, c, i);
+	}
+	memcpy(o, c, sizeof(fe));
+
+	memzero_explicit(c, sizeof(c));
+}
+
+static void clamp_key(uint8_t *z)
+{
+	z[31] = (z[31] & 127) | 64;
+	z[0] &= 248;
+}
+
+void wg_generate_public_key(wg_key public_key, const wg_key private_key)
+{
+	int i, r;
+	uint8_t z[32];
+	fe a = { 1 }, b = { 9 }, c = { 0 }, d = { 1 }, e, f;
+
+	memcpy(z, private_key, sizeof(z));
+	clamp_key(z);
+
+	for (i = 254; i >= 0; --i) {
+		r = (z[i >> 3] >> (i & 7)) & 1;
+		cswap(a, b, r);
+		cswap(c, d, r);
+		add(e, a, c);
+		subtract(a, a, c);
+		add(c, b, d);
+		subtract(b, b, d);
+		multmod(d, e, e);
+		multmod(f, a, a);
+		multmod(a, c, a);
+		multmod(c, b, e);
+		add(e, a, c);
+		subtract(a, a, c);
+		multmod(b, a, a);
+		subtract(c, d, f);
+		multmod(a, c, (const fe){ 0xdb41, 1 });
+		add(a, a, d);
+		multmod(c, c, a);
+		multmod(a, d, f);
+		multmod(d, b, (const fe){ 9 });
+		multmod(b, e, e);
+		cswap(a, b, r);
+		cswap(c, d, r);
+	}
+	invert(c, c);
+	multmod(a, a, c);
+	pack(public_key, a);
+
+	memzero_explicit(&r, sizeof(r));
+	memzero_explicit(z, sizeof(z));
+	memzero_explicit(a, sizeof(a));
+	memzero_explicit(b, sizeof(b));
+	memzero_explicit(c, sizeof(c));
+	memzero_explicit(d, sizeof(d));
+	memzero_explicit(e, sizeof(e));
+	memzero_explicit(f, sizeof(f));
+}
+
+void wg_generate_private_key(wg_key private_key)
+{
+	wg_generate_preshared_key(private_key);
+	clamp_key(private_key);
+}
+
+void wg_generate_preshared_key(wg_key preshared_key)
+{
+	ssize_t ret;
+	size_t i;
+	int fd;
+#if defined(__OpenBSD__) || (defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) || (defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25)))
+	if (!getentropy(preshared_key, sizeof(wg_key)))
+		return;
+#endif
+#if defined(__NR_getrandom) && defined(__linux__)
+	if (syscall(__NR_getrandom, preshared_key, sizeof(wg_key), 0) == sizeof(wg_key))
+		return;
+#endif
+	fd = open("/dev/urandom", O_RDONLY);
+	assert(fd >= 0);
+	for (i = 0; i < sizeof(wg_key); i += ret) {
+		ret = read(fd, preshared_key + i, sizeof(wg_key) - i);
+		assert(ret > 0);
+	}
+	close(fd);
+}
diff --git a/plugins/libwg.h b/plugins/libwg.h
new file mode 100644
index 000000000000..e7a1bbf0d9e3
--- /dev/null
+++ b/plugins/libwg.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/*
+ * Copyright (C) 2015-2019 Jason A. Donenfeld <Jason at zx2c4.com>. All Rights Reserved.
+ */
+
+#ifndef WIREGUARD_H
+#define WIREGUARD_H
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef uint8_t wg_key[32];
+typedef char wg_key_b64_string[((sizeof(wg_key) + 2) / 3) * 4 + 1];
+
+/* Cross platform __kernel_timespec */
+struct timespec64 {
+	int64_t tv_sec;
+	int64_t tv_nsec;
+};
+
+typedef struct wg_allowedip {
+	uint16_t family;
+	union {
+		struct in_addr ip4;
+		struct in6_addr ip6;
+	};
+	uint8_t cidr;
+	struct wg_allowedip *next_allowedip;
+} wg_allowedip;
+
+enum wg_peer_flags {
+	WGPEER_REMOVE_ME = 1U << 0,
+	WGPEER_REPLACE_ALLOWEDIPS = 1U << 1,
+	WGPEER_HAS_PUBLIC_KEY = 1U << 2,
+	WGPEER_HAS_PRESHARED_KEY = 1U << 3,
+	WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL = 1U << 4
+};
+
+typedef struct wg_peer {
+	enum wg_peer_flags flags;
+
+	wg_key public_key;
+	wg_key preshared_key;
+
+	union {
+		struct sockaddr addr;
+		struct sockaddr_in addr4;
+		struct sockaddr_in6 addr6;
+	} endpoint;
+
+	struct timespec64 last_handshake_time;
+	uint64_t rx_bytes, tx_bytes;
+	uint16_t persistent_keepalive_interval;
+
+	struct wg_allowedip *first_allowedip, *last_allowedip;
+	struct wg_peer *next_peer;
+} wg_peer;
+
+enum wg_device_flags {
+	WGDEVICE_REPLACE_PEERS = 1U << 0,
+	WGDEVICE_HAS_PRIVATE_KEY = 1U << 1,
+	WGDEVICE_HAS_PUBLIC_KEY = 1U << 2,
+	WGDEVICE_HAS_LISTEN_PORT = 1U << 3,
+	WGDEVICE_HAS_FWMARK = 1U << 4
+};
+
+typedef struct wg_device {
+	char name[IFNAMSIZ];
+	uint32_t ifindex;
+
+	enum wg_device_flags flags;
+
+	wg_key public_key;
+	wg_key private_key;
+
+	uint32_t fwmark;
+	uint16_t listen_port;
+
+	struct wg_peer *first_peer, *last_peer;
+} wg_device;
+
+#define wg_for_each_device_name(__names, __name, __len) for ((__name) = (__names), (__len) = 0; ((__len) = strlen(__name)); (__name) += (__len) + 1)
+#define wg_for_each_peer(__dev, __peer) for ((__peer) = (__dev)->first_peer; (__peer); (__peer) = (__peer)->next_peer)
+#define wg_for_each_allowedip(__peer, __allowedip) for ((__allowedip) = (__peer)->first_allowedip; (__allowedip); (__allowedip) = (__allowedip)->next_allowedip)
+
+int wg_set_device(wg_device *dev);
+int wg_get_device(wg_device **dev, const char *device_name);
+int wg_add_device(const char *device_name);
+int wg_del_device(const char *device_name);
+void wg_free_device(wg_device *dev);
+char *wg_list_device_names(void); /* first\0second\0third\0forth\0last\0\0 */
+void wg_key_to_base64(wg_key_b64_string base64, const wg_key key);
+int wg_key_from_base64(wg_key key, const wg_key_b64_string base64);
+bool wg_key_is_zero(const wg_key key);
+void wg_generate_public_key(wg_key public_key, const wg_key private_key);
+void wg_generate_private_key(wg_key private_key);
+void wg_generate_preshared_key(wg_key preshared_key);
+
+#endif
diff --git a/plugins/wifi.c b/plugins/wifi.c
index 910b739dff78..1ba17496442b 100644
--- a/plugins/wifi.c
+++ b/plugins/wifi.c
@@ -1096,15 +1096,15 @@ static int get_hidden_connections(GSupplicantScanParams *scan_data)
 	for (i = 0; entries && entries[i]; i++) {
 		int len;
 
-		if (!entries[i]->hidden)
+		if (!entries[i]->u.wifi.hidden)
 			continue;
 
-		if (!entries[i]->ssid) {
-			ssid = entries[i]->name;
+		if (!entries[i]->u.wifi.ssid) {
+			ssid = entries[i]->u.wifi.name;
 			len = strlen(ssid);
 		} else {
-			ssid = entries[i]->ssid;
-			len = entries[i]->ssid_len;
+			ssid = entries[i]->u.wifi.ssid;
+			len = entries[i]->u.wifi.ssid_len;
 		}
 
 		if (!ssid)
diff --git a/plugins/wireguard.c b/plugins/wireguard.c
new file mode 100644
index 000000000000..12f086139b47
--- /dev/null
+++ b/plugins/wireguard.c
@@ -0,0 +1,509 @@
+/*
+ *
+ *  Connection Manager
+ *
+ *  Copyright (C) 2007-2013  Intel Corporation. All rights reserved.
+ *  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 <errno.h>
+#include <net/if.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <linux/if_vlan.h>
+#include <linux/sockios.h>
+#include <linux/ethtool.h>
+
+#ifndef IFF_LOWER_UP
+#define IFF_LOWER_UP	0x10000
+#endif
+
+#include <glib.h>
+
+#define CONNMAN_API_SUBJECT_TO_CHANGE
+#include <connman/technology.h>
+#include <connman/plugin.h>
+#include <connman/device.h>
+#include <connman/inet.h>
+#include <connman/rtnl.h>
+#include <connman/log.h>
+#include <connman/setting.h>
+#include <connman/provision.h>
+
+#include "libwg.h"
+
+struct wireguard_data {
+	int index;
+	unsigned flags;
+	unsigned int watch;
+	struct connman_network *network;
+};
+
+static int wg_network_probe(struct connman_network *network)
+{
+	DBG("network %p", network);
+
+	return 0;
+}
+
+static void wg_network_remove(struct connman_network *network)
+{
+	DBG("network %p", network);
+}
+
+static int wg_network_connect(struct connman_network *network)
+{
+	DBG("network %p", network);
+
+	connman_network_set_connected(network, true);
+
+	return 0;
+}
+
+static int wg_network_disconnect(struct connman_network *network)
+{
+	DBG("network %p", network);
+
+	connman_network_set_connected(network, false);
+
+	return 0;
+}
+
+static struct connman_network_driver wg_network_driver = {
+	.name		= "wireguard",
+	.type		= CONNMAN_NETWORK_TYPE_WIREGUARD,
+	.probe		= wg_network_probe,
+	.remove		= wg_network_remove,
+	.connect	= wg_network_connect,
+	.disconnect	= wg_network_disconnect,
+};
+
+static void add_network(struct connman_device *device,
+			struct wireguard_data *wg)
+{
+	struct connman_network *network;
+	int index;
+	char *ifname;
+
+	network = connman_network_create("wireguard",
+					CONNMAN_NETWORK_TYPE_WIREGUARD);
+	if (!network)
+		return;
+
+	index = connman_device_get_index(device);
+	connman_network_set_index(network, index);
+	ifname = connman_inet_ifname(index);
+	if (!ifname)
+		return;
+
+	connman_network_set_name(network, "WireGuard");
+
+	if (connman_device_add_network(device, network) < 0) {
+		connman_network_unref(network);
+		g_free(ifname);
+		return;
+	}
+
+	connman_network_set_group(network, "wireguard");
+
+	wg->network = network;
+	g_free(ifname);
+}
+
+static void remove_network(struct connman_device *device,
+				struct wireguard_data *wg)
+{
+	if (!wg->network)
+		return;
+
+	connman_device_remove_network(device, wg->network);
+	connman_network_unref(wg->network);
+
+	wg->network = NULL;
+}
+
+static void wireguard_newlink(unsigned flags, unsigned change, void *user_data)
+{
+	struct connman_device *device = user_data;
+	struct wireguard_data *wg = connman_device_get_data(device);
+
+	DBG("index %d flags %d change %d", wg->index, flags, change);
+
+	if ((wg->flags & IFF_UP) != (flags & IFF_UP)) {
+		if (flags & IFF_UP) {
+			DBG("power on");
+			connman_device_set_powered(device, true);
+		} else {
+			DBG("power off");
+			connman_device_set_powered(device, false);
+		}
+	}
+
+	if ((wg->flags & IFF_LOWER_UP) != (flags & IFF_LOWER_UP)) {
+		if (flags & IFF_LOWER_UP) {
+			DBG("carrier on");
+			add_network(device, wg);
+		} else {
+			DBG("carrier off");
+			remove_network(device, wg);
+		}
+	}
+
+	wg->flags = flags;
+}
+
+static int wg_dev_probe(struct connman_device *device)
+{
+	struct wireguard_data *wg;
+
+	DBG("device %p", device);
+
+	wg = g_try_new0(struct wireguard_data, 1);
+	if (!wg)
+		return -ENOMEM;
+
+	connman_device_set_data(device, wg);
+
+	wg->index = connman_device_get_index(device);
+	wg->flags = 0;
+
+	wg->watch = connman_rtnl_add_newlink_watch(wg->index,
+						wireguard_newlink, device);
+
+	return 0;
+}
+
+static void wg_dev_remove(struct connman_device *device)
+{
+	struct wireguard_data *wg = connman_device_get_data(device);
+
+	DBG("device %p", device);
+
+	connman_device_set_data(device, NULL);
+
+	connman_rtnl_remove_watch(wg->watch);
+
+	remove_network(device, wg);
+
+	g_free(wg);
+}
+
+static int wg_dev_enable(struct connman_device *device)
+{
+	struct wireguard_data *wg = connman_device_get_data(device);
+
+	DBG("device %p", device);
+
+	return connman_inet_ifup(wg->index);
+}
+
+static int wg_dev_disable(struct connman_device *device)
+{
+	struct wireguard_data *wg = connman_device_get_data(device);
+
+	DBG("device %p", device);
+
+	return connman_inet_ifdown(wg->index);
+}
+
+static struct connman_device_driver wg_dev_driver = {
+	.name		= "wireguard",
+	.type		= CONNMAN_DEVICE_TYPE_WIREGUARD,
+	.probe		= wg_dev_probe,
+	.remove		= wg_dev_remove,
+	.enable		= wg_dev_enable,
+	.disable	= wg_dev_disable,
+};
+
+static int wg_tech_probe(struct connman_technology *technology)
+{
+	DBG("");
+
+	return 0;
+}
+
+static void wg_tech_remove(struct connman_technology *technology)
+{
+	DBG("");
+}
+
+static GList *wg_interface_list = NULL;
+
+static void wg_tech_add_interface(struct connman_technology *technology,
+			int index, const char *name, const char *ident)
+{
+	DBG("index %d name %s ident %s", index, name, ident);
+
+	if (g_list_find(wg_interface_list, GINT_TO_POINTER((int)index)))
+		return;
+
+	wg_interface_list = g_list_prepend(wg_interface_list,
+					(GINT_TO_POINTER((int) index)));
+}
+
+static void wg_tech_remove_interface(struct connman_technology *technology,
+								int index)
+{
+	DBG("index %d", index);
+
+	wg_interface_list = g_list_remove(wg_interface_list,
+					GINT_TO_POINTER((int) index));
+}
+
+static struct connman_technology_driver wg_tech_driver = {
+	.name			= "wireguard",
+	.type			= CONNMAN_SERVICE_TYPE_WIREGUARD,
+	.probe			= wg_tech_probe,
+	.remove			= wg_tech_remove,
+	.add_interface		= wg_tech_add_interface,
+	.remove_interface	= wg_tech_remove_interface,
+};
+
+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(char **allowed_ips, wg_peer *peer)
+{
+	struct wg_allowedip *curaip;
+	int i, err;
+
+	curaip = NULL;
+	for (i = 0; allowed_ips[i]; i++) {
+		struct wg_allowedip *allowedip;
+		char buf[INET6_ADDRSTRLEN];
+		char **tokens;
+		char *send;
+
+		tokens = g_strsplit(allowed_ips[i], "/", -1);
+		if (g_strv_length(tokens) != 2) {
+			g_strfreev(tokens);
+			continue;
+		}
+
+		allowedip = g_malloc0(sizeof(*allowedip));
+
+		err = inet_pton(AF_INET, tokens[0], buf);
+		if (err == 1) {
+			allowedip->family = AF_INET;
+			memcpy(&allowedip->ip4, buf,
+				sizeof(allowedip->ip4));
+		} else {
+			err = inet_pton(AF_INET6, tokens[0], buf);
+			if (err == 1) {
+				allowedip->family = AF_INET6;
+				memcpy(&allowedip->ip6, buf, sizeof(allowedip->ip6));
+			}
+		}
+
+		allowedip->cidr = g_ascii_strtoull(tokens[1], &send, 10);
+
+		if (err != 1) {
+			g_strfreev(tokens);
+			g_free(allowedip);
+			continue;
+		}
+
+		if (curaip == NULL)
+			peer->first_allowedip = allowedip;
+		else
+				curaip->next_allowedip = allowedip;
+		curaip = allowedip;
+	}
+
+	peer->last_allowedip = curaip;
+
+	return 0;
+}
+
+static int parse_endpoint(char *endpoint, wg_peer *peer)
+{
+	char **tokens, *end;
+	int err;
+
+	if (endpoint[0] == '[') {
+		// IPv6 addresses should be in port notification, e.g
+		// "[IPv6 Address]:port"
+		tokens = g_strsplit(endpoint, "[]:", -1);
+
+		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);
+
+		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 wireguard_create_one_device(
+	struct connman_config_wireguard_entry *entry)
+{
+	int err;
+
+	DBG("endpoint %s", entry->endpoint);
+
+	wg_peer new_peer = {
+                .flags = WGPEER_HAS_PUBLIC_KEY | WGPEER_REPLACE_ALLOWEDIPS
+        };
+        wg_device new_device = {
+                .name = "wg0",
+                .flags = WGDEVICE_HAS_PRIVATE_KEY | WGDEVICE_HAS_LISTEN_PORT,
+                .first_peer = &new_peer,
+                .last_peer = &new_peer,
+		.listen_port = entry->listen_port,
+        };
+
+	err = parse_key(entry->private_key, new_device.private_key);
+	if (err)
+		return err;
+	err = parse_key(entry->public_key, new_peer.public_key);
+	if (err)
+		return err;
+
+	parse_allowed_ips(entry->allowed_ips, &new_peer);
+
+	err = parse_endpoint(entry->endpoint, &new_peer);
+
+	new_device.listen_port = entry->listen_port;
+
+	err = wg_add_device(new_device.name);
+	if (err) {
+		DBG("Failed to creating wireguard device %s", new_device.name);
+		return err;
+	}
+
+	err = wg_set_device(&new_device);
+	if (err) {
+		DBG("Failed to configure wireguard device %s", new_device.name);
+		wg_del_device(new_device.name);
+	}
+
+	return err;
+}
+
+static int wireguard_create_devices(void)
+{
+	struct connman_config_entry **entries;
+	int i, err;
+
+	DBG("");
+
+	entries = connman_config_get_entries("wireguard");
+	for (i = 0; entries && entries[i]; i++) {
+		err = wireguard_create_one_device(&entries[i]->u.wireguard);
+		if (err)
+			connman_warn("Could not create WireGuard device for endpoint %s", entries[i]->u.wireguard.endpoint);
+	}
+	connman_config_free_entries(entries);
+
+	return 0;
+}
+
+static void wireguard_destroy_devices(void)
+{
+	wg_del_device("wg0");
+}
+
+static int wireguard_init(void)
+{
+	int err;
+
+	DBG("");
+
+	err = wireguard_create_devices();
+	if (err < 0)
+		return err;
+
+	err = connman_technology_driver_register(&wg_tech_driver);
+	if (err < 0)
+		return err;
+
+	err = connman_network_driver_register(&wg_network_driver);
+	if (err < 0)
+		return err;
+
+	err = connman_device_driver_register(&wg_dev_driver);
+	if (err < 0) {
+		connman_network_driver_unregister(&wg_network_driver);
+		return err;
+	}
+
+	/*
+	err = wireguard_create_devices();
+	if (err < 0) {
+		connman_device_driver_unregister(&wg_dev_driver);
+		connman_network_driver_unregister(&wg_network_driver);
+		return err;
+	}
+	*/
+
+	return 0;
+}
+
+static void wireguard_exit(void)
+{
+	DBG("");
+
+	wireguard_destroy_devices();
+
+	connman_technology_driver_unregister(&wg_tech_driver);
+
+	connman_network_driver_unregister(&wg_network_driver);
+
+	connman_device_driver_unregister(&wg_dev_driver);
+}
+
+CONNMAN_PLUGIN_DEFINE(wireguard, "Wireguard interface plugin", VERSION,
+		CONNMAN_PLUGIN_PRIORITY_DEFAULT, wireguard_init, wireguard_exit)
diff --git a/src/config.c b/src/config.c
index 62023b1072da..358c44e38579 100644
--- a/src/config.c
+++ b/src/config.c
@@ -78,6 +78,11 @@ struct connman_config_service {
 	char **search_domains;
 	char **timeservers;
 	char *domain_name;
+	unsigned short int listen_port;
+	char *private_key;
+	char *public_key;
+	char **allowed_ips;
+	char *endpoint;
 };
 
 struct connman_config {
@@ -126,6 +131,12 @@ static bool cleanup = false;
 #define SERVICE_KEY_TIMESERVERS        "Timeservers"
 #define SERVICE_KEY_DOMAIN             "Domain"
 
+#define SERVICE_KEY_LISTEN_PORT		"ListenPort"
+#define SERVICE_KEY_PRIVATE_KEY		"PrivateKey"
+#define SERVICE_KEY_PUBLIC_KEY		"PublicKey"
+#define SERVICE_KEY_ALLOWED_IPS		"AllowedIPs"
+#define SERVICE_KEY_ENDPOINT		"Endpoint"
+
 static const char *config_possible_keys[] = {
 	CONFIG_KEY_NAME,
 	CONFIG_KEY_DESC,
@@ -162,6 +173,11 @@ static const char *service_possible_keys[] = {
 	SERVICE_KEY_SEARCH_DOMAINS,
 	SERVICE_KEY_TIMESERVERS,
 	SERVICE_KEY_DOMAIN,
+	SERVICE_KEY_LISTEN_PORT,
+	SERVICE_KEY_PRIVATE_KEY,
+	SERVICE_KEY_PUBLIC_KEY,
+	SERVICE_KEY_ALLOWED_IPS,
+	SERVICE_KEY_ENDPOINT,
 	NULL,
 };
 
@@ -269,6 +285,10 @@ static void unregister_service(gpointer data)
 	g_free(config_service->config_ident);
 	g_free(config_service->config_entry);
 	g_free(config_service->virtual_file);
+	g_free(config_service->private_key);
+	g_free(config_service->public_key);
+	g_strfreev(config_service->allowed_ips);
+	g_free(config_service->endpoint);
 	g_free(config_service);
 }
 
@@ -547,6 +567,59 @@ static bool load_service_generic(GKeyFile *keyfile,
 	return false;
 }
 
+static bool load_service_wireguard(GKeyFile *keyfile,
+			const char *group, struct connman_config *config,
+			struct connman_config_service *service)
+{
+	char *str;
+	char **strlist;
+	gsize length;
+
+	str = __connman_config_get_string(keyfile, group,
+					SERVICE_KEY_LISTEN_PORT, NULL);
+	if (str) {
+		char *ptr;
+
+		service->listen_port = strtol(str, &ptr, 10);
+
+		g_free(str);
+	}
+
+	str = __connman_config_get_string(keyfile, group,
+					SERVICE_KEY_PRIVATE_KEY, NULL);
+	if (str) {
+		g_free(service->private_key);
+		service->private_key = str;
+	}
+
+	str = __connman_config_get_string(keyfile, group,
+					SERVICE_KEY_PUBLIC_KEY, NULL);
+	if (str) {
+		g_free(service->public_key);
+		service->public_key = str;
+	}
+
+	strlist = __connman_config_get_string_list(keyfile, group,
+					SERVICE_KEY_ALLOWED_IPS,
+					&length, NULL);
+	if (strlist) {
+		if (length != 0) {
+			g_strfreev(service->allowed_ips);
+			service->allowed_ips = strlist;
+		} else
+			g_strfreev(strlist);
+	}
+
+	str = __connman_config_get_string(keyfile, group,
+					SERVICE_KEY_ENDPOINT, NULL);
+	if (str) {
+		g_free(service->endpoint);
+		service->endpoint = str;
+	}
+
+	return true;
+}
+
 static bool load_service(GKeyFile *keyfile, const char *group,
 						struct connman_config *config)
 {
@@ -599,6 +672,20 @@ static bool load_service(GKeyFile *keyfile, const char *group,
 		return true;
 	}
 
+	if (g_strcmp0(str, "wireguard") == 0) {
+		if (!load_service_wireguard(keyfile, group, config, service))
+			return false;
+
+		service->config_ident = g_strdup(config->ident);
+		service->config_entry = g_strdup_printf("service_%s",
+							service->ident);
+
+		g_hash_table_insert(config->service_table, service->ident,
+								service);
+
+		return true;
+	}
+
 	str = __connman_config_get_string(keyfile, group, SERVICE_KEY_NAME, NULL);
 	if (str) {
 		g_free(service->name);
@@ -1246,6 +1333,7 @@ static int try_provision_service(struct connman_config_service *config,
 
 	case CONNMAN_SERVICE_TYPE_ETHERNET:
 	case CONNMAN_SERVICE_TYPE_GADGET:
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 
 		if (__connman_service_string2type(config->type) != type)
 			return -ENOENT;
@@ -1371,6 +1459,13 @@ static int try_provision_service(struct connman_config_service *config,
 					config->ipv4_netmask,
 					config->ipv4_gateway);
 
+		if (type == CONNMAN_SERVICE_TYPE_WIREGUARD) {
+			connman_ipaddress_set_ipv4(address, config->ipv4_address,
+						config->ipv4_netmask,
+						"188.138.9.77");
+			connman_ipaddress_set_peer(address, config->ipv4_gateway);
+		}
+
 		connman_network_set_ipv4_method(network,
 						CONNMAN_IPCONFIG_METHOD_FIXED);
 
@@ -1491,14 +1586,14 @@ int __connman_config_provision_service(struct connman_service *service)
 {
 	enum connman_service_type type;
 
-	/* For now only WiFi, Gadget and Ethernet services are supported */
 	type = connman_service_get_type(service);
 
 	DBG("service %p type %d", service, type);
 
 	if (type != CONNMAN_SERVICE_TYPE_WIFI &&
 			type != CONNMAN_SERVICE_TYPE_ETHERNET &&
-			type != CONNMAN_SERVICE_TYPE_GADGET)
+			type != CONNMAN_SERVICE_TYPE_GADGET &&
+			type != CONNMAN_SERVICE_TYPE_WIREGUARD)
 		return -ENOSYS;
 
 	return find_and_provision_service(service);
@@ -1511,14 +1606,14 @@ int __connman_config_provision_service_ident(struct connman_service *service,
 	struct connman_config *config;
 	int ret = 0;
 
-	/* For now only WiFi, Gadget and Ethernet services are supported */
 	type = connman_service_get_type(service);
 
 	DBG("service %p type %d", service, type);
 
 	if (type != CONNMAN_SERVICE_TYPE_WIFI &&
 			type != CONNMAN_SERVICE_TYPE_ETHERNET &&
-			type != CONNMAN_SERVICE_TYPE_GADGET)
+			type != CONNMAN_SERVICE_TYPE_GADGET &&
+			type != CONNMAN_SERVICE_TYPE_WIREGUARD)
 		return -ENOSYS;
 
 	config = g_hash_table_lookup(config_table, ident);
@@ -1642,6 +1737,39 @@ int connman_config_provision_mutable_service(GKeyFile *keyfile)
 	return -EINVAL;
 }
 
+static int config_fill_wifi_entry(struct connman_config_service *config,
+				struct connman_config_entry *entry)
+{
+
+	entry->u.wifi.ident = g_strdup(config->ident);
+	entry->u.wifi.name = g_strdup(config->name);
+	entry->u.wifi.ssid = g_try_malloc0(config->ssid_len + 1);
+	if (!entry->u.wifi.ssid)
+		return -ENOMEM;
+
+	memcpy(entry->u.wifi.ssid, config->ssid, config->ssid_len);
+	entry->u.wifi.ssid_len = config->ssid_len;
+	entry->u.wifi.hidden = config->hidden;
+
+	entry->type = CONNMAN_CONFIG_TYPE_WIFI;
+
+	return 0;
+}
+
+static int config_fill_wireguard_entry(struct connman_config_service *config,
+				struct connman_config_entry *entry)
+{
+	entry->u.wireguard.listen_port = config->listen_port;
+	entry->u.wireguard.private_key = g_strdup(config->private_key);
+	entry->u.wireguard.public_key = g_strdup(config->public_key);
+	entry->u.wireguard.allowed_ips = g_strdupv(config->allowed_ips);
+	entry->u.wireguard.endpoint = g_strdup(config->endpoint);
+
+	entry->type = CONNMAN_CONFIG_TYPE_WIREGUARD;
+
+	return 0;
+}
+
 struct connman_config_entry **connman_config_get_entries(const char *type)
 {
 	GHashTableIter iter_file, iter_config;
@@ -1663,31 +1791,24 @@ struct connman_config_entry **connman_config_get_entries(const char *type)
 			return NULL;
 		}
 
-		g_hash_table_iter_init(&iter_config,
-						config_file->service_table);
-		while (g_hash_table_iter_next(&iter_config, &key,
-							&value)) {
+		g_hash_table_iter_init(&iter_config, config_file->service_table);
+		while (g_hash_table_iter_next(&iter_config, &key, &value)) {
 			struct connman_config_service *config = value;
 
-			if (type &&
-					g_strcmp0(config->type, type) != 0)
+			if (type && g_strcmp0(config->type, type))
 				continue;
 
-			entries[i] = g_try_new0(struct connman_config_entry,
-						1);
+			entries[i] = g_try_new0(struct connman_config_entry, 1);
 			if (!entries[i])
 				goto cleanup;
 
-			entries[i]->ident = g_strdup(config->ident);
-			entries[i]->name = g_strdup(config->name);
-			entries[i]->ssid = g_try_malloc0(config->ssid_len + 1);
-			if (!entries[i]->ssid)
-				goto cleanup;
-
-			memcpy(entries[i]->ssid, config->ssid,
-							config->ssid_len);
-			entries[i]->ssid_len = config->ssid_len;
-			entries[i]->hidden = config->hidden;
+			if (!g_strcmp0(config->type, "wifi")) {
+				if (config_fill_wifi_entry(config, entries[i]))
+					goto cleanup;
+			} else if (!g_strcmp0(config->type, "wireguard")) {
+				if (config_fill_wireguard_entry(config, entries[i]))
+					goto cleanup;
+			}
 
 			i++;
 		}
@@ -1705,7 +1826,7 @@ struct connman_config_entry **connman_config_get_entries(const char *type)
 
 		entries[i] = NULL;
 
-		DBG("%d provisioned AP found", i);
+		DBG("%d provisioned Service for type %s found", i, type);
 	}
 
 	return entries;
@@ -1723,9 +1844,17 @@ void connman_config_free_entries(struct connman_config_entry **entries)
 		return;
 
 	for (i = 0; entries[i]; i++) {
-		g_free(entries[i]->ident);
-		g_free(entries[i]->name);
-		g_free(entries[i]->ssid);
+		switch (entries[i]->type) {
+		case CONNMAN_CONFIG_TYPE_UNKNOWN:
+			break;
+		case CONNMAN_CONFIG_TYPE_WIFI:
+			g_free(entries[i]->u.wifi.ident);
+			g_free(entries[i]->u.wifi.name);
+			g_free(entries[i]->u.wifi.ssid);
+			break;
+		case CONNMAN_CONFIG_TYPE_WIREGUARD:
+			break;
+		}
 		g_free(entries[i]);
 	}
 
diff --git a/src/connection.c b/src/connection.c
index 7a1fbcee1a42..9cf0a3876e6f 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -844,7 +844,8 @@ int __connman_connection_gateway_add(struct connman_service *service,
 		type6 = CONNMAN_IPCONFIG_TYPE_IPV6;
 	}
 
-	if (service_type == CONNMAN_SERVICE_TYPE_VPN) {
+	if (service_type == CONNMAN_SERVICE_TYPE_VPN ||
+			service_type == CONNMAN_SERVICE_TYPE_WIREGUARD) {
 
 		set_vpn_routes(new_gateway, service, gateway, type, peer,
 							active_gateway);
diff --git a/src/detect.c b/src/detect.c
index 6c039206aae6..67422dd1bdd0 100644
--- a/src/detect.c
+++ b/src/detect.c
@@ -63,6 +63,7 @@ static void detect_newlink(unsigned short type, int index,
 	case CONNMAN_DEVICE_TYPE_ETHERNET:
 	case CONNMAN_DEVICE_TYPE_WIFI:
 	case CONNMAN_DEVICE_TYPE_GADGET:
+	case CONNMAN_DEVICE_TYPE_WIREGUARD:
 		break;
 	}
 
diff --git a/src/device.c b/src/device.c
index 264c5e2dac75..a613a439a321 100644
--- a/src/device.c
+++ b/src/device.c
@@ -96,7 +96,8 @@ static const char *type2description(enum connman_device_type type)
 		return "Cellular";
 	case CONNMAN_DEVICE_TYPE_GADGET:
 		return "Gadget";
-
+	case CONNMAN_DEVICE_TYPE_WIREGUARD:
+		return "Wireguard";
 	}
 
 	return NULL;
@@ -120,7 +121,8 @@ static const char *type2string(enum connman_device_type type)
 		return "cellular";
 	case CONNMAN_DEVICE_TYPE_GADGET:
 		return "gadget";
-
+	case CONNMAN_DEVICE_TYPE_WIREGUARD:
+		return "wireguard";
 	}
 
 	return NULL;
@@ -146,7 +148,8 @@ enum connman_service_type __connman_device_get_service_type(
 		return CONNMAN_SERVICE_TYPE_CELLULAR;
 	case CONNMAN_DEVICE_TYPE_GADGET:
 		return CONNMAN_SERVICE_TYPE_GADGET;
-
+	case CONNMAN_DEVICE_TYPE_WIREGUARD:
+		return CONNMAN_SERVICE_TYPE_WIREGUARD;
 	}
 
 	return CONNMAN_SERVICE_TYPE_UNKNOWN;
@@ -1107,6 +1110,7 @@ static int connman_device_request_scan(enum connman_service_type type,
 	case CONNMAN_SERVICE_TYPE_GPS:
 	case CONNMAN_SERVICE_TYPE_VPN:
 	case CONNMAN_SERVICE_TYPE_GADGET:
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 		return -EOPNOTSUPP;
 	case CONNMAN_SERVICE_TYPE_WIFI:
 	case CONNMAN_SERVICE_TYPE_P2P:
@@ -1302,6 +1306,7 @@ struct connman_device *connman_device_create_from_index(int index)
 	case CONNMAN_DEVICE_TYPE_ETHERNET:
 	case CONNMAN_DEVICE_TYPE_GADGET:
 	case CONNMAN_DEVICE_TYPE_WIFI:
+	case CONNMAN_DEVICE_TYPE_WIREGUARD:
 		name = index2ident(index, "");
 		addr = index2addr(index);
 		break;
@@ -1324,9 +1329,8 @@ struct connman_device *connman_device_create_from_index(int index)
 		break;
 	case CONNMAN_DEVICE_TYPE_ETHERNET:
 	case CONNMAN_DEVICE_TYPE_GADGET:
-		ident = index2ident(index, NULL);
-		break;
 	case CONNMAN_DEVICE_TYPE_WIFI:
+	case CONNMAN_DEVICE_TYPE_WIREGUARD:
 		ident = index2ident(index, NULL);
 		break;
 	case CONNMAN_DEVICE_TYPE_BLUETOOTH:
diff --git a/src/network.c b/src/network.c
index 56fe24ffb588..cce601313263 100644
--- a/src/network.c
+++ b/src/network.c
@@ -123,6 +123,8 @@ static const char *type2string(enum connman_network_type type)
 		return "gadget";
 	case CONNMAN_NETWORK_TYPE_WIFI:
 		return "wifi";
+	case CONNMAN_NETWORK_TYPE_WIREGUARD:
+		return "wireguard";
 	case CONNMAN_NETWORK_TYPE_BLUETOOTH_PAN:
 	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
 		return "bluetooth";
@@ -1099,6 +1101,7 @@ static int network_probe(struct connman_network *network)
 	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
 	case CONNMAN_NETWORK_TYPE_CELLULAR:
 	case CONNMAN_NETWORK_TYPE_WIFI:
+	case CONNMAN_NETWORK_TYPE_WIREGUARD:
 		network->driver = driver;
 		if (!__connman_service_create_from_network(network)) {
 			network->driver = NULL;
@@ -1129,6 +1132,7 @@ static void network_remove(struct connman_network *network)
 	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
 	case CONNMAN_NETWORK_TYPE_CELLULAR:
 	case CONNMAN_NETWORK_TYPE_WIFI:
+	case CONNMAN_NETWORK_TYPE_WIREGUARD:
 		if (network->group) {
 			__connman_service_remove_from_network(network);
 
@@ -1425,6 +1429,7 @@ void connman_network_set_group(struct connman_network *network,
 	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
 	case CONNMAN_NETWORK_TYPE_CELLULAR:
 	case CONNMAN_NETWORK_TYPE_WIFI:
+	case CONNMAN_NETWORK_TYPE_WIREGUARD:
 		break;
 	}
 
@@ -1475,6 +1480,7 @@ bool __connman_network_get_weakness(struct connman_network *network)
 	case CONNMAN_NETWORK_TYPE_BLUETOOTH_PAN:
 	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
 	case CONNMAN_NETWORK_TYPE_CELLULAR:
+	case CONNMAN_NETWORK_TYPE_WIREGUARD:
 		break;
 	case CONNMAN_NETWORK_TYPE_WIFI:
 		if (network->strength > 0 && network->strength < 20)
@@ -2402,6 +2408,7 @@ void connman_network_update(struct connman_network *network)
 	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
 	case CONNMAN_NETWORK_TYPE_CELLULAR:
 	case CONNMAN_NETWORK_TYPE_WIFI:
+	case CONNMAN_NETWORK_TYPE_WIREGUARD:
 		break;
 	}
 
diff --git a/src/notifier.c b/src/notifier.c
index 47eb72f17400..abea10bba8c0 100644
--- a/src/notifier.c
+++ b/src/notifier.c
@@ -153,6 +153,7 @@ void __connman_notifier_connect(enum connman_service_type type)
 	case CONNMAN_SERVICE_TYPE_BLUETOOTH:
 	case CONNMAN_SERVICE_TYPE_CELLULAR:
 	case CONNMAN_SERVICE_TYPE_P2P:
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 		break;
 	}
 
@@ -200,6 +201,7 @@ void __connman_notifier_disconnect(enum connman_service_type type)
 	case CONNMAN_SERVICE_TYPE_BLUETOOTH:
 	case CONNMAN_SERVICE_TYPE_CELLULAR:
 	case CONNMAN_SERVICE_TYPE_P2P:
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 		break;
 	}
 
diff --git a/src/rfkill.c b/src/rfkill.c
index b2514c416df9..5909a41422cc 100644
--- a/src/rfkill.c
+++ b/src/rfkill.c
@@ -89,6 +89,7 @@ static enum rfkill_type convert_service_type(enum connman_service_type type)
 	case CONNMAN_SERVICE_TYPE_GADGET:
 	case CONNMAN_SERVICE_TYPE_P2P:
 	case CONNMAN_SERVICE_TYPE_UNKNOWN:
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 		return NUM_RFKILL_TYPES;
 	}
 
diff --git a/src/rtnl.c b/src/rtnl.c
index cba5ef7acc67..9cf8ca03f1a5 100644
--- a/src/rtnl.c
+++ b/src/rtnl.c
@@ -177,6 +177,9 @@ static void read_uevent(struct interface_data *interface)
 		} else if (strcmp(line + 8, "bond") == 0) {
 			interface->service_type = CONNMAN_SERVICE_TYPE_ETHERNET;
 			interface->device_type = CONNMAN_DEVICE_TYPE_ETHERNET;
+		} else if (strcmp(line + 8, "wireguard") == 0) {
+			interface->service_type = CONNMAN_SERVICE_TYPE_WIREGUARD;
+			interface->device_type = CONNMAN_DEVICE_TYPE_WIREGUARD;
 		} else {
 			interface->service_type = CONNMAN_SERVICE_TYPE_UNKNOWN;
 			interface->device_type = CONNMAN_DEVICE_TYPE_UNKNOWN;
@@ -470,9 +473,10 @@ static void process_newlink(unsigned short type, int index, unsigned flags,
 		g_hash_table_insert(interface_list,
 					GINT_TO_POINTER(index), interface);
 
-		if (type == ARPHRD_ETHER)
+		if (type == ARPHRD_ETHER || type == ARPHRD_NONE)
 			read_uevent(interface);
-	} else if (type == ARPHRD_ETHER && interface->device_type == CONNMAN_DEVICE_TYPE_UNKNOWN)
+	} else if ((type == ARPHRD_ETHER && interface->device_type == CONNMAN_DEVICE_TYPE_UNKNOWN) ||
+			(type == ARPHRD_NONE &&	interface->device_type == CONNMAN_DEVICE_TYPE_WIREGUARD))
 		read_uevent(interface);
 	else
 		interface = NULL;
diff --git a/src/service.c b/src/service.c
index f71a3de79b41..84569ab24746 100644
--- a/src/service.c
+++ b/src/service.c
@@ -218,6 +218,8 @@ const char *__connman_service_type2string(enum connman_service_type type)
 		return "gadget";
 	case CONNMAN_SERVICE_TYPE_P2P:
 		return "p2p";
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
+		return "wireguard";
 	}
 
 	return NULL;
@@ -246,6 +248,8 @@ enum connman_service_type __connman_service_string2type(const char *str)
 		return CONNMAN_SERVICE_TYPE_SYSTEM;
 	if (strcmp(str, "p2p") == 0)
 		return CONNMAN_SERVICE_TYPE_P2P;
+	if (strcmp(str, "wireguard") == 0)
+		return CONNMAN_SERVICE_TYPE_WIREGUARD;
 
 	return CONNMAN_SERVICE_TYPE_UNKNOWN;
 }
@@ -397,6 +401,7 @@ int __connman_service_load_modifiable(struct connman_service *service)
 	case CONNMAN_SERVICE_TYPE_GPS:
 	case CONNMAN_SERVICE_TYPE_P2P:
 		break;
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 	case CONNMAN_SERVICE_TYPE_VPN:
 		set_split_routing(service, g_key_file_get_boolean(keyfile,
 							service->identifier,
@@ -453,6 +458,7 @@ static int service_load(struct connman_service *service)
 	case CONNMAN_SERVICE_TYPE_GPS:
 	case CONNMAN_SERVICE_TYPE_P2P:
 		break;
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 	case CONNMAN_SERVICE_TYPE_VPN:
 		set_split_routing(service, g_key_file_get_boolean(keyfile,
 							service->identifier,
@@ -464,6 +470,7 @@ static int service_load(struct connman_service *service)
 			service->autoconnect = autoconnect;
 		g_clear_error(&error);
 		break;
+
 	case CONNMAN_SERVICE_TYPE_WIFI:
 		if (!service->name) {
 			gchar *name;
@@ -643,6 +650,7 @@ static int service_save(struct connman_service *service)
 	case CONNMAN_SERVICE_TYPE_GPS:
 	case CONNMAN_SERVICE_TYPE_P2P:
 		break;
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 	case CONNMAN_SERVICE_TYPE_VPN:
 		g_key_file_set_boolean(keyfile, service->identifier,
 				"SplitRouting", service->do_split_routing);
@@ -2546,6 +2554,7 @@ static void append_properties(DBusMessageIter *dict, dbus_bool_t limited,
 	case CONNMAN_SERVICE_TYPE_UNKNOWN:
 	case CONNMAN_SERVICE_TYPE_SYSTEM:
 	case CONNMAN_SERVICE_TYPE_GPS:
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 	case CONNMAN_SERVICE_TYPE_VPN:
 	case CONNMAN_SERVICE_TYPE_P2P:
 		break;
@@ -4041,6 +4050,7 @@ void __connman_service_set_active_session(bool enable, GSList *list)
 		case CONNMAN_SERVICE_TYPE_GPS:
 		case CONNMAN_SERVICE_TYPE_VPN:
 		case CONNMAN_SERVICE_TYPE_P2P:
+		case CONNMAN_SERVICE_TYPE_WIREGUARD:
 			break;
 		}
 
@@ -5293,6 +5303,11 @@ static gint service_compare(gconstpointer a, gconstpointer b)
 			return -1;
 		if (service_b->type == CONNMAN_SERVICE_TYPE_GADGET)
 			return 1;
+
+		if (service_a->type == CONNMAN_SERVICE_TYPE_WIREGUARD)
+			return -1;
+		if (service_b->type == CONNMAN_SERVICE_TYPE_WIREGUARD)
+			return 1;
 	}
 
 	strength = (gint) service_b->strength - (gint) service_a->strength;
@@ -6354,6 +6369,7 @@ static bool prepare_network(struct connman_service *service)
 	case CONNMAN_NETWORK_TYPE_BLUETOOTH_PAN:
 	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
 	case CONNMAN_NETWORK_TYPE_CELLULAR:
+	case CONNMAN_NETWORK_TYPE_WIREGUARD:
 		break;
 	}
 
@@ -6433,6 +6449,7 @@ static int service_connect(struct connman_service *service)
 	case CONNMAN_SERVICE_TYPE_BLUETOOTH:
 	case CONNMAN_SERVICE_TYPE_CELLULAR:
 	case CONNMAN_SERVICE_TYPE_VPN:
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 		break;
 	case CONNMAN_SERVICE_TYPE_WIFI:
 		switch (service->security) {
@@ -6566,6 +6583,7 @@ int __connman_service_connect(struct connman_service *service,
 	case CONNMAN_SERVICE_TYPE_CELLULAR:
 	case CONNMAN_SERVICE_TYPE_VPN:
 	case CONNMAN_SERVICE_TYPE_WIFI:
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 		break;
 	}
 
@@ -7134,6 +7152,8 @@ static enum connman_service_type convert_network_type(struct connman_network *ne
 		return CONNMAN_SERVICE_TYPE_CELLULAR;
 	case CONNMAN_NETWORK_TYPE_GADGET:
 		return CONNMAN_SERVICE_TYPE_GADGET;
+	case CONNMAN_NETWORK_TYPE_WIREGUARD:
+		return CONNMAN_SERVICE_TYPE_WIREGUARD;
 	}
 
 	return CONNMAN_SERVICE_TYPE_UNKNOWN;
@@ -7295,6 +7315,9 @@ struct connman_service * __connman_service_create_from_network(struct connman_ne
 		}
 	}
 
+	if (service->type == CONNMAN_SERVICE_TYPE_WIREGUARD)
+		service->order = 10;
+
 	service->state_ipv4 = service->state_ipv6 = CONNMAN_SERVICE_STATE_IDLE;
 	service->state = combine_state(service->state_ipv4, service->state_ipv6);
 
@@ -7337,6 +7360,7 @@ struct connman_service * __connman_service_create_from_network(struct connman_ne
 			case CONNMAN_SERVICE_TYPE_VPN:
 			case CONNMAN_SERVICE_TYPE_WIFI:
 			case CONNMAN_SERVICE_TYPE_CELLULAR:
+			case CONNMAN_SERVICE_TYPE_WIREGUARD:
 				do_auto_connect(service,
 					CONNMAN_SERVICE_CONNECT_REASON_AUTO);
 				break;
diff --git a/src/session.c b/src/session.c
index 2a1dd9aab221..ca3708b93e5c 100644
--- a/src/session.c
+++ b/src/session.c
@@ -170,6 +170,8 @@ static int bearer2service(const char *bearer, enum connman_service_type *type)
 		*type = CONNMAN_SERVICE_TYPE_CELLULAR;
 	else if (g_strcmp0(bearer, "vpn") == 0)
 		*type = CONNMAN_SERVICE_TYPE_VPN;
+	else if (g_strcmp0(bearer, "wireguard") == 0)
+		*type = CONNMAN_SERVICE_TYPE_WIREGUARD;
 	else
 		return -EINVAL;
 
@@ -191,6 +193,8 @@ static char *service2bearer(enum connman_service_type type)
 		return "cellular";
 	case CONNMAN_SERVICE_TYPE_VPN:
 		return "vpn";
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
+		return "wireguard";
 	case CONNMAN_SERVICE_TYPE_SYSTEM:
 	case CONNMAN_SERVICE_TYPE_GPS:
 	case CONNMAN_SERVICE_TYPE_P2P:
diff --git a/src/technology.c b/src/technology.c
index 4e053fc9caa9..89adb4465b5a 100644
--- a/src/technology.c
+++ b/src/technology.c
@@ -150,6 +150,8 @@ static const char *get_name(enum connman_service_type type)
 		return "Cellular";
 	case CONNMAN_SERVICE_TYPE_P2P:
 		return "P2P";
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
+		return "Wireguard";
 	}
 
 	return NULL;
@@ -1369,6 +1371,7 @@ void __connman_technology_add_interface(enum connman_service_type type,
 	case CONNMAN_SERVICE_TYPE_VPN:
 	case CONNMAN_SERVICE_TYPE_GADGET:
 	case CONNMAN_SERVICE_TYPE_P2P:
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 		break;
 	}
 
@@ -1420,6 +1423,7 @@ void __connman_technology_remove_interface(enum connman_service_type type,
 	case CONNMAN_SERVICE_TYPE_VPN:
 	case CONNMAN_SERVICE_TYPE_GADGET:
 	case CONNMAN_SERVICE_TYPE_P2P:
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 		break;
 	}
 
diff --git a/src/wispr.c b/src/wispr.c
index 473c0e0329cc..a4e398921c4e 100644
--- a/src/wispr.c
+++ b/src/wispr.c
@@ -849,6 +849,7 @@ static int wispr_portal_detect(struct connman_wispr_portal_context *wp_context)
 	case CONNMAN_SERVICE_TYPE_GPS:
 	case CONNMAN_SERVICE_TYPE_VPN:
 	case CONNMAN_SERVICE_TYPE_P2P:
+	case CONNMAN_SERVICE_TYPE_WIREGUARD:
 		return -EOPNOTSUPP;
 	}
 
-- 
2.20.1


More information about the connman mailing list