summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2019-02-22 13:54:48 +0100
committerThomas Haller <thaller@redhat.com>2019-02-22 13:54:48 +0100
commit4aaa0ed482f4354b5819883a1627d9968e642f1c (patch)
tree14e912ca5d5c87c971ab963c255e3774cc240402
parent1d47643d954d69aa6be86a0f308111da9f926389 (diff)
parent6d5aa85181e2904f7776f993738774c47a43849d (diff)
downloadNetworkManager-4aaa0ed482f4354b5819883a1627d9968e642f1c.tar.gz
wireguard: merge branch 'th/wireguard-pt3'
https://github.com/NetworkManager/NetworkManager/pull/295
-rw-r--r--Makefile.am2
-rw-r--r--Makefile.examples1
-rw-r--r--NEWS3
-rw-r--r--clients/cli/connections.c1
-rw-r--r--clients/common/nm-meta-setting-desc.c29
-rw-r--r--clients/common/nm-secret-agent-simple.c124
-rw-r--r--clients/common/nm-secret-agent-simple.h1
-rw-r--r--clients/common/settings-docs.h.in4
-rw-r--r--docs/libnm/libnm-docs.xml1
-rwxr-xr-xexamples/python/gi/nm-wg-set423
-rw-r--r--libnm-core/meson.build2
-rw-r--r--libnm-core/nm-connection.c27
-rw-r--r--libnm-core/nm-core-enum-types.c.template1
-rw-r--r--libnm-core/nm-core-internal.h23
-rw-r--r--libnm-core/nm-core-types.h1
-rw-r--r--libnm-core/nm-keyfile-utils.h3
-rw-r--r--libnm-core/nm-keyfile.c228
-rw-r--r--libnm-core/nm-setting-wireguard.c2356
-rw-r--r--libnm-core/nm-setting-wireguard.h201
-rw-r--r--libnm-core/nm-setting.c2
-rw-r--r--libnm-core/nm-utils.c75
-rw-r--r--libnm-core/tests/test-setting.c425
-rw-r--r--libnm/NetworkManager.h1
-rw-r--r--libnm/libnm.ver37
-rw-r--r--libnm/nm-autoptr.h1
-rw-r--r--po/POTFILES.in1
-rw-r--r--shared/nm-meta-setting.c7
-rw-r--r--shared/nm-meta-setting.h1
-rw-r--r--src/devices/nm-device-wireguard.c1321
-rw-r--r--src/devices/nm-device.c3
30 files changed, 5274 insertions, 31 deletions
diff --git a/Makefile.am b/Makefile.am
index bbbb37de87..005dbace18 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -670,6 +670,7 @@ libnm_core_lib_h_pub_real = \
libnm-core/nm-setting-wifi-p2p.h \
libnm-core/nm-setting-wimax.h \
libnm-core/nm-setting-wired.h \
+ libnm-core/nm-setting-wireguard.h \
libnm-core/nm-setting-wireless-security.h \
libnm-core/nm-setting-wireless.h \
libnm-core/nm-setting-wpan.h \
@@ -739,6 +740,7 @@ libnm_core_lib_c_settings_real = \
libnm-core/nm-setting-wifi-p2p.c \
libnm-core/nm-setting-wimax.c \
libnm-core/nm-setting-wired.c \
+ libnm-core/nm-setting-wireguard.c \
libnm-core/nm-setting-wireless-security.c \
libnm-core/nm-setting-wireless.c \
libnm-core/nm-setting-wpan.c
diff --git a/Makefile.examples b/Makefile.examples
index d49db6ce86..92923a8db6 100644
--- a/Makefile.examples
+++ b/Makefile.examples
@@ -177,6 +177,7 @@ EXTRA_DIST += \
examples/python/gi/get_ips.py \
examples/python/gi/list-connections.py \
examples/python/gi/nm-connection-update-stable-id.py \
+ examples/python/gi/nm-wg-set \
examples/python/gi/setting-user-data.py \
examples/python/gi/show-wifi-networks.py \
examples/python/gi/update-ip4-method.py \
diff --git a/NEWS b/NEWS
index 5e6e2473be..e140a4ad60 100644
--- a/NEWS
+++ b/NEWS
@@ -14,6 +14,9 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE!
* Use a new type of secret-keys that combines the secret value with /etc/machine-id.
This way when cloning a VM it suffices to change machine-id to generate different
addresses.
+* Add support for WireGuard VPN tunnels to NetworkManager. D-Bus API and libnm
+ support all options. nmcli supports creating and managing WireGuard profiles,
+ with the exception of configuring and showing peers.
The following changes were backported to 1.14.x releases between 1.14.0
and 1.14.2 are also present in NetworkManager-1.14:
diff --git a/clients/cli/connections.c b/clients/cli/connections.c
index ff7d020303..63cb0bb90c 100644
--- a/clients/cli/connections.c
+++ b/clients/cli/connections.c
@@ -822,6 +822,7 @@ const NmcMetaGenericInfo *const metagen_con_active_vpn[_NMC_GENERIC_INFO_TYPE_CO
NM_SETTING_VXLAN_SETTING_NAME"," \
NM_SETTING_WPAN_SETTING_NAME","\
NM_SETTING_6LOWPAN_SETTING_NAME","\
+ NM_SETTING_WIREGUARD_SETTING_NAME","\
NM_SETTING_PROXY_SETTING_NAME"," \
NM_SETTING_TC_CONFIG_SETTING_NAME"," \
NM_SETTING_SRIOV_SETTING_NAME"," \
diff --git a/clients/common/nm-meta-setting-desc.c b/clients/common/nm-meta-setting-desc.c
index e33070dc06..03be32f645 100644
--- a/clients/common/nm-meta-setting-desc.c
+++ b/clients/common/nm-meta-setting-desc.c
@@ -7505,6 +7505,28 @@ static const NMMetaPropertyInfo *const property_infos_WIRED[] = {
};
#undef _CURRENT_NM_META_SETTING_TYPE
+#define _CURRENT_NM_META_SETTING_TYPE NM_META_SETTING_TYPE_WIREGUARD
+static const NMMetaPropertyInfo *const property_infos_WIREGUARD[] = {
+ PROPERTY_INFO_WITH_DESC (NM_SETTING_WIREGUARD_PRIVATE_KEY,
+ .is_secret = TRUE,
+ .property_type = &_pt_gobject_string,
+ ),
+ PROPERTY_INFO_WITH_DESC (NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS,
+ .property_type = &_pt_gobject_secret_flags,
+ ),
+ PROPERTY_INFO_WITH_DESC (NM_SETTING_WIREGUARD_LISTEN_PORT,
+ .property_type = &_pt_gobject_int,
+ ),
+ PROPERTY_INFO_WITH_DESC (NM_SETTING_WIREGUARD_FWMARK,
+ .property_type = &_pt_gobject_int,
+ .property_typ_data = DEFINE_PROPERTY_TYP_DATA_SUBTYPE (gobject_int, \
+ .base = 16,
+ ),
+ ),
+ NULL
+};
+
+#undef _CURRENT_NM_META_SETTING_TYPE
#define _CURRENT_NM_META_SETTING_TYPE NM_META_SETTING_TYPE_WIRELESS
static const NMMetaPropertyInfo *const property_infos_WIRELESS[] = {
PROPERTY_INFO_WITH_DESC (NM_SETTING_WIRELESS_SSID,
@@ -8001,6 +8023,7 @@ _setting_init_fcn_wireless (ARGS_SETTING_INIT_FCN)
#define SETTING_PRETTY_NAME_WIFI_P2P N_("Wi-Fi P2P connection")
#define SETTING_PRETTY_NAME_WIMAX N_("WiMAX connection")
#define SETTING_PRETTY_NAME_WIRED N_("Wired Ethernet")
+#define SETTING_PRETTY_NAME_WIREGUARD N_("WireGuard VPN settings")
#define SETTING_PRETTY_NAME_WIRELESS N_("Wi-Fi connection")
#define SETTING_PRETTY_NAME_WIRELESS_SECURITY N_("Wi-Fi security settings")
#define SETTING_PRETTY_NAME_WPAN N_("WPAN settings")
@@ -8264,6 +8287,12 @@ const NMMetaSettingInfoEditor nm_meta_setting_infos_editor[] = {
NM_META_SETTING_VALID_PART_ITEM (ETHTOOL, FALSE),
),
),
+ SETTING_INFO (WIREGUARD,
+ .valid_parts = NM_META_SETTING_VALID_PARTS (
+ NM_META_SETTING_VALID_PART_ITEM (CONNECTION, TRUE),
+ NM_META_SETTING_VALID_PART_ITEM (WIREGUARD, TRUE),
+ ),
+ ),
SETTING_INFO (WIRELESS,
.alias = "wifi",
.valid_parts = NM_META_SETTING_VALID_PARTS (
diff --git a/clients/common/nm-secret-agent-simple.c b/clients/common/nm-secret-agent-simple.c
index ffcb7c8978..eeded86151 100644
--- a/clients/common/nm-secret-agent-simple.c
+++ b/clients/common/nm-secret-agent-simple.c
@@ -214,6 +214,32 @@ _secret_real_new_vpn_secret (const char *pretty_name,
return &real->base;
}
+static NMSecretAgentSimpleSecret *
+_secret_real_new_wireguard_peer_psk (NMSettingWireGuard *s_wg,
+ const char *public_key,
+ const char *preshared_key)
+{
+ SecretReal *real;
+
+ nm_assert (NM_IS_SETTING_WIREGUARD (s_wg));
+ nm_assert (public_key);
+
+ real = g_slice_new (SecretReal);
+ *real = (SecretReal) {
+ .base.secret_type = NM_SECRET_AGENT_SECRET_TYPE_WIREGUARD_PEER_PSK,
+ .base.pretty_name = g_strdup_printf (_("Preshared-key for %s"),
+ public_key),
+ .base.entry_id = g_strdup_printf (NM_SETTING_WIREGUARD_SETTING_NAME"."NM_SETTING_WIREGUARD_PEERS".%s."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY,
+ public_key),
+ .base.value = g_strdup (preshared_key),
+ .base.is_secret = TRUE,
+ .base.no_prompt_entry_id = TRUE,
+ .setting = NM_SETTING (g_object_ref (s_wg)),
+ .property = g_strdup (public_key),
+ };
+ return &real->base;
+}
+
/*****************************************************************************/
static gboolean
@@ -405,8 +431,8 @@ add_vpn_secret_helper (GPtrArray *secrets, NMSettingVpn *s_vpn, const char *name
static gboolean
add_vpn_secrets (RequestData *request,
- GPtrArray *secrets,
- char **msg)
+ GPtrArray *secrets,
+ char **msg)
{
NMSettingVpn *s_vpn = nm_connection_get_setting_vpn (request->connection);
const VpnPasswordName *secret_names, *p;
@@ -435,6 +461,74 @@ add_vpn_secrets (RequestData *request,
return TRUE;
}
+static gboolean
+add_wireguard_secrets (RequestData *request,
+ GPtrArray *secrets,
+ char **msg,
+ GError **error)
+{
+ NMSettingWireGuard *s_wg;
+ NMSecretAgentSimpleSecret *secret;
+ guint i;
+
+ s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (request->connection, NM_TYPE_SETTING_WIREGUARD));
+ if (!s_wg) {
+ g_set_error (error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_FAILED,
+ "Cannot service a WireGuard secrets request %s for a connection without WireGuard settings",
+ request->request_id);
+ return FALSE;
+ }
+
+ if ( !request->hints
+ || !request->hints[0]
+ || g_strv_contains (NM_CAST_STRV_CC (request->hints), NM_SETTING_WIREGUARD_PRIVATE_KEY)) {
+ secret = _secret_real_new_plain (NM_SECRET_AGENT_SECRET_TYPE_SECRET,
+ _("WireGuard private-key"),
+ NM_SETTING (s_wg),
+ NM_SETTING_WIREGUARD_PRIVATE_KEY);
+ g_ptr_array_add (secrets, secret);
+ }
+
+ if (request->hints) {
+
+ for (i = 0; request->hints[i]; i++) {
+ NMWireGuardPeer *peer;
+ const char *name = request->hints[i];
+ gs_free char *public_key = NULL;
+
+ if (nm_streq (name, NM_SETTING_WIREGUARD_PRIVATE_KEY))
+ continue;
+
+ if (NM_STR_HAS_PREFIX (name, NM_SETTING_WIREGUARD_PEERS".")) {
+ const char *tmp;
+
+ tmp = &name[NM_STRLEN (NM_SETTING_WIREGUARD_PEERS".")];
+ if (NM_STR_HAS_SUFFIX (tmp, "."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) {
+ public_key = g_strndup (tmp,
+ strlen (tmp) - NM_STRLEN ("."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY));
+ }
+ }
+
+ if (!public_key)
+ continue;
+
+ peer = nm_setting_wireguard_get_peer_by_public_key (s_wg, public_key, NULL);
+
+ g_ptr_array_add (secrets, _secret_real_new_wireguard_peer_psk (s_wg,
+ ( peer
+ ? nm_wireguard_peer_get_public_key (peer)
+ : public_key),
+ ( peer
+ ? nm_wireguard_peer_get_preshared_key (peer)
+ : NULL)));
+ }
+ }
+
+ *msg = g_strdup_printf (_("Secrets are required to connect WireGuard VPN '%s'"),
+ nm_connection_get_id (request->connection));
+ return TRUE;
+}
+
typedef struct {
GPid auth_dialog_pid;
GString *auth_dialog_response;
@@ -820,6 +914,10 @@ request_secrets_from_ui (RequestData *request)
if (!add_8021x_secrets (request, secrets))
goto out_fail;
}
+ } else if (nm_connection_is_type (request->connection, NM_SETTING_WIREGUARD_SETTING_NAME)) {
+ title = _("WireGuard VPN secret");
+ if (!add_wireguard_secrets (request, secrets, &msg, &error))
+ goto out_fail_error;
} else if (nm_connection_is_type (request->connection, NM_SETTING_CDMA_SETTING_NAME)) {
NMSettingCdma *s_cdma = nm_connection_get_setting_cdma (request->connection);
@@ -980,10 +1078,13 @@ nm_secret_agent_simple_response (NMSecretAgentSimple *self,
if (secrets) {
GVariantBuilder conn_builder, *setting_builder;
GVariantBuilder vpn_secrets_builder;
+ GVariantBuilder wg_secrets_builder;
+ GVariantBuilder wg_peer_builder;
GHashTable *settings;
GHashTableIter iter;
const char *name;
gboolean has_vpn = FALSE;
+ gboolean has_wg = FALSE;
settings = g_hash_table_new (nm_str_hash, g_str_equal);
for (i = 0; i < secrets->len; i++) {
@@ -1011,6 +1112,19 @@ nm_secret_agent_simple_response (NMSecretAgentSimple *self,
g_variant_builder_add (&vpn_secrets_builder, "{ss}",
secret->property, secret->base.value);
break;
+ case NM_SECRET_AGENT_SECRET_TYPE_WIREGUARD_PEER_PSK:
+ if (!has_wg) {
+ g_variant_builder_init (&wg_secrets_builder, G_VARIANT_TYPE ("aa{sv}"));
+ has_wg = TRUE;
+ }
+ g_variant_builder_init (&wg_peer_builder, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&wg_peer_builder, "{sv}",
+ NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, g_variant_new_string (secret->property));
+ g_variant_builder_add (&wg_peer_builder, "{sv}",
+ NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, g_variant_new_string (secret->base.value));
+ g_variant_builder_add (&wg_secrets_builder, "a{sv}",
+ &wg_peer_builder);
+ break;
}
}
@@ -1020,6 +1134,12 @@ nm_secret_agent_simple_response (NMSecretAgentSimple *self,
g_variant_builder_end (&vpn_secrets_builder));
}
+ if (has_wg) {
+ g_variant_builder_add (setting_builder, "{sv}",
+ NM_SETTING_WIREGUARD_PEERS,
+ g_variant_builder_end (&wg_secrets_builder));
+ }
+
g_variant_builder_init (&conn_builder, NM_VARIANT_TYPE_CONNECTION);
g_hash_table_iter_init (&iter, settings);
while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &setting_builder))
diff --git a/clients/common/nm-secret-agent-simple.h b/clients/common/nm-secret-agent-simple.h
index 3e61dace4c..4a666d1711 100644
--- a/clients/common/nm-secret-agent-simple.h
+++ b/clients/common/nm-secret-agent-simple.h
@@ -25,6 +25,7 @@ typedef enum {
NM_SECRET_AGENT_SECRET_TYPE_PROPERTY,
NM_SECRET_AGENT_SECRET_TYPE_SECRET,
NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET,
+ NM_SECRET_AGENT_SECRET_TYPE_WIREGUARD_PEER_PSK,
} NMSecretAgentSecretType;
typedef struct {
diff --git a/clients/common/settings-docs.h.in b/clients/common/settings-docs.h.in
index 6a0586e30e..235a9c7f30 100644
--- a/clients/common/settings-docs.h.in
+++ b/clients/common/settings-docs.h.in
@@ -362,6 +362,10 @@
#define DESCRIBE_DOC_NM_SETTING_WIFI_P2P_WPS_METHOD N_("Flags indicating which mode of WPS is to be used. There's little point in changing the default setting as NetworkManager will automatically determine the best method to use.")
#define DESCRIBE_DOC_NM_SETTING_WIMAX_MAC_ADDRESS N_("If specified, this connection will only apply to the WiMAX device whose MAC address matches. This property does not change the MAC address of the device (known as MAC spoofing). Deprecated: 1")
#define DESCRIBE_DOC_NM_SETTING_WIMAX_NETWORK_NAME N_("Network Service Provider (NSP) name of the WiMAX network this connection should use. Deprecated: 1")
+#define DESCRIBE_DOC_NM_SETTING_WIREGUARD_FWMARK N_("The use of fwmark is optional and is by default off. Setting it to 0 disables it. Otherwise it is a 32-bit fwmark for outgoing packets.")
+#define DESCRIBE_DOC_NM_SETTING_WIREGUARD_LISTEN_PORT N_("The listen-port. If listen-port is not specified, the port will be chosen randomly when the interface comes up.")
+#define DESCRIBE_DOC_NM_SETTING_WIREGUARD_PRIVATE_KEY N_("The 256 bit private-key in base64 encoding.")
+#define DESCRIBE_DOC_NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS N_("Flags indicating how to handle the \"private-key\" property.")
#define DESCRIBE_DOC_NM_SETTING_WPAN_CHANNEL N_("IEEE 802.15.4 channel. A positive integer or -1, meaning \"do not set, use whatever the device is already set to\".")
#define DESCRIBE_DOC_NM_SETTING_WPAN_MAC_ADDRESS N_("If specified, this connection will only apply to the IEEE 802.15.4 (WPAN) MAC layer device whose permanent MAC address matches.")
#define DESCRIBE_DOC_NM_SETTING_WPAN_PAGE N_("IEEE 802.15.4 channel page. A positive integer or -1, meaning \"do not set, use whatever the device is already set to\".")
diff --git a/docs/libnm/libnm-docs.xml b/docs/libnm/libnm-docs.xml
index 2a7b76bf34..74866acebd 100644
--- a/docs/libnm/libnm-docs.xml
+++ b/docs/libnm/libnm-docs.xml
@@ -234,6 +234,7 @@ print ("NetworkManager version " + client.get_version())]]></programlisting></in
<xi:include href="xml/nm-setting-wifi-p2p.xml"/>
<xi:include href="xml/nm-setting-wimax.xml"/>
<xi:include href="xml/nm-setting-wired.xml"/>
+ <xi:include href="xml/nm-setting-wireguard.xml"/>
<xi:include href="xml/nm-setting-wireless-security.xml"/>
<xi:include href="xml/nm-setting-wireless.xml"/>
<xi:include href="xml/nm-setting-wpan.xml"/>
diff --git a/examples/python/gi/nm-wg-set b/examples/python/gi/nm-wg-set
new file mode 100755
index 0000000000..85376eada3
--- /dev/null
+++ b/examples/python/gi/nm-wg-set
@@ -0,0 +1,423 @@
+#!/usr/bin/env python
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright 2018 - 2019 Red Hat, Inc.
+
+# nm-wg-set: modify an existing WireGuard connection profile.
+#
+# $ nm-wg-set [id|uuid|interface] ID [wg-args...]
+#
+# The arguments to set the parameters are like the set parameters from `man 8 wg`.
+# For example:
+#
+# $ nm-wg-set wg0 peer wN8G5HpphoXOGkiXTgBPyr9BhrRm2z9JEI6BiH6fB0g= preshared-key <(wg genpsk)
+#
+# extra, script specific arguments:
+# - private-key-flags
+# - preshared-key-flags
+#
+# Note that the arguments have some simliarities to `wg set` command. But this
+# script only modify the connection profile in NetworkManager. They don't (re)activate
+# the profile and thus the changes only result in the configuration of the kernel interface
+# after activating the profile. Use `nmcli connection up` for that.
+#
+# The example script does not support creating or deleting the WireGuard profile itself. It also
+# does not support modifying other settings of the connection profile, like the IP address configuation.
+# For that also use nmcli. For example:
+#
+# PROFILE=wg0
+#
+# # create the WireGuard profile with nmcli
+# PRIVKEY_FILE=/tmp/wg.key
+# (umask 077; rm -f "$PRIVKEY_FILE"; wg genkey > "$PRIVKEY_FILE")
+# IFNAME=wg0
+# PUBKEY=$(wg pubkey < "$PRIVKEY_FILE")
+# IP4ADDR=192.168.99.5/24
+# IP4GW=192.168.99.1
+# nmcli connection delete id "$PROFILE"
+# nmcli connection add \
+# type wireguard \
+# con-name "$PROFILE" \
+# ifname "$IFNAME" \
+# connection.stable-id "$PROFILE-$PUBKEY" \
+# ipv4.method manual \
+# ipv4.addresses "$IP4ADDR" \
+# ipv4.gateway "$IP4GW" \
+# ipv4.never-default yes \
+# ipv6.method link-local \
+# wireguard.listen-port 0 \
+# wireguard.fwmark 0 \
+# wireguard.private-key '' \
+# wireguard.private-key-flags 0
+# nmcli connection up \
+# id "$PROFILE" \
+# passwd-file <(echo "wireguard.private-key:$(cat "$PRIVKEY_FILE")")
+#
+# # modify the WireGuard profile with the script
+# nm-wg-set id "$PROFILE" $WG_ARGS
+
+import sys
+import re
+
+import gi
+gi.require_version('NM', '1.0')
+from gi.repository import NM
+
+class MyError(Exception):
+ pass
+
+def pr(v):
+ import pprint
+ pprint.pprint(v, indent=4, depth=5, width=60)
+
+###############################################################################
+
+def connection_is_wireguard(conn):
+ s_con = conn.get_setting(NM.SettingConnection)
+ return s_con \
+ and s_con.get_connection_type() == NM.SETTING_WIREGUARD_SETTING_NAME \
+ and conn.get_setting(NM.SettingWireGuard)
+
+def connection_to_str(conn):
+ if connection_is_wireguard(conn):
+ iface = conn.get_setting(NM.SettingConnection).get_interface_name()
+ if iface:
+ extra = ', interface: "%s"' % (iface)
+ else:
+ extra = ''
+ else:
+ extra = ', type: %s' % (conn.get_setting(NM.SettingConnection).get_connection_type())
+
+ return '"%s" (%s%s)' % (conn.get_id(), conn.get_uuid(), extra)
+
+def connections_find(connections, con_spec, con_id):
+ connections = list(sorted(connections, key=connection_to_str))
+ l = []
+ if con_spec in [None, 'id']:
+ for c in connections:
+ if con_id == c.get_id():
+ if c not in l:
+ l.append(c)
+ if con_spec in [None, 'interface']:
+ for c in connections:
+ s_con = c.get_setting(NM.SettingConnection)
+ if s_con \
+ and con_id == s_con.get_interface_name():
+ if c not in l:
+ l.append(c)
+ if con_spec in [None, 'uuid']:
+ for c in connections:
+ if con_id == c.get_uuid():
+ if c not in l:
+ l.append(c)
+ return l
+
+###############################################################################
+
+def argv_get_one(argv, idx, type_ctor=None, topic=None):
+
+ if topic is not None:
+ try:
+ v = argv_get_one(argv, idx, type_ctor, None)
+ except MyError as e:
+ if isinstance(topic, (int, long)):
+ topic = argv[topic]
+ raise MyError('error for "%s": %s' % (topic, e.message))
+ return v
+
+ v = None
+ try:
+ v = argv[idx]
+ except:
+ raise MyError('missing argument')
+ if type_ctor is not None:
+ try:
+ v = type_ctor(v)
+ except Exception as e:
+ raise MyError('invalid argument "%s" (%s)' % (v, e.message))
+ return v
+
+###############################################################################
+
+def arg_parse_secret_flags(arg):
+ try:
+ f = arg.strip()
+ n = {
+ 'none': NM.SettingSecretFlags.NONE,
+ 'not-saved': NM.SettingSecretFlags.NOT_SAVED,
+ 'not-required': NM.SettingSecretFlags.NOT_REQUIRED,
+ 'agent-owned': NM.SettingSecretFlags.AGENT_OWNED,
+ }.get(f)
+ if n is not None:
+ return n
+ return NM.SettingSecretFlags(int(f))
+ except Exception as e:
+ raise MyError('invalid secret flags "%s"' % (arg))
+
+def _arg_parse_int(arg, vmin, vmax, key, base = 0):
+ try:
+ v = int(arg, base)
+ if v >= vmin and vmax <= 0xFFFFFFFF:
+ return v
+ except:
+ raise MyError('invalid %s "%s"' % (key, arg))
+ raise MyError("%s out of range" % (key))
+
+def arg_parse_listen_port(arg):
+ return _arg_parse_int(arg, 0, 0xFFFF, "listen-port")
+
+def arg_parse_fwmark(arg):
+ return _arg_parse_int(arg, 0, 0xFFFFFFFF, "fwmark", base = 0)
+
+def arg_parse_persistent_keep_alive(arg):
+ return _arg_parse_int(arg, 0, 0xFFFFFFFF, "persistent-keepalive")
+
+def arg_parse_allowed_ips(arg):
+ l = [s.strip() for s in arg.strip().split(',')]
+ l = [s for s in l if s != '']
+ l = list(l)
+ # use a peer to parse and validate the allowed-ips.
+ peer = NM.WireGuardPeer()
+ for aip in l:
+ if not peer.append_allowed_ip(aip, False):
+ raise MyError('invalid allowed-ip "%s"' % (aip))
+ return l
+
+###############################################################################
+
+def secret_flags_to_string(flags):
+ nick = {
+ NM.SettingSecretFlags.NONE: 'none',
+ NM.SettingSecretFlags.NOT_SAVED: 'not-saved',
+ NM.SettingSecretFlags.NOT_REQUIRED: 'not-required',
+ NM.SettingSecretFlags.AGENT_OWNED: 'agent-owned',
+ }.get(flags)
+ num = str(int(flags))
+ if nick is None:
+ return num
+ return '%s (%s)' % (num, nick)
+
+###############################################################################
+
+def wg_read_private_key(privkey_file):
+ import base64
+ try:
+ with open(privkey_file, "r") as f:
+ data = f.read()
+ bdata = base64.decodestring(data)
+ if len(bdata) != 32:
+ raise Exception("not 32 bytes base64 encoded")
+ return base64.encodestring(bdata).strip()
+ except Exception as e:
+ raise MyError('failed to read private key "%s": %s' % (privkey_file, e.message))
+
+def wg_peer_is_valid(peer, msg = None):
+ try:
+ peer.is_valid(True, True)
+ except gi.repository.GLib.Error as e:
+ if msg is None:
+ raise MyError('%s' % (e.message))
+ else:
+ raise MyError('%s' % (msg))
+
+###############################################################################
+
+def do_get(nm_client, connection):
+ s_con = conn.get_setting(NM.SettingConnection)
+ s_wg = conn.get_setting(NM.SettingWireGuard)
+
+ # Fetching secrets is not implemented. For now show them all as
+ # <hidden>.
+
+ print('interface: %s' % (s_con.get_interface_name()))
+ print('uuid: %s' % (conn.get_uuid()))
+ print('id: %s' % (conn.get_id()))
+ print('private-key: %s' % ('<hidden>'))
+ print('private-key-flags: %s' % (secret_flags_to_string(s_wg.get_private_key_flags())))
+ print('listen-port: %s' % (s_wg.get_listen_port()))
+ print('fwmark: 0x%x' % (s_wg.get_fwmark()))
+ for i in range(s_wg.get_peers_len()):
+ peer = s_wg.get_peer(i)
+ print('peer[%d].public-key: %s' % (i, peer.get_public_key()))
+ print('peer[%d].preshared-key: %s' % (i, '<hidden>' if peer.get_preshared_key_flags() != NM.SettingSecretFlags.NOT_REQUIRED else ''))
+ print('peer[%d].preshared-key-flags: %s' % (i, secret_flags_to_string(peer.get_preshared_key_flags())))
+ print('peer[%d].endpoint: %s' % (i, peer.get_endpoint() if peer.get_endpoint() else ''))
+ print('peer[%d].persistent-keepalive: %s' % (i, peer.get_persistent_keepalive()))
+ print('peer[%d].allowed-ips: %s' % (i, ','.join([peer.get_allowed_ip(j) for j in range(peer.get_allowed_ips_len())])))
+
+def do_set(nm_client, conn, argv):
+ s_wg = conn.get_setting(NM.SettingWireGuard)
+ peer = None
+ peer_remove = False
+ peer_idx = None
+ peer_secret_flags = None
+
+ try:
+ idx = 0
+ while True:
+ if peer \
+ and ( idx >= len(argv) \
+ or argv[idx] == 'peer'):
+ if peer_remove:
+ pp_peer, pp_idx = s_wg.get_peer_by_public_key(peer.get_public_key())
+ if pp_peer:
+ s_wg.remove_peer(pp_idx)
+ else:
+ if peer_secret_flags is not None:
+ peer.set_preshared_key_flags(peer_secret_flags)
+ wg_peer_is_valid(peer)
+ if peer_idx is None:
+ s_wg.append_peer(peer)
+ else:
+ s_wg.set_peer(peer, peer_idx)
+ peer = None
+ peer_remove = False
+ peer_idx = None
+ peer_secret_flags = None
+
+ if idx >= len(argv):
+ break;
+
+ if not peer and argv[idx] == 'private-key':
+ key = argv_get_one(argv, idx + 1, None, idx)
+ if key == '':
+ s_wg.set_property(NM.SETTING_WIREGUARD_PRIVATE_KEY, None)
+ else:
+ s_wg.set_property(NM.SETTING_WIREGUARD_PRIVATE_KEY, wg_read_private_key(key))
+ idx += 2
+ continue
+ if not peer and argv[idx] == 'private-key-flags':
+ s_wg.set_property(NM.SETTING_WIREGUARD_PRIVATE_KEY_FLAGS, argv_get_one(argv, idx + 1, arg_parse_secret_flags, idx))
+ idx += 2
+ continue
+ if not peer and argv[idx] == 'listen-port':
+ s_wg.set_property(NM.SETTING_WIREGUARD_LISTEN_PORT, argv_get_one(argv, idx + 1, arg_parse_listen_port, idx))
+ idx += 2
+ continue
+ if not peer and argv[idx] == 'fwmark':
+ s_wg.set_property(NM.SETTING_WIREGUARD_FWMARK, argv_get_one(argv, idx + 1, arg_parse_fwmark, idx))
+ idx += 2
+ continue
+ if argv[idx] == 'peer':
+ public_key = argv_get_one(argv, idx + 1, None, idx)
+ peer, peer_idx = s_wg.get_peer_by_public_key(public_key)
+ if peer:
+ peer = peer.new_clone(True)
+ else:
+ peer_idx = None
+ peer = NM.WireGuardPeer()
+ peer.set_public_key(public_key)
+ wg_peer_is_valid(peer, 'public key "%s" is invalid' % (public_key))
+ peer_remove = False
+ idx += 2
+ continue
+ if peer and argv[idx] == 'remove':
+ peer_remove = True
+ idx += 1
+ continue
+ if peer and argv[idx] == 'preshared-key':
+ psk = argv_get_one(argv, idx + 1, None, idx)
+ if psk == '':
+ peer.set_preshared_key(None)
+ if peer_secret_flags is not None:
+ peer_secret_flags = NM.SettingSecretFlags.NOT_REQUIRED
+ else:
+ peer.set_preshared_key(wg_read_private_key(psk))
+ if peer_secret_flags is not None:
+ peer_secret_flags = NM.SettingSecretFlags.NONE
+ idx += 2
+ continue
+ if peer and argv[idx] == 'preshared-key-flags':
+ peer_secret_flags = argv_get_one(argv, idx + 1, arg_parse_secret_flags, idx)
+ idx += 2
+ continue
+ if peer and argv[idx] == 'endpoint':
+ peer.set_endpoint(argv_get_one(argv, idx + 1, None, idx))
+ idx += 2
+ continue
+ if peer and argv[idx] == 'persistent-keepalive':
+ peer.set_persistent_keepalive(argv_get_one(argv, idx + 1, arg_parse_persistent_keep_alive, idx))
+ idx += 2
+ continue
+ if peer and argv[idx] == 'allowed-ips':
+ allowed_ips = list(argv_get_one(argv, idx + 1, arg_parse_allowed_ips, idx))
+ peer.clear_allowed_ips()
+ for aip in allowed_ips:
+ peer.append_allowed_ip(aip, False)
+ del allowed_ips
+ idx += 2
+ continue
+
+ raise MyError('invalid argument "%s"' % (argv[idx]))
+ except MyError as e:
+ print('Error: %s' % (e.message))
+ sys.exit(1)
+
+ try:
+ conn.commit_changes(True, None)
+ except Exception as e:
+ print('failure to commit connection: %s' % (e))
+ sys.exit(1)
+
+ print('Success')
+ sys.exit(0)
+
+###############################################################################
+
+if __name__ == '__main__':
+
+ argv = sys.argv
+ del argv[0]
+
+ con_spec = None
+ if len(argv) >= 1:
+ if argv[0] in [ 'id', 'uuid', 'interface' ]:
+ con_spec = argv[0]
+ del argv[0]
+ if len(argv) < 1:
+ print('Requires an existing NetworkManager connection profile as first argument')
+ print('Select it based on the connection ID, UUID, or interface-name (optionally qualify the selection with [id|uuid|interface])')
+ print('Maybe you want to create one first with')
+ print(' nmcli connection add type wireguard ifname wg0 $MORE_ARGS')
+ sys.exit(1)
+ con_id = argv[0]
+ del argv[0]
+
+ nm_client = NM.Client.new(None)
+
+ connections = connections_find(nm_client.get_connections(), con_spec, con_id)
+ if len(connections) == 0:
+ print('No matching connection %s\"%s\" found.' % ((con_spec+' ' if con_spec else ''), con_id))
+ print('Maybe you want to create one first with')
+ print(' nmcli connection add type wireguard ifname wg0 $MORE_ARGS')
+ sys.exit(1)
+ if len(connections) > 1:
+ print("Connection %s\"%s\" is not unique (%s)" % ((con_spec+' ' if con_spec else ''), con_id, ', '.join(['['+connection_to_str(c)+']' for c in connections])))
+ if not con_spec:
+ print('Maybe qualify the name with [id|uuid|interface]?')
+ sys.exit(1)
+
+ conn = connections[0]
+ if not connection_is_wireguard(conn):
+ print('Connection %s is not a WireGuard profile' % (connection_to_str(conn)))
+ print('See available profiles with `nmcli connection show`')
+ sys.exit(1)
+
+ if not argv:
+ do_get(nm_client, conn)
+ else:
+ do_set(nm_client, conn, argv)
+
diff --git a/libnm-core/meson.build b/libnm-core/meson.build
index a2610fe46f..d10dd1c551 100644
--- a/libnm-core/meson.build
+++ b/libnm-core/meson.build
@@ -48,6 +48,7 @@ libnm_core_headers = files(
'nm-setting-wifi-p2p.h',
'nm-setting-wimax.h',
'nm-setting-wired.h',
+ 'nm-setting-wireguard.h',
'nm-setting-wireless-security.h',
'nm-setting-wireless.h',
'nm-setting-wpan.h',
@@ -104,6 +105,7 @@ libnm_core_settings_sources = files(
'nm-setting-wifi-p2p.c',
'nm-setting-wimax.c',
'nm-setting-wired.c',
+ 'nm-setting-wireguard.c',
'nm-setting-wireless-security.c',
'nm-setting-wireless.c',
'nm-setting-wpan.c',
diff --git a/libnm-core/nm-connection.c b/libnm-core/nm-connection.c
index 5e84cf45b6..e958aff06d 100644
--- a/libnm-core/nm-connection.c
+++ b/libnm-core/nm-connection.c
@@ -905,25 +905,24 @@ _supports_addr_family (NMConnection *self, int family)
static gboolean
_normalize_ip_config (NMConnection *self, GHashTable *parameters)
{
- const char *default_ip4_method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
- const char *default_ip6_method = NULL;
NMSettingIPConfig *s_ip4, *s_ip6;
NMSettingProxy *s_proxy;
NMSetting *setting;
gboolean changed = FALSE;
guint num, i;
- if (parameters)
- default_ip6_method = g_hash_table_lookup (parameters, NM_CONNECTION_NORMALIZE_PARAM_IP6_CONFIG_METHOD);
- if (!default_ip6_method)
- default_ip6_method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
-
s_ip4 = nm_connection_get_setting_ip4_config (self);
s_ip6 = nm_connection_get_setting_ip6_config (self);
s_proxy = nm_connection_get_setting_proxy (self);
if (_supports_addr_family (self, AF_INET)) {
+
if (!s_ip4) {
+ const char *default_ip4_method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
+
+ if (nm_connection_is_type (self, NM_SETTING_WIREGUARD_SETTING_NAME))
+ default_ip4_method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
+
/* But if no IP4 setting was specified, assume the caller was just
* being lazy and use the default method.
*/
@@ -966,6 +965,17 @@ _normalize_ip_config (NMConnection *self, GHashTable *parameters)
if (_supports_addr_family (self, AF_INET6)) {
if (!s_ip6) {
+ const char *default_ip6_method = NULL;
+
+ if (parameters)
+ default_ip6_method = g_hash_table_lookup (parameters, NM_CONNECTION_NORMALIZE_PARAM_IP6_CONFIG_METHOD);
+ if (!default_ip6_method) {
+ if (nm_connection_is_type (self, NM_SETTING_WIREGUARD_SETTING_NAME))
+ default_ip6_method = NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
+ else
+ default_ip6_method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
+ }
+
/* If no IP6 setting was specified, then assume that means IP6 config is
* allowed to fail.
*/
@@ -2419,7 +2429,8 @@ nm_connection_is_virtual (NMConnection *connection)
NM_SETTING_TEAM_SETTING_NAME,
NM_SETTING_TUN_SETTING_NAME,
NM_SETTING_VLAN_SETTING_NAME,
- NM_SETTING_VXLAN_SETTING_NAME))
+ NM_SETTING_VXLAN_SETTING_NAME,
+ NM_SETTING_WIREGUARD_SETTING_NAME))
return TRUE;
if (nm_streq (type, NM_SETTING_INFINIBAND_SETTING_NAME)) {
diff --git a/libnm-core/nm-core-enum-types.c.template b/libnm-core/nm-core-enum-types.c.template
index 2cef0307a1..94744827ba 100644
--- a/libnm-core/nm-core-enum-types.c.template
+++ b/libnm-core/nm-core-enum-types.c.template
@@ -46,6 +46,7 @@
#include "nm-setting-wifi-p2p.h"
#include "nm-setting-wimax.h"
#include "nm-setting-wired.h"
+#include "nm-setting-wireguard.h"
#include "nm-setting-wireless-security.h"
#include "nm-setting-wireless.h"
#include "nm-setting-wpan.h"
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h
index 19a914956e..4a5796bd6e 100644
--- a/libnm-core/nm-core-internal.h
+++ b/libnm-core/nm-core-internal.h
@@ -79,6 +79,7 @@
#include "nm-setting-wifi-p2p.h"
#include "nm-setting-wimax.h"
#include "nm-setting-wired.h"
+#include "nm-setting-wireguard.h"
#include "nm-setting-wireless-security.h"
#include "nm-setting-wireless.h"
#include "nm-setting-wpan.h"
@@ -633,6 +634,15 @@ NM_AUTO_DEFINE_FCN_VOID0 (NMSockAddrEndpoint *, _nm_auto_unref_sockaddrendpoint,
/*****************************************************************************/
+NMSockAddrEndpoint *_nm_wireguard_peer_get_endpoint (const NMWireGuardPeer *self);
+void _nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self,
+ NMSockAddrEndpoint *endpoint);
+
+void _nm_wireguard_peer_set_public_key_bin (NMWireGuardPeer *self,
+ const guint8 public_key[static NM_WIREGUARD_PUBLIC_KEY_LEN]);
+
+/*****************************************************************************/
+
typedef struct _NMSettInfoSetting NMSettInfoSetting;
typedef struct _NMSettInfoProperty NMSettInfoProperty;
@@ -768,4 +778,17 @@ gboolean _nm_connection_find_secret (NMConnection *self,
/*****************************************************************************/
+#define nm_auto_unref_wgpeer nm_auto(_nm_auto_unref_wgpeer)
+NM_AUTO_DEFINE_FCN_VOID0 (NMWireGuardPeer *, _nm_auto_unref_wgpeer, nm_wireguard_peer_unref)
+
+gboolean _nm_utils_wireguard_decode_key (const char *base64_key,
+ gsize required_key_len,
+ guint8 *out_key);
+
+gboolean _nm_utils_wireguard_normalize_key (const char *base64_key,
+ gsize required_key_len,
+ char **out_base64_key_norm);
+
+/*****************************************************************************/
+
#endif
diff --git a/libnm-core/nm-core-types.h b/libnm-core/nm-core-types.h
index e8aa67a93f..f20ffc09da 100644
--- a/libnm-core/nm-core-types.h
+++ b/libnm-core/nm-core-types.h
@@ -72,6 +72,7 @@ typedef struct _NMSettingVxlan NMSettingVxlan;
typedef struct _NMSettingWifiP2P NMSettingWifiP2P;
typedef struct _NMSettingWimax NMSettingWimax;
typedef struct _NMSettingWired NMSettingWired;
+typedef struct _NMSettingWireGuard NMSettingWireGuard;
typedef struct _NMSettingWireless NMSettingWireless;
typedef struct _NMSettingWirelessSecurity NMSettingWirelessSecurity;
typedef struct _NMSettingWpan NMSettingWpan;
diff --git a/libnm-core/nm-keyfile-utils.h b/libnm-core/nm-keyfile-utils.h
index 0467230e49..9403dfa3a8 100644
--- a/libnm-core/nm-keyfile-utils.h
+++ b/libnm-core/nm-keyfile-utils.h
@@ -25,7 +25,8 @@
#error Cannot use this header.
#endif
-#define NM_KEYFILE_GROUP_VPN_SECRETS "vpn-secrets"
+#define NM_KEYFILE_GROUP_VPN_SECRETS "vpn-secrets"
+#define NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER "wireguard-peer."
const char *nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name);
diff --git a/libnm-core/nm-keyfile.c b/libnm-core/nm-keyfile.c
index b81020a3bc..d756a17733 100644
--- a/libnm-core/nm-keyfile.c
+++ b/libnm-core/nm-keyfile.c
@@ -32,6 +32,7 @@
#include <linux/pkt_sched.h>
#include "nm-utils/nm-secret-utils.h"
+#include "systemd/nm-sd-utils-shared.h"
#include "nm-common-macros.h"
#include "nm-core-internal.h"
#include "nm-keyfile-utils.h"
@@ -2902,6 +2903,137 @@ out:
}
static void
+_read_setting_wireguard_peer (KeyfileReaderInfo *info)
+{
+ gs_unref_object NMSettingWireGuard *s_wg_new = NULL;
+ nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
+ gs_free_error GError *error = NULL;
+ NMSettingWireGuard *s_wg;
+ gs_free char *str = NULL;
+ const char *cstr = NULL;
+ const char *key;
+ gint64 i64;
+ gs_strfreev char **sa = NULL;
+ gsize n_sa;
+
+ peer = nm_wireguard_peer_new ();
+
+ nm_assert (g_str_has_prefix (info->group, NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER));
+ cstr = &info->group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)];
+ if ( !_nm_utils_wireguard_normalize_key (cstr, NM_WIREGUARD_PUBLIC_KEY_LEN, &str)
+ || !nm_streq0 (str, cstr)) {
+ /* the group name must be identical to the normalized(!) key, so that it
+ * is uniquely identified. */
+ handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN,
+ _("invalid peer public key in section '%s'"),
+ info->group);
+ return;
+ }
+ nm_wireguard_peer_set_public_key (peer, cstr);
+ nm_clear_g_free (&str);
+
+ key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY;
+ str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL);
+ if (str) {
+ if (!_nm_utils_wireguard_decode_key (str, NM_WIREGUARD_SYMMETRIC_KEY_LEN, NULL)) {
+ if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+ _("key '%s.%s' is not not a valid 256 bit key in base64 encoding"),
+ info->group, key))
+ return;
+ } else
+ nm_wireguard_peer_set_preshared_key (peer, str);
+ nm_clear_g_free (&str);
+ }
+
+ key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS;
+ i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, NM_SETTING_SECRET_FLAG_ALL, -1, NULL);
+ if (errno != ENODATA) {
+ if ( i64 == -1
+ || !_nm_setting_secret_flags_valid (i64)) {
+ if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+ _("key '%s.%s' is not not a valid secret flag"),
+ info->group, key))
+ return;
+ } else
+ nm_wireguard_peer_set_preshared_key_flags (peer, i64);
+ }
+
+ key = NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE;
+ i64 = nm_keyfile_plugin_kf_get_int64 (info->keyfile, info->group, key, 0, 0, G_MAXUINT32, -1, NULL);
+ if (errno != ENODATA) {
+ if (i64 == -1) {
+ if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+ _("key '%s.%s' is not not a integer in range 0 to 2^32"),
+ info->group, key))
+ return;
+ } else
+ nm_wireguard_peer_set_persistent_keepalive (peer, i64);
+ }
+
+ key = NM_WIREGUARD_PEER_ATTR_ENDPOINT;
+ str = nm_keyfile_plugin_kf_get_string (info->keyfile, info->group, key, NULL);
+ if (str && str[0]) {
+ nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL;
+
+ ep = nm_sock_addr_endpoint_new (str);
+ if (!nm_sock_addr_endpoint_get_host (ep)) {
+ if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+ _("key '%s.%s' is not not a valid endpoint"),
+ info->group, key))
+ return;
+ } else
+ _nm_wireguard_peer_set_endpoint (peer, ep);
+ }
+ nm_clear_g_free (&str);
+
+ key = NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS;
+ sa = nm_keyfile_plugin_kf_get_string_list (info->keyfile, info->group, key, &n_sa, NULL);
+ if (n_sa > 0) {
+ gboolean has_error = FALSE;
+ gsize i;
+
+ for (i = 0; i < n_sa; i++) {
+ if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC, sa[i], NULL, NULL, NULL)) {
+ has_error = TRUE;
+ continue;
+ }
+ nm_wireguard_peer_append_allowed_ip (peer, sa[i], TRUE);
+ }
+ if (has_error) {
+ if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+ _("key '%s.%s' has invalid allowed-ips"),
+ info->group, key))
+ return;
+ }
+ }
+ nm_clear_pointer (&sa, g_strfreev);
+
+ if (info->error)
+ return;
+
+ if (!nm_wireguard_peer_is_valid (peer, TRUE, TRUE, &error)) {
+ if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+ _("peer '%s' is invalid: %s"),
+ info->group, error->message))
+ return;
+ return;
+ }
+
+ s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (info->connection, NM_TYPE_SETTING_WIREGUARD));
+ if (!s_wg) {
+ s_wg_new = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ());
+ s_wg = s_wg_new;
+ }
+
+ nm_setting_wireguard_append_peer (s_wg, peer);
+
+ if (s_wg_new) {
+ nm_connection_add_setting (info->connection,
+ NM_SETTING (g_steal_pointer (&s_wg_new)));
+ }
+}
+
+static void
_read_setting_vpn_secrets (KeyfileReaderInfo *info)
{
gs_strfreev char **keys = NULL;
@@ -3021,7 +3153,9 @@ nm_keyfile_read (GKeyFile *keyfile,
if (nm_streq (groups[i], NM_KEYFILE_GROUP_VPN_SECRETS)) {
/* Only read out secrets when needed */
vpn_secrets = TRUE;
- } else
+ } else if (NM_STR_HAS_PREFIX (groups[i], NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER))
+ _read_setting_wireguard_peer (&info);
+ else
_read_setting (&info);
info.group = NULL;
@@ -3198,6 +3332,92 @@ out_unset_value:
g_value_unset (&value);
}
+static void
+_write_setting_wireguard (NMSetting *setting, KeyfileWriterInfo *info)
+{
+ NMSettingWireGuard *s_wg;
+ guint i_peer, n_peers;
+
+ s_wg = NM_SETTING_WIREGUARD (setting);
+
+ n_peers = nm_setting_wireguard_get_peers_len (s_wg);
+ for (i_peer = 0; i_peer < n_peers; i_peer++) {
+ NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i_peer);
+ const char *public_key;
+ char group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER) + 200];
+ NMSettingSecretFlags secret_flags;
+ gboolean any_key = FALSE;
+ guint i_aip, n_aip;
+ const char *cstr;
+ guint32 u32;
+
+ public_key = nm_wireguard_peer_get_public_key (peer);
+ if ( !public_key
+ || !public_key[0]
+ || !NM_STRCHAR_ALL (public_key, ch, nm_sd_utils_unbase64char (ch, TRUE) >= 0)) {
+ /* invalid peer. Skip it */
+ continue;
+ }
+
+ if (g_snprintf (group,
+ sizeof (group),
+ "%s%s",
+ NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER,
+ nm_wireguard_peer_get_public_key (peer)) >= sizeof (group)) {
+ /* Too long. Not a valid public key. Skip the peer. */
+ continue;
+ }
+
+ cstr = nm_wireguard_peer_get_endpoint (peer);
+ if (cstr) {
+ g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, cstr);
+ any_key = TRUE;
+ }
+
+ secret_flags = nm_wireguard_peer_get_preshared_key_flags (peer);
+ if (_secret_flags_persist_secret (secret_flags)) {
+ cstr = nm_wireguard_peer_get_preshared_key (peer);
+ if (cstr) {
+ g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, cstr);
+ any_key = TRUE;
+ }
+ }
+
+ /* usually, we don't persist the secret-flags 0 (because they are the default).
+ * For WireGuard peers, the default secret-flags for preshared-key are 4 (not-required).
+ * So, in this case behave differently: a missing preshared-key-flag setting means
+ * "not-required". */
+ if (secret_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED) {
+ g_key_file_set_int64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, secret_flags);
+ any_key = TRUE;
+ }
+
+ u32 = nm_wireguard_peer_get_persistent_keepalive (peer);
+ if (u32) {
+ g_key_file_set_uint64 (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, u32);
+ any_key = TRUE;
+ }
+
+ n_aip = nm_wireguard_peer_get_allowed_ips_len (peer);
+ if (n_aip > 0) {
+ gs_free const char **strv = NULL;
+
+ strv = g_new (const char *, ((gsize) n_aip) + 1);
+ for (i_aip = 0; i_aip < n_aip; i_aip++)
+ strv[i_aip] = nm_wireguard_peer_get_allowed_ip (peer, i_aip, NULL);
+ strv[n_aip] = NULL;
+ g_key_file_set_string_list (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS,
+ strv, n_aip);
+ any_key = TRUE;
+ }
+
+ if (!any_key) {
+ /* we cannot omit all keys. At an empty endpoint. */
+ g_key_file_set_string (info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, "");
+ }
+ }
+}
+
GKeyFile *
nm_keyfile_write (NMConnection *connection,
NMKeyfileWriteHandler handler,
@@ -3275,6 +3495,12 @@ nm_keyfile_write (NMConnection *connection,
goto out_with_info_error;
}
+ if (NM_IS_SETTING_WIREGUARD (setting)) {
+ _write_setting_wireguard (setting, &info);
+ if (info.error)
+ goto out_with_info_error;
+ }
+
nm_assert (!info.error);
}
diff --git a/libnm-core/nm-setting-wireguard.c b/libnm-core/nm-setting-wireguard.c
new file mode 100644
index 0000000000..19b418547d
--- /dev/null
+++ b/libnm-core/nm-setting-wireguard.c
@@ -0,0 +1,2356 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 - 2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-setting-wireguard.h"
+
+#include "nm-setting-private.h"
+#include "nm-utils-private.h"
+#include "nm-connection-private.h"
+#include "nm-utils/nm-secret-utils.h"
+
+/*****************************************************************************/
+
+/**
+ * SECTION:nm-setting-wireguard
+ * @short_description: Describes connection properties for wireguard related options
+ *
+ * The #NMSettingWireGuard object is a #NMSetting subclass that contains settings
+ * for configuring WireGuard.
+ **/
+
+/*****************************************************************************/
+
+static NMWireGuardPeer *_wireguard_peer_dup (const NMWireGuardPeer *self);
+
+G_DEFINE_BOXED_TYPE (NMWireGuardPeer, nm_wireguard_peer, _wireguard_peer_dup, nm_wireguard_peer_unref)
+
+/* NMWireGuardPeer can also track invalid allowed-ip settings, and only reject
+ * them later during is_valid(). Such values are marked by a leading 'X' character
+ * in the @allowed_ips. It is expected, that such values are the expception, and
+ * commonly not present. */
+#define ALLOWED_IP_INVALID_X 'X'
+#define ALLOWED_IP_INVALID_X_STR "X"
+
+/**
+ * NMWireGuardPeer:
+ *
+ * The settings of one WireGuard peer.
+ *
+ * Since: 1.16
+ */
+struct _NMWireGuardPeer {
+ NMSockAddrEndpoint *endpoint;
+ char *public_key;
+ char *preshared_key;
+ GPtrArray *allowed_ips;
+ guint refcount;
+ NMSettingSecretFlags preshared_key_flags;
+ guint16 persistent_keepalive;
+ bool public_key_valid:1;
+ bool preshared_key_valid:1;
+ bool sealed:1;
+};
+
+static gboolean
+NM_IS_WIREGUARD_PEER (const NMWireGuardPeer *self, gboolean also_sealed)
+{
+ return self
+ && self->refcount > 0
+ && ( also_sealed
+ || !self->sealed);
+}
+
+/**
+ * nm_wireguard_peer_new:
+ *
+ * Returns: (transfer full): a new, default, unsealed #NMWireGuardPeer instance.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_wireguard_peer_new (void)
+{
+ NMWireGuardPeer *self;
+
+ self = g_slice_new (NMWireGuardPeer);
+ *self = (NMWireGuardPeer) {
+ .refcount = 1,
+ .preshared_key_flags = NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
+ };
+ return self;
+}
+
+/**
+ * nm_wireguard_peer_new_clone:
+ * @self: the #NMWireGuardPeer instance to copy.
+ * @with_secrets: if %TRUE, the preshared-key secrets are copied
+ * as well. Otherwise, they will be removed.
+ *
+ * Returns: (transfer full): a clone of @self. This instance
+ * is always unsealed.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_wireguard_peer_new_clone (const NMWireGuardPeer *self,
+ gboolean with_secrets)
+{
+ NMWireGuardPeer *new;
+ guint i;
+
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
+
+ new = g_slice_new (NMWireGuardPeer);
+ *new = (NMWireGuardPeer) {
+ .refcount = 1,
+ .public_key = g_strdup (self->public_key),
+ .public_key_valid = self->public_key_valid,
+ .preshared_key = with_secrets ? g_strdup (self->preshared_key) : NULL,
+ .preshared_key_valid = self->preshared_key_valid,
+ .preshared_key_flags = self->preshared_key_flags,
+ .endpoint = nm_sock_addr_endpoint_ref (self->endpoint),
+ .persistent_keepalive = self->persistent_keepalive,
+ };
+ if ( self->allowed_ips
+ && self->allowed_ips->len > 0) {
+ new->allowed_ips = g_ptr_array_new_full (self->allowed_ips->len,
+ g_free);
+ for (i = 0; i < self->allowed_ips->len; i++) {
+ g_ptr_array_add (new->allowed_ips,
+ g_strdup (self->allowed_ips->pdata[i]));
+ }
+ }
+ return new;
+}
+
+/**
+ * nm_wireguard_peer_ref:
+ * @self: (allow-none): the #NMWireGuardPeer instance
+ *
+ * This is not thread-safe.
+ *
+ * Returns: returns the input argument @self after incrementing
+ * the reference count.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_wireguard_peer_ref (NMWireGuardPeer *self)
+{
+ if (!self)
+ return NULL;
+
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
+
+ nm_assert (self->refcount < G_MAXUINT);
+
+ self->refcount++;
+ return self;
+}
+
+/**
+ * nm_wireguard_peer_unref:
+ * @self: (allow-none): the #NMWireGuardPeer instance
+ *
+ * Drop a reference to @self. If the last reference is dropped,
+ * the instance is freed and all accociate data released.
+ *
+ * This is not thread-safe.
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_unref (NMWireGuardPeer *self)
+{
+ if (!self)
+ return;
+
+ g_return_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE));
+
+ if (--self->refcount > 0)
+ return;
+
+ nm_sock_addr_endpoint_unref (self->endpoint);
+ if (self->allowed_ips)
+ g_ptr_array_unref (self->allowed_ips);
+ g_free (self->public_key);
+ nm_free_secret (self->preshared_key);
+ g_slice_free (NMWireGuardPeer, self);
+}
+
+/**
+ * _wireguard_peer_dup:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Duplicates the #NMWireGuardPeer instance. Note that if @self
+ * is already sealed, this increments the reference count and
+ * returns it. If the instance is still unsealed, it is copied.
+ *
+ * Returns: (transfer full): a duplicate of @self, or (if the
+ * instance is sealed and thus immutable) a reference to @self.
+ * As such, the instance will be sealed if and only if @self is
+ * sealed.
+ */
+static NMWireGuardPeer *
+_wireguard_peer_dup (const NMWireGuardPeer *self)
+{
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
+
+ if (self->sealed)
+ return nm_wireguard_peer_ref ((NMWireGuardPeer *) self);
+ return nm_wireguard_peer_new_clone (self, TRUE);
+}
+
+/**
+ * nm_wireguard_peer_seal:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Seal the #NMWireGuardPeer instance. Afterwards, it is a bug
+ * to call all functions that modify the instance (except ref/unref).
+ * A sealed instance cannot be unsealed again, but you can create
+ * an unsealed copy with nm_wireguard_peer_new_clone().
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_seal (NMWireGuardPeer *self)
+{
+ g_return_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE));
+
+ self->sealed = TRUE;
+
+ if (self->allowed_ips) {
+ if (self->allowed_ips->len == 0)
+ nm_clear_pointer (&self->allowed_ips, g_ptr_array_unref);
+ }
+}
+
+/**
+ * nm_wireguard_peer_is_sealed:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: whether @self is sealed or not.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_is_sealed (const NMWireGuardPeer *self)
+{
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), FALSE);
+
+ return self->sealed;
+}
+
+/**
+ * nm_wireguard_peer_get_public_key:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: (transfer none): the public key or %NULL if unset.
+ *
+ * Since: 1.16
+ */
+const char *
+nm_wireguard_peer_get_public_key (const NMWireGuardPeer *self)
+{
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
+
+ return self->public_key;
+}
+
+/**
+ * nm_wireguard_peer_set_public_key:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @public_key: (allow-none): (transfer none): the new public
+ * key or %NULL to clear the public key.
+ *
+ * Reset the public key. Note that if the public key is valid, it
+ * will be normalized (which may or may not modify the set value).
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_set_public_key (NMWireGuardPeer *self,
+ const char *public_key)
+{
+ char *public_key_normalized = NULL;
+
+ g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
+
+ if (!public_key) {
+ nm_clear_g_free (&self->public_key);
+ return;
+ }
+
+ self->public_key_valid = _nm_utils_wireguard_normalize_key (public_key,
+ NM_WIREGUARD_PUBLIC_KEY_LEN,
+ &public_key_normalized);
+ nm_assert (self->public_key_valid == (public_key_normalized != NULL));
+
+ g_free (self->public_key);
+ self->public_key = public_key_normalized ?: g_strdup (public_key);
+}
+
+void
+_nm_wireguard_peer_set_public_key_bin (NMWireGuardPeer *self,
+ const guint8 public_key[static NM_WIREGUARD_PUBLIC_KEY_LEN])
+{
+ g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
+
+ nm_clear_g_free (&self->public_key);
+
+ if (!public_key)
+ return;
+
+ self->public_key = g_base64_encode (public_key, NM_WIREGUARD_PUBLIC_KEY_LEN);
+ self->public_key_valid = TRUE;
+}
+
+/**
+ * nm_wireguard_peer_get_preshared_key:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: (transfer none): the preshared key or %NULL if unset.
+ *
+ * Since: 1.16
+ */
+const char *
+nm_wireguard_peer_get_preshared_key (const NMWireGuardPeer *self)
+{
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
+
+ return self->preshared_key;
+}
+
+/**
+ * nm_wireguard_peer_set_preshared_key:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @preshared_key: (allow-none): (transfer none): the new preshared
+ * key or %NULL to clear the preshared key.
+ *
+ * Reset the preshared key. Note that if the preshared key is valid, it
+ * will be normalized (which may or may not modify the set value).
+ *
+ * Note that the preshared-key is a secret and consequently has corresponding
+ * preshared-key-flags property. This is so that secrets can be optional
+ * and requested on demand from a secret-agent. Also, an invalid preshared-key
+ * may optionally cause nm_wireguard_peer_is_valid() to fail or it may
+ * be accepted.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_set_preshared_key (NMWireGuardPeer *self,
+ const char *preshared_key)
+{
+ char *preshared_key_normalized = NULL;
+
+ g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
+
+ if (!preshared_key) {
+ nm_clear_pointer (&self->preshared_key, nm_free_secret);
+ return;
+ }
+
+ self->preshared_key_valid = _nm_utils_wireguard_normalize_key (preshared_key,
+ NM_WIREGUARD_SYMMETRIC_KEY_LEN,
+ &preshared_key_normalized);
+ nm_assert (self->preshared_key_valid == (preshared_key_normalized != NULL));
+
+ nm_free_secret (self->preshared_key);
+ self->preshared_key = preshared_key_normalized ?: g_strdup (preshared_key);
+}
+
+/**
+ * nm_wireguard_peer_get_preshared_key_flags:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: get the secret flags for the preshared-key.
+ *
+ * Since: 1.16
+ */
+NMSettingSecretFlags
+nm_wireguard_peer_get_preshared_key_flags (const NMWireGuardPeer *self)
+{
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), 0);
+
+ return self->preshared_key_flags;
+}
+
+/**
+ * nm_wireguard_peer_set_preshared_key_flags:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @preshared_key_flags: the secret flags to set.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_set_preshared_key_flags (NMWireGuardPeer *self,
+ NMSettingSecretFlags preshared_key_flags)
+{
+ g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
+
+ self->preshared_key_flags = preshared_key_flags;
+}
+
+/**
+ * nm_wireguard_peer_get_persistent_keepalive:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: get the persistent-keepalive setting in seconds. Set to zero to disable
+ * keep-alive.
+ *
+ * Since: 1.16
+ */
+guint16
+nm_wireguard_peer_get_persistent_keepalive (const NMWireGuardPeer *self)
+{
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), 0);
+
+ return self->persistent_keepalive;
+}
+
+/**
+ * nm_wireguard_peer_set_persistent_keepalive:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @persistent_keepalive: the keep-alive value to set.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_set_persistent_keepalive (NMWireGuardPeer *self,
+ guint16 persistent_keepalive)
+{
+ g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
+
+ self->persistent_keepalive = persistent_keepalive;
+}
+
+NMSockAddrEndpoint *
+_nm_wireguard_peer_get_endpoint (const NMWireGuardPeer *self)
+{
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
+
+ return self->endpoint;
+}
+
+/**
+ * nm_wireguard_peer_get_endpoint:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: (transfer none): the endpoint or %NULL if none was set.
+ *
+ * Since: 1.16
+ */
+const char *
+nm_wireguard_peer_get_endpoint (const NMWireGuardPeer *self)
+{
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
+
+ return self->endpoint
+ ? nm_sock_addr_endpoint_get_endpoint (self->endpoint)
+ : NULL;
+}
+
+void
+_nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self,
+ NMSockAddrEndpoint *endpoint)
+{
+ NMSockAddrEndpoint *old;
+
+ nm_assert (NM_IS_WIREGUARD_PEER (self, FALSE));
+
+ old = self->endpoint;
+ self->endpoint = nm_sock_addr_endpoint_ref (endpoint);
+ nm_sock_addr_endpoint_unref (old);
+}
+
+/**
+ * nm_wireguard_peer_set_endpoint:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @endpoint: the socket address endpoint to set or %NULL.
+ *
+ * Sets or clears the endpoint of @self.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self,
+ const char *endpoint)
+{
+ NMSockAddrEndpoint *old;
+
+ g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
+
+ old = self->endpoint;
+ self->endpoint = endpoint
+ ? nm_sock_addr_endpoint_new (endpoint)
+ : NULL;
+ nm_sock_addr_endpoint_unref (old);
+}
+
+/**
+ * nm_wireguard_peer_get_allowed_ips_len:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: the number of allowed-ips entries.
+ *
+ * Since: 1.16
+ */
+guint
+nm_wireguard_peer_get_allowed_ips_len (const NMWireGuardPeer *self)
+{
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), 0);
+
+ return self->allowed_ips ? self->allowed_ips->len : 0u;
+}
+
+/**
+ * nm_wireguard_peer_get_allowed_ip:
+ * @self: the #NMWireGuardPeer instance
+ * @idx: the index from zero to (allowed-ips-len - 1) to
+ * retrieve.
+ * @out_is_valid: (allow-none): %TRUE if the returned value is a valid allowed-ip
+ * setting.
+ *
+ * Returns: (transfer none): the allowed-ip setting at index @idx.
+ * If @idx is out of range, %NULL will be returned.
+ *
+ * Since: 1.16
+ */
+const char *
+nm_wireguard_peer_get_allowed_ip (const NMWireGuardPeer *self,
+ guint idx,
+ gboolean *out_is_valid)
+{
+ const char *s;
+
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
+
+ if ( !self->allowed_ips
+ || idx >= self->allowed_ips->len) {
+ NM_SET_OUT (out_is_valid, FALSE);
+ return NULL;
+ }
+
+ s = self->allowed_ips->pdata[idx];
+ NM_SET_OUT (out_is_valid, s[0] != ALLOWED_IP_INVALID_X);
+ return s[0] == ALLOWED_IP_INVALID_X ? &s[1] : s;
+}
+
+/**
+ * nm_wireguard_peer_clear_allowed_ips:
+ * @self: the unsealed #NMWireGuardPeer instance
+ *
+ * Removes all allowed-ip entries.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_clear_allowed_ips (NMWireGuardPeer *self)
+{
+ g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
+
+ if (self->allowed_ips)
+ g_ptr_array_set_size (self->allowed_ips, 0);
+}
+
+static gboolean
+_peer_append_allowed_ip (NMWireGuardPeer *self,
+ const char *allowed_ip,
+ gboolean accept_invalid)
+{
+ int addr_family;
+ int prefix;
+ NMIPAddr addrbin;
+ char *str;
+
+ nm_assert (NM_IS_WIREGUARD_PEER (self, FALSE));
+ nm_assert (allowed_ip);
+
+ /* normalize the address (if it is valid. Otherwise, take it
+ * as-is (it will render the instance invalid). */
+ if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC,
+ allowed_ip,
+ &addr_family,
+ &addrbin,
+ &prefix)) {
+ if (!accept_invalid)
+ return FALSE;
+ /* mark the entry as invalid by having a "X" prefix. */
+ str = g_strconcat (ALLOWED_IP_INVALID_X_STR, allowed_ip, NULL);
+ } else {
+ char addrstr[NM_UTILS_INET_ADDRSTRLEN];
+
+ nm_assert_addr_family (addr_family);
+
+ nm_utils_inet_ntop (addr_family, &addrbin, addrstr);
+ if (prefix >= 0)
+ str = g_strdup_printf ("%s/%d", addrstr, prefix);
+ else
+ str = g_strdup (addrstr);
+ nm_assert (str[0] != ALLOWED_IP_INVALID_X);
+ }
+
+ if (!self->allowed_ips)
+ self->allowed_ips = g_ptr_array_new_with_free_func (g_free);
+
+ g_ptr_array_add (self->allowed_ips, str);
+ return TRUE;
+}
+
+/**
+ * nm_wireguard_peer_append_allowed_ip:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @allowed_ip: the allowed-ip entry to set.
+ * @accept_invalid: if %TRUE, also invalid @allowed_ip value
+ * will be appended. Otherwise, the function does nothing
+ * in face of invalid values and returns %FALSE.
+ *
+ * Appends @allowed_ip setting to the list. This does not check
+ * for duplicates and always appends @allowed_ip to the end of the
+ * list. If @allowed_ip is valid, it will be normalized and a modified
+ * for might be appended. If @allowed_ip is invalid, it will still be
+ * appended, but later verification will fail.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Returns: %TRUE if the value is a valid allowed-ips value, %FALSE otherwise.
+ * Depending on @accept_invalid, also invalid values are added.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_append_allowed_ip (NMWireGuardPeer *self,
+ const char *allowed_ip,
+ gboolean accept_invalid)
+{
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
+ g_return_val_if_fail (allowed_ip, FALSE);
+
+ return _peer_append_allowed_ip (self, allowed_ip, accept_invalid);
+}
+
+/**
+ * nm_wireguard_peer_remove_allowed_ip:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @idx: the index from zero to (allowed-ips-len - 1) to
+ * retrieve. If the index is out of range, %FALSE is returned
+ * and nothing is done.
+ *
+ * Removes the allowed-ip at the given @idx. This shifts all
+ * following entries one index down.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Returns: %TRUE if @idx was valid and the allowed-ip was removed.
+ * %FALSE otherwise, and the peer will not be changed.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_remove_allowed_ip (NMWireGuardPeer *self,
+ guint idx)
+{
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
+
+ if ( !self->allowed_ips
+ || idx >= self->allowed_ips->len)
+ return FALSE;
+
+ g_ptr_array_remove_index (self->allowed_ips, idx);
+ return TRUE;
+}
+
+/**
+ * nm_wireguard_peer_is_valid:
+ * @self: the #NMWireGuardPeer instance
+ * @check_secrets: if %TRUE, non-secret properties are validated.
+ * Otherwise they are ignored for this purpose.
+ * @check_non_secrets: if %TRUE, secret properties are validated.
+ * Otherwise they are ignored for this purpose.
+ * @error: the #GError location for returning the failure reason.
+ *
+ * Returns: %TRUE if the peer is valid or fails with an error
+ * reason.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_is_valid (const NMWireGuardPeer *self,
+ gboolean check_non_secrets,
+ gboolean check_secrets,
+ GError **error)
+{
+ guint i;
+
+ g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), FALSE);
+ g_return_val_if_fail (!error || !*error, FALSE);
+
+ if (check_non_secrets) {
+ if (!self->public_key) {
+ g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
+ _("missing public-key for peer"));
+ return FALSE;
+ } else if (!self->public_key_valid) {
+ g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("invalid public-key for peer"));
+ return FALSE;
+ }
+ }
+
+ if (check_secrets) {
+ if ( self->preshared_key
+ && !self->preshared_key_valid) {
+ g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("invalid preshared-key for peer"));
+ return FALSE;
+ }
+ }
+
+ if (check_non_secrets) {
+ if (!_nm_utils_secret_flags_validate (self->preshared_key_flags,
+ NULL,
+ NULL,
+ NM_SETTING_SECRET_FLAG_NONE,
+ error))
+ return FALSE;
+ }
+
+ if (check_non_secrets) {
+ if ( self->endpoint
+ && !nm_sock_addr_endpoint_get_host (self->endpoint)) {
+ g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("invalid endpoint for peer"));
+ return FALSE;
+ }
+
+ if (self->allowed_ips) {
+ for (i = 0; i < self->allowed_ips->len; i++) {
+ const char *s = self->allowed_ips->pdata[i];
+
+ if (s[0] == ALLOWED_IP_INVALID_X) {
+ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("invalid IP address \"%s\" for allowed-ip of peer"),
+ &s[1]);
+ return FALSE;
+ }
+ }
+ }
+
+ if (!_nm_setting_secret_flags_valid (self->preshared_key_flags)) {
+ g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("invalid preshared-key-flags for peer"));
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * nm_wireguard_peer_cmp:
+ * @a: (allow-none): the #NMWireGuardPeer to compare.
+ * @b: (allow-none): the other #NMWireGuardPeer to compare.
+ * @compare_flags: #NMSettingCompareFlags to affect the comparison.
+ *
+ * Returns: zero of the two instances are equivalent or
+ * a non-zero integer otherwise. This defines a total ordering
+ * over the peers. Whether a peer is sealed or not, does not
+ * affect the comparison.
+ *
+ * Since: 1.16
+ */
+int
+nm_wireguard_peer_cmp (const NMWireGuardPeer *a,
+ const NMWireGuardPeer *b,
+ NMSettingCompareFlags compare_flags)
+{
+ guint i, n;
+
+ NM_CMP_SELF (a, b);
+
+ /* regardless of the @compare_flags, the public-key is the ID of the peer. It must
+ * always be compared. */
+ NM_CMP_FIELD_BOOL (a, b, public_key_valid);
+ NM_CMP_FIELD_STR0 (a, b, public_key);
+
+ if (NM_FLAGS_ANY (compare_flags, NM_SETTING_COMPARE_FLAG_INFERRABLE
+ | NM_SETTING_COMPARE_FLAG_FUZZY))
+ return 0;
+
+ NM_CMP_FIELD_BOOL (a, b, endpoint);
+ if (a->endpoint) {
+ NM_CMP_DIRECT_STRCMP0 (nm_sock_addr_endpoint_get_endpoint (a->endpoint),
+ nm_sock_addr_endpoint_get_endpoint (b->endpoint));
+ }
+
+ NM_CMP_FIELD (a, b, persistent_keepalive);
+
+ NM_CMP_DIRECT ((n = (a->allowed_ips ? a->allowed_ips->len : 0u)),
+ ( b->allowed_ips ? b->allowed_ips->len : 0u ));
+ for (i = 0; i < n; i++)
+ NM_CMP_DIRECT_STRCMP0 (a->allowed_ips->pdata[i], b->allowed_ips->pdata[i]);
+
+ NM_CMP_FIELD (a, b, preshared_key_flags);
+
+ if (!NM_FLAGS_HAS (compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS)) {
+ if ( NM_FLAGS_HAS (compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS)
+ && NM_FLAGS_HAS (a->preshared_key_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)) {
+ /* pass */
+ } else if ( NM_FLAGS_HAS (compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)
+ && NM_FLAGS_HAS (a->preshared_key_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED)) {
+ /* pass */
+ } else {
+ NM_CMP_FIELD_BOOL (a, b, preshared_key_valid);
+ NM_CMP_FIELD_STR0 (a, b, preshared_key);
+ }
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ const char *public_key;
+ NMWireGuardPeer *peer;
+ guint idx;
+} PeerData;
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+ PROP_PRIVATE_KEY,
+ PROP_PRIVATE_KEY_FLAGS,
+ PROP_LISTEN_PORT,
+ PROP_FWMARK,
+);
+
+typedef struct {
+ char *private_key;
+ GPtrArray *peers_arr;
+ GHashTable *peers_hash;
+ NMSettingSecretFlags private_key_flags;
+ guint32 fwmark;
+ guint16 listen_port;
+ bool private_key_valid:1;
+} NMSettingWireGuardPrivate;
+
+/**
+ * NMSettingWireGuard:
+ *
+ * WireGuard Ethernet Settings
+ *
+ * Since: 1.16
+ */
+struct _NMSettingWireGuard {
+ NMSetting parent;
+ NMSettingWireGuardPrivate _priv;
+};
+
+struct _NMSettingWireGuardClass {
+ NMSettingClass parent;
+};
+
+G_DEFINE_TYPE (NMSettingWireGuard, nm_setting_wireguard, NM_TYPE_SETTING)
+
+#define NM_SETTING_WIREGUARD_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSettingWireGuard, NM_IS_SETTING_WIREGUARD, NMSetting)
+
+/*****************************************************************************/
+
+#define peers_psk_get_secret_name_a(public_key, to_free) \
+ nm_construct_name_a (NM_SETTING_WIREGUARD_PEERS".%s."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, (public_key), (to_free))
+
+#define peers_psk_get_secret_name_dup(public_key) \
+ g_strdup_printf (NM_SETTING_WIREGUARD_PEERS".%s."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, (public_key))
+
+#define peers_psk_get_secret_parse_a(secret_public_key, public_key_free) \
+ ({ \
+ const char *_secret_public_key = (secret_public_key); \
+ char **_public_key_free = (public_key_free); \
+ const char *_public_key = NULL; \
+ \
+ nm_assert (_public_key_free && !*_public_key_free); \
+ \
+ if (NM_STR_HAS_PREFIX (_secret_public_key, NM_SETTING_WIREGUARD_PEERS".")) { \
+ _secret_public_key += NM_STRLEN (NM_SETTING_WIREGUARD_PEERS"."); \
+ if (NM_STR_HAS_SUFFIX (_secret_public_key, "."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) { \
+ _public_key = nm_strndup_a (300, _secret_public_key, strlen (_secret_public_key) - NM_STRLEN ("."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY), _public_key_free); \
+ } \
+ } \
+ \
+ _public_key; \
+ })
+
+/*****************************************************************************/
+
+/**
+ * nm_setting_wireguard_get_private_key:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: (transfer none): the set private-key or %NULL.
+ *
+ * Since: 1.16
+ */
+const char *
+nm_setting_wireguard_get_private_key (NMSettingWireGuard *self)
+{
+ g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), NULL);
+
+ return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->private_key;
+}
+
+/**
+ * nm_setting_wireguard_get_private_key_flags:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the secret-flags for #NMSettingWireGuard:private-key.
+ *
+ * Since: 1.16
+ */
+NMSettingSecretFlags
+nm_setting_wireguard_get_private_key_flags (NMSettingWireGuard *self)
+{
+ g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
+
+ return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->private_key_flags;
+}
+
+/**
+ * nm_setting_wireguard_get_fwmark:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the set firewall mark.
+ *
+ * Since: 1.16
+ */
+guint32
+nm_setting_wireguard_get_fwmark (NMSettingWireGuard *self)
+{
+ g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
+
+ return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->fwmark;
+}
+
+/**
+ * nm_setting_wireguard_get_listen_port:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the set UDP listen port.
+ *
+ * Since: 1.16
+ */
+guint16
+nm_setting_wireguard_get_listen_port (NMSettingWireGuard *self)
+{
+ g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
+
+ return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->listen_port;
+}
+
+/*****************************************************************************/
+
+static void
+_peer_free (PeerData *pd)
+{
+ nm_assert (pd);
+
+ nm_wireguard_peer_unref (pd->peer);
+ g_slice_free (PeerData, pd);
+}
+
+/*****************************************************************************/
+
+static void
+_peers_notify (gpointer self)
+{
+ _nm_setting_emit_property_changed (self);
+}
+
+static PeerData *
+_peers_get (NMSettingWireGuardPrivate *priv,
+ guint idx)
+{
+ PeerData *pd;
+
+ nm_assert (priv);
+ nm_assert (idx < priv->peers_arr->len);
+
+ pd = priv->peers_arr->pdata[idx];
+
+ nm_assert (pd);
+ nm_assert (pd->idx == idx);
+ nm_assert (NM_IS_WIREGUARD_PEER (pd->peer, TRUE));
+ nm_assert (nm_wireguard_peer_is_sealed (pd->peer));
+ nm_assert (pd->public_key == nm_wireguard_peer_get_public_key (pd->peer));
+ nm_assert (g_hash_table_lookup (priv->peers_hash, pd) == pd);
+
+ return pd;
+}
+
+static PeerData *
+_peers_get_by_public_key (NMSettingWireGuardPrivate *priv,
+ const char *public_key,
+ gboolean try_with_normalized_key)
+{
+ gs_free char *public_key_normalized = NULL;
+ PeerData *pd;
+
+again:
+ nm_assert (priv);
+ nm_assert (public_key);
+
+ pd = g_hash_table_lookup (priv->peers_hash, &public_key);
+ if (pd) {
+ nm_assert (_peers_get (priv, pd->idx) == pd);
+ return pd;
+ }
+ if ( try_with_normalized_key
+ && _nm_utils_wireguard_normalize_key (public_key,
+ NM_WIREGUARD_PUBLIC_KEY_LEN,
+ &public_key_normalized)) {
+ public_key = public_key_normalized;
+ try_with_normalized_key = FALSE;
+ goto again;
+ }
+ return NULL;
+}
+
+static void
+_peers_remove (NMSettingWireGuardPrivate *priv,
+ PeerData *pd,
+ gboolean do_free)
+{
+ guint i;
+
+ nm_assert (pd);
+ nm_assert (_peers_get (priv, pd->idx) == pd);
+
+ for (i = pd->idx + 1; i < priv->peers_arr->len; i++)
+ _peers_get (priv, i)->idx--;
+
+ g_ptr_array_remove_index (priv->peers_arr, pd->idx);
+ if (!g_hash_table_remove (priv->peers_hash, pd))
+ nm_assert_not_reached ();
+ if (do_free)
+ _peer_free (pd);
+}
+
+/**
+ * nm_setting_wireguard_get_peers_len:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the number of registered peers.
+ *
+ * Since: 1.16
+ */
+guint
+nm_setting_wireguard_get_peers_len (NMSettingWireGuard *self)
+{
+ g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
+
+ return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->peers_arr->len;
+}
+
+/**
+ * nm_setting_wireguard_get_peer:
+ * @self: the #NMSettingWireGuard instance
+ * @idx: the index to lookup.
+ *
+ * Returns: (transfer none): the #NMWireGuardPeer entry at
+ * index @idx. If the index is out of range, %NULL is returned.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_setting_wireguard_get_peer (NMSettingWireGuard *self,
+ guint idx)
+{
+ NMSettingWireGuardPrivate *priv;
+
+ g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), NULL);
+
+ priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
+
+ if (idx >= priv->peers_arr->len)
+ return NULL;
+
+ return _peers_get (priv, idx)->peer;
+}
+
+/**
+ * nm_setting_wireguard_get_peer_by_public_key:
+ * @self: the #NMSettingWireGuard instance
+ * @public_key: the public key for looking up the
+ * peer.
+ * @out_idx: (out): (allow-none): optional output argument
+ * for the index of the found peer. If no index is found,
+ * this is set to the nm_setting_wireguard_get_peers_len().
+ *
+ * Returns: (transfer none): the #NMWireGuardPeer instance with a
+ * matching public key. If no such peer exists, %NULL is returned.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_setting_wireguard_get_peer_by_public_key (NMSettingWireGuard *self,
+ const char *public_key,
+ guint *out_idx)
+{
+ NMSettingWireGuardPrivate *priv;
+ PeerData *pd;
+
+ g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), NULL);
+ g_return_val_if_fail (public_key, NULL);
+
+ priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
+
+ pd = _peers_get_by_public_key (priv, public_key, TRUE);
+ if (!pd) {
+ NM_SET_OUT (out_idx, priv->peers_arr->len);
+ return NULL;
+ }
+ NM_SET_OUT (out_idx, pd->idx);
+ return pd->peer;
+}
+
+static gboolean
+_peers_set (NMSettingWireGuardPrivate *priv,
+ NMWireGuardPeer *peer,
+ guint idx,
+ gboolean check_same_key)
+{
+ PeerData *pd_same_key = NULL;
+ PeerData *pd_idx = NULL;
+ const char *public_key;
+
+ nm_assert (idx <= priv->peers_arr->len);
+
+ public_key = nm_wireguard_peer_get_public_key (peer);
+
+ if (idx < priv->peers_arr->len) {
+ pd_idx = _peers_get (priv, idx);
+
+ if (pd_idx->peer == peer)
+ return FALSE;
+
+ if ( check_same_key
+ && nm_streq (public_key, nm_wireguard_peer_get_public_key (pd_idx->peer)))
+ check_same_key = FALSE;
+ }
+
+ nm_wireguard_peer_seal (peer);
+ nm_wireguard_peer_ref (peer);
+
+ if (check_same_key) {
+ pd_same_key = _peers_get_by_public_key (priv, public_key, FALSE);
+ if (pd_same_key) {
+ if (pd_idx) {
+ nm_assert (pd_same_key != pd_idx);
+ _peers_remove (priv, pd_same_key, TRUE);
+ pd_same_key = NULL;
+ } else {
+ if ( pd_same_key->peer == peer
+ && pd_same_key->idx == priv->peers_arr->len - 1) {
+ nm_wireguard_peer_unref (peer);
+ return FALSE;
+ }
+ _peers_remove (priv, pd_same_key, FALSE);
+ nm_wireguard_peer_unref (pd_same_key->peer);
+ }
+ }
+ } else
+ nm_assert (_peers_get_by_public_key (priv, public_key, FALSE) == pd_idx);
+
+ if (pd_idx) {
+ g_hash_table_remove (priv->peers_hash, pd_idx);
+ nm_wireguard_peer_unref (pd_idx->peer);
+ pd_idx->public_key = public_key;
+ pd_idx->peer = peer;
+ g_hash_table_add (priv->peers_hash, pd_idx);
+ return TRUE;
+ }
+
+
+ if (!pd_same_key)
+ pd_same_key = g_slice_new (PeerData);
+
+ *pd_same_key = (PeerData) {
+ .peer = peer,
+ .public_key = public_key,
+ .idx = priv->peers_arr->len,
+ };
+
+ g_ptr_array_add (priv->peers_arr, pd_same_key);
+ if (!nm_g_hash_table_add (priv->peers_hash, pd_same_key))
+ nm_assert_not_reached ();
+
+ nm_assert (_peers_get (priv, pd_same_key->idx) == pd_same_key);
+
+ return TRUE;
+}
+
+static gboolean
+_peers_append (NMSettingWireGuardPrivate *priv,
+ NMWireGuardPeer *peer,
+ gboolean check_same_key)
+{
+ return _peers_set (priv, peer, priv->peers_arr->len, check_same_key);
+}
+
+/**
+ * nm_setting_wireguard_set_peer:
+ * @self: the #NMSettingWireGuard instance
+ * @peer: the #NMWireGuardPeer instance to set.
+ * This seals @peer and keeps a reference on the
+ * instance.
+ * @idx: the index, in the range of 0 to the number of
+ * peers (including). That means, if @idx is one past
+ * the end of the number of peers, this is the same as
+ * nm_setting_wireguard_append_peer(). Otherwise, the
+ * peer at this index is replaced.
+ *
+ * If @idx is one past the last peer, the behavior is the same
+ * as nm_setting_wireguard_append_peer().
+ * Otherwise, the peer will be at @idx and replace the peer
+ * instance at that index. Note that if a peer with the same
+ * public-key exists on another index, then that peer will also
+ * be replaced. In that case, the number of peers will shrink
+ * by one (because the one at @idx got replace and then one
+ * with the same public-key got removed). This also means,
+ * that the resulting index afterwards may be one less than
+ * @idx (if another peer with a lower index was dropped).
+ *
+ * Since: 1.16
+ */
+void
+nm_setting_wireguard_set_peer (NMSettingWireGuard *self,
+ NMWireGuardPeer *peer,
+ guint idx)
+{
+ NMSettingWireGuardPrivate *priv;
+
+ g_return_if_fail (NM_IS_SETTING_WIREGUARD (self));
+ g_return_if_fail (NM_IS_WIREGUARD_PEER (peer, TRUE));
+
+ priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
+
+ g_return_if_fail (idx <= priv->peers_arr->len);
+
+ if (_peers_set (priv, peer, idx, TRUE))
+ _peers_notify (self);
+}
+
+/**
+ * nm_setting_wireguard_append_peer:
+ * @self: the #NMSettingWireGuard instance
+ * @peer: the #NMWireGuardPeer instance to append.
+ * This seals @peer and keeps a reference on the
+ * instance.
+ *
+ * If a peer with the same public-key already exists, that
+ * one is replaced by @peer. The new @peer is always appended
+ * (or moved to) the end, so in case a peer is replaced, the
+ * indexes are shifted and the number of peers stays unchanged.
+ *
+ * Since: 1.16
+ */
+void
+nm_setting_wireguard_append_peer (NMSettingWireGuard *self,
+ NMWireGuardPeer *peer)
+{
+ g_return_if_fail (NM_IS_SETTING_WIREGUARD (self));
+ g_return_if_fail (NM_IS_WIREGUARD_PEER (peer, TRUE));
+
+ if (_peers_append (NM_SETTING_WIREGUARD_GET_PRIVATE (self),
+ peer,
+ TRUE))
+ _peers_notify (self);
+}
+
+/**
+ * nm_setting_wireguard_remove_peer
+ * @self: the #NMSettingWireGuard instance
+ * @idx: the index to remove.
+ *
+ * Returns: %TRUE if @idx was in range and a peer
+ * was removed. Otherwise, @self is unchanged.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_setting_wireguard_remove_peer (NMSettingWireGuard *self,
+ guint idx)
+{
+ NMSettingWireGuardPrivate *priv;
+
+ g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), FALSE);
+
+ priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
+
+ if (idx >= priv->peers_arr->len)
+ return FALSE;
+
+ _peers_remove (priv, _peers_get (priv, idx), TRUE);
+ _peers_notify (self);
+ return TRUE;
+}
+
+static guint
+_peers_clear (NMSettingWireGuardPrivate *priv)
+{
+ guint l;
+
+ l = priv->peers_arr->len;
+ while (priv->peers_arr->len > 0) {
+ _peers_remove (priv,
+ _peers_get (priv, priv->peers_arr->len - 1),
+ TRUE);
+ }
+ return l;
+}
+
+/**
+ * nm_setting_wireguard_:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the number of cleared peers.
+ *
+ * Since: 1.16
+ */
+guint
+nm_setting_wireguard_clear_peers (NMSettingWireGuard *self)
+{
+ guint l;
+
+ g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
+
+ l = _peers_clear (NM_SETTING_WIREGUARD_GET_PRIVATE (self));
+ if (l > 0)
+ _peers_notify (self);
+ return l;
+}
+
+/*****************************************************************************/
+
+static GVariant *
+_peers_dbus_only_synth (const NMSettInfoSetting *sett_info,
+ guint property_idx,
+ NMConnection *connection,
+ NMSetting *setting,
+ NMConnectionSerializationFlags flags)
+{
+ NMSettingWireGuard *self = NM_SETTING_WIREGUARD (setting);
+ NMSettingWireGuardPrivate *priv;
+ gboolean any_peers = FALSE;
+ GVariantBuilder peers_builder;
+ guint i_peer, n_peers;
+ guint i;
+
+ n_peers = nm_setting_wireguard_get_peers_len (self);
+ if (n_peers == 0)
+ return NULL;
+
+ priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
+
+ for (i_peer = 0; i_peer < n_peers; i_peer++) {
+ const NMWireGuardPeer *peer = _peers_get (priv, i_peer)->peer;
+ GVariantBuilder builder;
+
+ if (!peer->public_key)
+ continue;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+
+ g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, g_variant_new_string (peer->public_key));
+
+ if ( !NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS)
+ && peer->endpoint)
+ g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_ENDPOINT, g_variant_new_string (nm_sock_addr_endpoint_get_endpoint (peer->endpoint)));
+
+ if ( !NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_NO_SECRETS)
+ && peer->preshared_key)
+ g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, g_variant_new_string (peer->preshared_key));
+
+ if ( !NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS)
+ && peer->preshared_key_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
+ g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, g_variant_new_uint32 (peer->preshared_key_flags));
+
+ if ( !NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS)
+ && peer->persistent_keepalive != 0)
+ g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, g_variant_new_uint32 (peer->persistent_keepalive));
+
+ if ( !NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS)
+ && peer->allowed_ips
+ && peer->allowed_ips->len > 0) {
+ const char *const*strv = (const char *const*) peer->allowed_ips->pdata;
+ gs_free const char **strv_fixed = NULL;
+
+ for (i = 0; i < peer->allowed_ips->len; i++) {
+ if (strv[i][0] != ALLOWED_IP_INVALID_X)
+ continue;
+ if (!strv_fixed) {
+ strv_fixed = nm_memdup (strv, sizeof (strv[0]) * peer->allowed_ips->len);
+ strv = strv_fixed;
+ }
+ ((const char **) strv)[i]++;
+ }
+ g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS,
+ g_variant_new_strv (strv, peer->allowed_ips->len));
+ }
+
+ if (!any_peers) {
+ g_variant_builder_init (&peers_builder, G_VARIANT_TYPE ("aa{sv}"));
+ any_peers = TRUE;
+ }
+ g_variant_builder_add (&peers_builder, "a{sv}", &builder);
+ }
+
+ return any_peers
+ ? g_variant_builder_end (&peers_builder)
+ : NULL;
+}
+
+static gboolean
+_peers_dbus_only_set (NMSetting *setting,
+ GVariant *connection_dict,
+ const char *property,
+ GVariant *value,
+ NMSettingParseFlags parse_flags,
+ GError **error)
+{
+ GVariantIter iter_peers;
+ GVariant *peer_var;
+ guint i_peer;
+ gboolean success = FALSE;
+ gboolean peers_changed = FALSE;
+
+ nm_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}")));
+
+ g_variant_iter_init (&iter_peers, value);
+
+ i_peer = 0;
+ while (g_variant_iter_next (&iter_peers, "@a{sv}", &peer_var)) {
+ _nm_unused gs_unref_variant GVariant *peer_var_unref = peer_var;
+ nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
+ const char *cstr;
+ guint32 u32;
+ GVariant *var;
+
+ i_peer++;
+
+ if (!g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, "&s", &cstr)) {
+ if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
+ _("peer #%u has no public-key"),
+ i_peer);
+ goto out;
+ }
+ continue;
+ }
+
+ peer = nm_wireguard_peer_new ();
+ nm_wireguard_peer_set_public_key (peer, cstr);
+ if (!peer->public_key_valid) {
+ if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
+ _("peer #%u has invalid public-key"),
+ i_peer);
+ goto out;
+ }
+ continue;
+ }
+
+ if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_ENDPOINT, "&s", &cstr)) {
+ nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL;
+
+ ep = nm_sock_addr_endpoint_new (cstr);
+ if (!nm_sock_addr_endpoint_get_host (ep)) {
+ if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
+ _("peer #%u has invalid endpoint"),
+ i_peer);
+ goto out;
+ }
+ } else
+ _nm_wireguard_peer_set_endpoint (peer, ep);
+ }
+
+ if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, "&s", &cstr))
+ nm_wireguard_peer_set_preshared_key (peer, cstr);
+
+ if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, "u", &u32))
+ nm_wireguard_peer_set_preshared_key_flags (peer, u32);
+
+ if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, "u", &u32))
+ nm_wireguard_peer_set_persistent_keepalive (peer, u32);
+
+ if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, "@as", &var)) {
+ _nm_unused gs_unref_variant GVariant *var_free = var;
+ gs_free const char **allowed_ips = NULL;
+ gsize i, l;
+
+ allowed_ips = g_variant_get_strv (var, &l);
+ if (allowed_ips) {
+ for (i = 0; i < l; i++) {
+ if (_peer_append_allowed_ip (peer, allowed_ips[i], FALSE))
+ continue;
+ if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
+ continue;
+ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
+ _("peer #%u has invalid allowed-ips setting"),
+ i_peer);
+ goto out;
+ }
+ }
+ }
+
+ if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+ gs_free_error GError *local = NULL;
+
+ if (!nm_wireguard_peer_is_valid (peer, TRUE, FALSE, &local)) {
+ g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
+ _("peer #%u is invalid: %s"),
+ i_peer, local->message);
+ goto out;
+ }
+ }
+
+ /* we could easily reject duplicate peers (by public-key) or duplicate GVariant attributes.
+ * However, don't do that. In case of duplicate values, the latter peer overwrite the earlier
+ * and GVariant attributes are ignored by g_variant_lookup() above. */
+ if (_peers_append (NM_SETTING_WIREGUARD_GET_PRIVATE (setting),
+ peer,
+ TRUE))
+ peers_changed = TRUE;
+ }
+
+ success = TRUE;
+
+out:
+ if (peers_changed)
+ _peers_notify (setting);
+ return success;
+}
+
+/*****************************************************************************/
+
+static gboolean
+verify (NMSetting *setting, NMConnection *connection, GError **error)
+{
+ NMSettingWireGuard *s_wg = NM_SETTING_WIREGUARD (setting);
+ NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+ guint i;
+
+ if (!_nm_connection_verify_required_interface_name (connection, error))
+ return FALSE;
+
+ if (!_nm_utils_secret_flags_validate (nm_setting_wireguard_get_private_key_flags (s_wg),
+ NM_SETTING_WIREGUARD_SETTING_NAME,
+ NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS,
+ NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
+ error))
+ return FALSE;
+
+ for (i = 0; i < priv->peers_arr->len; i++) {
+ NMWireGuardPeer *peer = _peers_get (priv, i)->peer;
+
+ if (!nm_wireguard_peer_is_valid (peer, TRUE, FALSE, error)) {
+ g_prefix_error (error,
+ "%s.%s[%u]: ",
+ NM_SETTING_WIREGUARD_SETTING_NAME,
+ NM_SETTING_WIREGUARD_PEERS,
+ i);
+ return FALSE;
+ }
+ }
+
+ if (connection) {
+ NMSettingIPConfig *s_ip4;
+ NMSettingIPConfig *s_ip6;
+ const char *method;
+
+ /* WireGuard is Layer 3 only. For the moment, we only support a restricted set of
+ * IP methods. We may relax that later, once we fix the implementations so they
+ * actually work. */
+
+ if ( (s_ip4 = nm_connection_get_setting_ip4_config (connection))
+ && (method = nm_setting_ip_config_get_method (s_ip4))
+ && !NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
+ NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
+ g_set_error (error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("method \"%s\" is not supported for WireGuard"),
+ method);
+ g_prefix_error (error, "%s.%s: ", NM_SETTING_IP4_CONFIG_SETTING_NAME, NM_SETTING_IP_CONFIG_METHOD);
+ return FALSE;
+ }
+
+ if ( (s_ip6 = nm_connection_get_setting_ip6_config (connection))
+ && (method = nm_setting_ip_config_get_method (s_ip6))
+ && !NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+ NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
+ NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
+ g_set_error (error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("method \"%s\" is not supported for WireGuard"),
+ method);
+ g_prefix_error (error, "%s.%s: ", NM_SETTING_IP6_CONFIG_SETTING_NAME, NM_SETTING_IP_CONFIG_METHOD);
+ return FALSE;
+ }
+ }
+
+ /* private-key is a secret, hence we cannot verify it like a regular property. */
+ return TRUE;
+}
+
+static gboolean
+verify_secrets (NMSetting *setting, NMConnection *connection, GError **error)
+{
+ NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+ guint i;
+
+ if ( priv->private_key
+ && !priv->private_key_valid) {
+ g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+ _("key must be 32 bytes base64 encoded"));
+ g_prefix_error (error, "%s.%s: ", NM_SETTING_WIREGUARD_SETTING_NAME, NM_SETTING_WIREGUARD_PRIVATE_KEY);
+ return FALSE;
+ }
+
+ for (i = 0; i < priv->peers_arr->len; i++) {
+ NMWireGuardPeer *peer = _peers_get (priv, i)->peer;
+
+ if (!nm_wireguard_peer_is_valid (peer, FALSE, TRUE, error)) {
+ g_prefix_error (error,
+ "%s.%s[%u]: ",
+ NM_SETTING_WIREGUARD_SETTING_NAME,
+ NM_SETTING_WIREGUARD_PEERS,
+ i);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static GPtrArray *
+need_secrets (NMSetting *setting)
+{
+ NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+ GPtrArray *secrets = NULL;
+ guint i;
+
+ if ( !priv->private_key
+ || !priv->private_key_valid) {
+ secrets = g_ptr_array_new_full (1, g_free);
+ g_ptr_array_add (secrets, g_strdup (NM_SETTING_WIREGUARD_PRIVATE_KEY));
+ }
+
+ for (i = 0; i < priv->peers_arr->len; i++) {
+ NMWireGuardPeer *peer = _peers_get (priv, i)->peer;
+
+ if (NM_FLAGS_HAS (peer->preshared_key_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED))
+ continue;
+
+ if (peer->preshared_key_valid)
+ continue;
+
+ if (!peer->public_key_valid)
+ continue;
+
+ if (!secrets)
+ secrets = g_ptr_array_new_full (1, g_free);
+ g_ptr_array_add (secrets, peers_psk_get_secret_name_dup (peer->public_key));
+ }
+
+ return secrets;
+}
+
+static gboolean
+clear_secrets (const NMSettInfoSetting *sett_info,
+ guint property_idx,
+ NMSetting *setting,
+ NMSettingClearSecretsWithFlagsFn func,
+ gpointer user_data)
+{
+ if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_WIREGUARD_PEERS)) {
+ NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+ gboolean peers_changed = FALSE;
+ guint i, j;
+
+ j = 0;
+ for (i = 0; i < priv->peers_arr->len; i++) {
+ NMWireGuardPeer *peer = _peers_get (priv, i)->peer;
+
+ if (!peer->preshared_key)
+ continue;
+
+ if (func) {
+ gs_free char *name_free = NULL;
+ const char *name;
+
+ /* only stack-allocate (alloca) a few times. */
+ if (j++ < 5)
+ name = peers_psk_get_secret_name_a (peer->public_key, &name_free);
+ else {
+ name_free = peers_psk_get_secret_name_dup (peer->public_key);
+ name = name_free;
+ }
+
+ if (!func (setting, name, peer->preshared_key_flags, user_data))
+ continue;
+ }
+
+ {
+ nm_auto_unref_wgpeer NMWireGuardPeer *peer2 = NULL;
+
+ peer2 = nm_wireguard_peer_new_clone (peer, FALSE);
+
+ if (_peers_set (priv, peer2, i, FALSE))
+ peers_changed = TRUE;
+ }
+ }
+
+ if (peers_changed)
+ _peers_notify (setting);
+ return peers_changed;
+ }
+
+ return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->clear_secrets (sett_info,
+ property_idx,
+ setting,
+ func,
+ user_data);
+}
+
+static int
+update_one_secret (NMSetting *setting,
+ const char *key,
+ GVariant *value,
+ GError **error)
+{
+ NMSettingWireGuard *self = NM_SETTING_WIREGUARD (setting);
+ NMSettingWireGuardPrivate *priv;
+ gboolean has_changes = FALSE;
+ gboolean has_error = FALSE;
+ GVariantIter iter_peers;
+ GVariant *peer_var;
+ guint i_peer;
+
+ if (!nm_streq (key, NM_SETTING_WIREGUARD_PEERS)) {
+ return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->update_one_secret (setting,
+ key,
+ value,
+ error);
+ }
+
+ if (!g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}"))) {
+ g_set_error_literal (error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+ _("invalid peer secrets"));
+ g_prefix_error (error, "%s.%s: ", NM_SETTING_WIREGUARD_SETTING_NAME, NM_SETTING_WIREGUARD_PEERS);
+ return NM_SETTING_UPDATE_SECRET_ERROR;
+ }
+
+ priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
+
+ g_variant_iter_init (&iter_peers, value);
+
+ i_peer = 0;
+ while (g_variant_iter_next (&iter_peers, "@a{sv}", &peer_var)) {
+ _nm_unused gs_unref_variant GVariant *peer_var_unref = peer_var;
+ PeerData *pd;
+ NMWireGuardPeer *peer;
+ const char *cstr;
+
+ i_peer++;
+
+ if (!g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, "&s", &cstr)) {
+ if (!has_error) {
+ g_set_error (error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+ _("peer #%u lacks public-key"),
+ i_peer - 1);
+ g_prefix_error (error, "%s.%s: ", NM_SETTING_WIREGUARD_SETTING_NAME, NM_SETTING_WIREGUARD_PEERS);
+ has_error = TRUE;
+ }
+ continue;
+ }
+
+ pd = _peers_get_by_public_key (priv, cstr, TRUE);
+ if (!pd) {
+ if (!has_error) {
+ g_set_error (error,
+ NM_CONNECTION_ERROR,
+ NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+ _("non-existing peer '%s'"),
+ cstr);
+ g_prefix_error (error, "%s.%s: ", NM_SETTING_WIREGUARD_SETTING_NAME, NM_SETTING_WIREGUARD_PEERS);
+ has_error = TRUE;
+ }
+ continue;
+ }
+
+ if (!g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, "&s", &cstr)) {
+ /* no preshared-key. Ignore the rest.
+ *
+ * In particular, we don't reject all unknown fields. */
+ continue;
+ }
+
+ if (nm_streq0 (cstr, nm_wireguard_peer_get_preshared_key (pd->peer)))
+ continue;
+
+ peer = nm_wireguard_peer_new_clone (pd->peer, FALSE);
+ nm_wireguard_peer_set_preshared_key (peer, cstr);
+
+ if (!_peers_set (priv, peer, pd->idx, FALSE))
+ nm_assert_not_reached ();
+ has_changes = TRUE;
+ }
+
+ if (has_error)
+ return NM_SETTING_UPDATE_SECRET_ERROR;
+ if (has_changes)
+ return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED;
+ return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED;
+}
+
+static NMTernary
+compare_property (const NMSettInfoSetting *sett_info,
+ guint property_idx,
+ NMSetting *setting,
+ NMSetting *other,
+ NMSettingCompareFlags flags)
+{
+ NMSettingWireGuardPrivate *a_priv;
+ NMSettingWireGuardPrivate *b_priv;
+ guint i;
+
+ if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_WIREGUARD_PEERS)) {
+
+ if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE))
+ return NM_TERNARY_DEFAULT;
+
+ if (!other)
+ return TRUE;
+
+ a_priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+ b_priv = NM_SETTING_WIREGUARD_GET_PRIVATE (other);
+
+ if (a_priv->peers_arr->len != b_priv->peers_arr->len)
+ return FALSE;
+ for (i = 0; i < a_priv->peers_arr->len; i++) {
+ NMWireGuardPeer *a_peer = _peers_get (a_priv, i)->peer;
+ NMWireGuardPeer *b_peer = _peers_get (b_priv, i)->peer;
+
+ if (nm_wireguard_peer_cmp (a_peer,
+ b_peer,
+ flags) != 0)
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->compare_property (sett_info,
+ property_idx,
+ setting,
+ other,
+ flags);
+}
+
+static void
+duplicate_copy_properties (const NMSettInfoSetting *sett_info,
+ NMSetting *src,
+ NMSetting *dst)
+{
+ NMSettingWireGuardPrivate *priv_src = NM_SETTING_WIREGUARD_GET_PRIVATE (src);
+ NMSettingWireGuardPrivate *priv_dst = NM_SETTING_WIREGUARD_GET_PRIVATE (dst);
+ guint i;
+ gboolean peers_changed = FALSE;
+
+ NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->duplicate_copy_properties (sett_info,
+ src,
+ dst);
+
+ /* We don't bother comparing the existing peers with what we are about to set.
+ * Always reset all. */
+ if (_peers_clear (priv_dst) > 0)
+ peers_changed = TRUE;
+ for (i = 0; i < priv_src->peers_arr->len; i++) {
+ if (_peers_append (priv_dst,
+ _peers_get (priv_src, i)->peer,
+ FALSE))
+ peers_changed = TRUE;
+ }
+ if (peers_changed)
+ _peers_notify (dst);
+}
+
+static void
+enumerate_values (const NMSettInfoProperty *property_info,
+ NMSetting *setting,
+ NMSettingValueIterFn func,
+ gpointer user_data)
+{
+ if (nm_streq (property_info->name, NM_SETTING_WIREGUARD_PEERS)) {
+ NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+ nm_auto_unset_gvalue GValue value = G_VALUE_INIT;
+ GPtrArray *ptr = NULL;
+ guint i;
+
+ if (priv->peers_arr && priv->peers_arr->len > 0) {
+ ptr = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_wireguard_peer_unref);
+ for (i = 0; i < priv->peers_arr->len; i++)
+ g_ptr_array_add (ptr, nm_wireguard_peer_ref (_peers_get (priv, i)->peer));
+ }
+ g_value_init (&value, G_TYPE_PTR_ARRAY);
+ g_value_take_boxed (&value, ptr);
+ func (setting,
+ property_info->name,
+ &value,
+ 0,
+ user_data);
+ return;
+ }
+
+ NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->enumerate_values (property_info,
+ setting,
+ func,
+ user_data);
+}
+
+static gboolean
+aggregate (NMSetting *setting,
+ int type_i,
+ gpointer arg)
+{
+ NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+ NMConnectionAggregateType type = type_i;
+ NMSettingSecretFlags secret_flags;
+ guint i;
+
+ nm_assert (NM_IN_SET (type, NM_CONNECTION_AGGREGATE_ANY_SECRETS,
+ NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS));
+
+ switch (type) {
+
+ case NM_CONNECTION_AGGREGATE_ANY_SECRETS:
+ if (priv->private_key)
+ goto out_done;
+ for (i = 0; i < priv->peers_arr->len; i++) {
+ if (nm_wireguard_peer_get_preshared_key (_peers_get (priv, i)->peer))
+ goto out_done;
+ }
+ break;
+
+ case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS:
+#if NM_MORE_ASSERTS
+ if (!nm_setting_get_secret_flags (setting, NM_SETTING_WIREGUARD_PRIVATE_KEY, &secret_flags, NULL))
+ nm_assert_not_reached ();
+ nm_assert (secret_flags == priv->private_key_flags);
+#endif
+ if (priv->private_key_flags == NM_SETTING_SECRET_FLAG_NONE)
+ goto out_done;
+ for (i = 0; i < priv->peers_arr->len; i++) {
+ secret_flags = nm_wireguard_peer_get_preshared_key_flags (_peers_get (priv, i)->peer);
+ if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
+ goto out_done;
+ }
+ break;
+ }
+
+ return FALSE;
+
+out_done:
+ *((gboolean *) arg) = TRUE;
+ return TRUE;
+}
+
+static gboolean
+get_secret_flags (NMSetting *setting,
+ const char *secret_name,
+ NMSettingSecretFlags *out_flags,
+ GError **error)
+{
+ if (NM_STR_HAS_PREFIX (secret_name, NM_SETTING_WIREGUARD_PEERS".")) {
+ NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+ gs_free char *public_key_free = NULL;
+ const char *public_key;
+ PeerData *pd;
+
+ public_key = peers_psk_get_secret_parse_a (secret_name, &public_key_free);
+ if ( public_key
+ && (pd = _peers_get_by_public_key (priv, public_key, FALSE))) {
+ NM_SET_OUT (out_flags, nm_wireguard_peer_get_preshared_key_flags (pd->peer));
+ return TRUE;
+ }
+ }
+
+ return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->get_secret_flags (setting,
+ secret_name,
+ out_flags,
+ error);
+}
+
+static gboolean
+set_secret_flags (NMSetting *setting,
+ const char *secret_name,
+ NMSettingSecretFlags flags,
+ GError **error)
+{
+ if (NM_STR_HAS_PREFIX (secret_name, NM_SETTING_WIREGUARD_PEERS".")) {
+ NMSettingWireGuard *self = NM_SETTING_WIREGUARD (setting);
+ NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
+ gs_free char *public_key_free = NULL;
+ const char *public_key;
+ PeerData *pd;
+
+ public_key = peers_psk_get_secret_parse_a (secret_name, &public_key_free);
+ if ( public_key
+ && (pd = _peers_get_by_public_key (priv, public_key, FALSE))) {
+
+ if (nm_wireguard_peer_get_preshared_key_flags (pd->peer) != flags) {
+ nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
+
+ peer = nm_wireguard_peer_new_clone (pd->peer, TRUE);
+ peer->preshared_key_flags = flags;
+ if (_peers_set (priv, peer, pd->idx, FALSE))
+ _peers_notify (self);
+ }
+
+ return TRUE;
+ }
+ }
+
+ return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->set_secret_flags (setting,
+ secret_name,
+ flags,
+ error);
+}
+
+static void
+for_each_secret (NMSetting *setting,
+ const char *data_key,
+ GVariant *data_val,
+ gboolean remove_non_secrets,
+ _NMConnectionForEachSecretFunc callback,
+ gpointer callback_data,
+ GVariantBuilder *setting_builder)
+{
+ NMSettingWireGuard *s_wg;
+ NMSettingWireGuardPrivate *priv;
+ GVariantBuilder peers_builder;
+ GVariantIter *peer_iter;
+ GVariantIter data_iter;
+ const char *key;
+
+ if (!nm_streq (data_key, NM_SETTING_WIREGUARD_PEERS)) {
+ NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->for_each_secret (setting,
+ data_key,
+ data_val,
+ remove_non_secrets,
+ callback,
+ callback_data,
+ setting_builder);
+ return;
+ }
+
+ if (!g_variant_is_of_type (data_val, G_VARIANT_TYPE ("aa{sv}"))) {
+ /* invalid type. Silently ignore content as we cannot find secret-keys
+ * here. */
+ return;
+ }
+
+ s_wg = NM_SETTING_WIREGUARD (setting);
+ priv = NM_SETTING_WIREGUARD_GET_PRIVATE (s_wg);
+
+ g_variant_builder_init (&peers_builder, G_VARIANT_TYPE ("aa{sv}"));
+ g_variant_iter_init (&data_iter, data_val);
+ while (g_variant_iter_next (&data_iter, "a{sv}", &peer_iter)) {
+ _nm_unused nm_auto_free_variant_iter GVariantIter *peer_iter_free = peer_iter;
+ gs_unref_variant GVariant *preshared_key = NULL;
+ PeerData *pd = NULL;
+ NMSettingSecretFlags secret_flags;
+ GVariant *val;
+ GVariantBuilder peer_builder;
+
+ g_variant_builder_init (&peer_builder, G_VARIANT_TYPE ("a{sv}"));
+
+ while (g_variant_iter_next (peer_iter, "{&sv}", &key, &val)) {
+ _nm_unused gs_unref_variant GVariant *val_free = val;
+
+ if (nm_streq (key, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) {
+ if ( !preshared_key
+ && g_variant_is_of_type (val, G_VARIANT_TYPE_STRING))
+ preshared_key = g_variant_ref (val);
+ continue;
+ }
+
+ if (nm_streq (key, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY)) {
+ if ( !pd
+ && g_variant_is_of_type (val, G_VARIANT_TYPE_STRING))
+ pd = _peers_get_by_public_key (priv, g_variant_get_string (val, NULL), TRUE);
+ } else if (remove_non_secrets)
+ continue;
+
+ g_variant_builder_add (&peer_builder, "{sv}", key, val);
+ }
+
+ if (pd && preshared_key) {
+ /* without specifying a public-key of an existing peer, the secret is
+ * ignored. */
+ secret_flags = nm_wireguard_peer_get_preshared_key_flags (pd->peer);
+ if (callback (secret_flags, callback_data))
+ g_variant_builder_add (&peer_builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, preshared_key);
+ }
+
+ g_variant_builder_add (&peers_builder, "a{sv}", &peer_builder);
+ }
+
+ g_variant_builder_add (setting_builder,
+ "{sv}",
+ NM_SETTING_WIREGUARD_PEERS,
+ g_variant_builder_end (&peers_builder));
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ NMSettingWireGuard *setting = NM_SETTING_WIREGUARD (object);
+ NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+
+ switch (prop_id) {
+ case PROP_PRIVATE_KEY:
+ g_value_set_string (value, priv->private_key);
+ break;
+ case PROP_PRIVATE_KEY_FLAGS:
+ g_value_set_flags (value, priv->private_key_flags);
+ break;
+ case PROP_LISTEN_PORT:
+ g_value_set_uint (value, priv->listen_port);
+ break;
+ case PROP_FWMARK:
+ g_value_set_uint (value, priv->fwmark);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (object);
+ const char *str;
+
+ switch (prop_id) {
+ case PROP_PRIVATE_KEY:
+ nm_clear_pointer (&priv->private_key, nm_free_secret);
+ str = g_value_get_string (value);
+ if (str) {
+ if (_nm_utils_wireguard_normalize_key (str,
+ NM_WIREGUARD_PUBLIC_KEY_LEN,
+ &priv->private_key))
+ priv->private_key_valid = TRUE;
+ else {
+ priv->private_key = g_strdup (str);
+ priv->private_key_valid = FALSE;
+ }
+ }
+ break;
+ case PROP_PRIVATE_KEY_FLAGS:
+ priv->private_key_flags = g_value_get_flags (value);
+ break;
+ case PROP_LISTEN_PORT:
+ priv->listen_port = g_value_get_uint (value);
+ break;
+ case PROP_FWMARK:
+ priv->fwmark = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/*****************************************************************************/
+
+static void
+nm_setting_wireguard_init (NMSettingWireGuard *setting)
+{
+ NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+
+ priv->peers_arr = g_ptr_array_new ();
+ priv->peers_hash = g_hash_table_new (nm_pstr_hash, nm_pstr_equal);
+}
+
+/**
+ * nm_setting_wireguard_new:
+ *
+ * Creates a new #NMSettingWireGuard object with default values.
+ *
+ * Returns: (transfer full): the new empty #NMSettingWireGuard object
+ *
+ * Since: 1.16
+ **/
+NMSetting *
+nm_setting_wireguard_new (void)
+{
+ return g_object_new (NM_TYPE_SETTING_WIREGUARD, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+ NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (object);
+
+ nm_free_secret (priv->private_key);
+
+ _peers_clear (priv);
+ g_ptr_array_unref (priv->peers_arr);
+ g_hash_table_unref (priv->peers_hash);
+
+ G_OBJECT_CLASS (nm_setting_wireguard_parent_class)->finalize (object);
+}
+
+static void
+nm_setting_wireguard_class_init (NMSettingWireGuardClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ NMSettingClass *setting_class = NM_SETTING_CLASS (klass);
+ GArray *properties_override = _nm_sett_info_property_override_create_array ();
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->finalize = finalize;
+
+ setting_class->verify = verify;
+ setting_class->verify_secrets = verify_secrets;
+ setting_class->need_secrets = need_secrets;
+ setting_class->clear_secrets = clear_secrets;
+ setting_class->update_one_secret = update_one_secret;
+ setting_class->compare_property = compare_property;
+ setting_class->duplicate_copy_properties = duplicate_copy_properties;
+ setting_class->enumerate_values = enumerate_values;
+ setting_class->aggregate = aggregate;
+ setting_class->get_secret_flags = get_secret_flags;
+ setting_class->set_secret_flags = set_secret_flags;
+ setting_class->for_each_secret = for_each_secret;
+
+ /**
+ * NMSettingWireGuard:private-key:
+ *
+ * The 256 bit private-key in base64 encoding.
+ *
+ * Since: 1.16
+ **/
+ obj_properties[PROP_PRIVATE_KEY] =
+ g_param_spec_string (NM_SETTING_WIREGUARD_PRIVATE_KEY, "", "",
+ NULL,
+ G_PARAM_READWRITE
+ | NM_SETTING_PARAM_SECRET
+ | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * NMSettingWireGuard:private-key-flags:
+ *
+ * Flags indicating how to handle the #NMSettingWirelessSecurity:private-key
+ * property.
+ *
+ * Since: 1.16
+ **/
+ obj_properties[PROP_PRIVATE_KEY_FLAGS] =
+ g_param_spec_flags (NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS, "", "",
+ NM_TYPE_SETTING_SECRET_FLAGS,
+ NM_SETTING_SECRET_FLAG_NONE,
+ G_PARAM_READWRITE
+ | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * NMSettingWireGuard:fwmark:
+ *
+ * The use of fwmark is optional and is by default off. Setting it to 0
+ * disables it. Otherwise it is a 32-bit fwmark for outgoing packets.
+ *
+ * Since: 1.16
+ **/
+ obj_properties[PROP_FWMARK] =
+ g_param_spec_uint (NM_SETTING_WIREGUARD_FWMARK, "", "",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_READWRITE
+ | NM_SETTING_PARAM_INFERRABLE
+ | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * NMSettingWireGuard:listen-port:
+ *
+ * The listen-port. If listen-port is not specified, the port will be chosen
+ * randomly when the interface comes up.
+ *
+ * Since: 1.16
+ **/
+ obj_properties[PROP_LISTEN_PORT] =
+ g_param_spec_uint (NM_SETTING_WIREGUARD_LISTEN_PORT, "", "",
+ 0, 65535, 0,
+ G_PARAM_READWRITE
+ | NM_SETTING_PARAM_INFERRABLE
+ | G_PARAM_STATIC_STRINGS);
+
+ /* ---dbus---
+ * property: peers
+ * format: array of 'a{sv}'
+ * description: Array of dictionaries for the WireGuard peers.
+ * ---end---
+ */
+ _properties_override_add_dbus_only (properties_override,
+ NM_SETTING_WIREGUARD_PEERS,
+ G_VARIANT_TYPE ("aa{sv}"),
+ _peers_dbus_only_synth,
+ _peers_dbus_only_set);
+
+ g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+ _nm_setting_class_commit_full (setting_class, NM_META_SETTING_TYPE_WIREGUARD, NULL, properties_override);
+}
diff --git a/libnm-core/nm-setting-wireguard.h b/libnm-core/nm-setting-wireguard.h
new file mode 100644
index 0000000000..3810aa3048
--- /dev/null
+++ b/libnm-core/nm-setting-wireguard.h
@@ -0,0 +1,201 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 - 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_SETTING_WIREGUARD_H__
+#define __NM_SETTING_WIREGUARD_H__
+
+#if !defined (__NETWORKMANAGER_H_INSIDE__) && !defined (NETWORKMANAGER_COMPILATION)
+#error "Only <NetworkManager.h> can be included directly."
+#endif
+
+#include "nm-setting.h"
+#include "nm-utils.h"
+
+G_BEGIN_DECLS
+
+/*****************************************************************************/
+
+#define NM_WIREGUARD_PUBLIC_KEY_LEN 32
+#define NM_WIREGUARD_SYMMETRIC_KEY_LEN 32
+
+/*****************************************************************************/
+
+typedef struct _NMWireGuardPeer NMWireGuardPeer;
+
+NM_AVAILABLE_IN_1_16
+GType nm_wireguard_peer_get_type (void);
+
+NM_AVAILABLE_IN_1_16
+NMWireGuardPeer *nm_wireguard_peer_new (void);
+
+NM_AVAILABLE_IN_1_16
+NMWireGuardPeer *nm_wireguard_peer_new_clone (const NMWireGuardPeer *self,
+ gboolean with_secrets);
+
+NM_AVAILABLE_IN_1_16
+NMWireGuardPeer *nm_wireguard_peer_ref (NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+void nm_wireguard_peer_unref (NMWireGuardPeer *self);
+
+NM_AVAILABLE_IN_1_16
+void nm_wireguard_peer_seal (NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+gboolean nm_wireguard_peer_is_sealed (const NMWireGuardPeer *self);
+
+NM_AVAILABLE_IN_1_16
+const char *nm_wireguard_peer_get_public_key (const NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+void nm_wireguard_peer_set_public_key (NMWireGuardPeer *self,
+ const char *public_key);
+
+NM_AVAILABLE_IN_1_16
+const char *nm_wireguard_peer_get_preshared_key (const NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+void nm_wireguard_peer_set_preshared_key (NMWireGuardPeer *self,
+ const char *preshared_key);
+
+NM_AVAILABLE_IN_1_16
+NMSettingSecretFlags nm_wireguard_peer_get_preshared_key_flags (const NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+void nm_wireguard_peer_set_preshared_key_flags (NMWireGuardPeer *self,
+ NMSettingSecretFlags preshared_key_flags);
+
+NM_AVAILABLE_IN_1_16
+guint16 nm_wireguard_peer_get_persistent_keepalive (const NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+void nm_wireguard_peer_set_persistent_keepalive (NMWireGuardPeer *self,
+ guint16 persistent_keepalive);
+
+NM_AVAILABLE_IN_1_16
+const char *nm_wireguard_peer_get_endpoint (const NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+void nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self,
+ const char *endpoint);
+
+NM_AVAILABLE_IN_1_16
+guint nm_wireguard_peer_get_allowed_ips_len (const NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+const char *nm_wireguard_peer_get_allowed_ip (const NMWireGuardPeer *self,
+ guint idx,
+ gboolean *out_is_valid);
+NM_AVAILABLE_IN_1_16
+void nm_wireguard_peer_clear_allowed_ips (NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+gboolean nm_wireguard_peer_append_allowed_ip (NMWireGuardPeer *self,
+ const char *allowed_ip,
+ gboolean accept_invalid);
+NM_AVAILABLE_IN_1_16
+gboolean nm_wireguard_peer_remove_allowed_ip (NMWireGuardPeer *self,
+ guint idx);
+
+NM_AVAILABLE_IN_1_16
+gboolean nm_wireguard_peer_is_valid (const NMWireGuardPeer *self,
+ gboolean check_non_secrets,
+ gboolean check_secrets,
+ GError **error);
+
+NM_AVAILABLE_IN_1_16
+int nm_wireguard_peer_cmp (const NMWireGuardPeer *a,
+ const NMWireGuardPeer *b,
+ NMSettingCompareFlags compare_flags);
+
+/*****************************************************************************/
+
+#define NM_TYPE_SETTING_WIREGUARD (nm_setting_wireguard_get_type ())
+#define NM_SETTING_WIREGUARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTING_WIREGUARD, NMSettingWireGuard))
+#define NM_SETTING_WIREGUARD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTING_WIREGUARD, NMSettingWireGuardClass))
+#define NM_IS_SETTING_WIREGUARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SETTING_WIREGUARD))
+#define NM_IS_SETTING_WIREGUARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SETTING_WIREGUARD))
+#define NM_SETTING_WIREGUARD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SETTING_WIREGUARD, NMSettingWireGuardClass))
+
+#define NM_SETTING_WIREGUARD_SETTING_NAME "wireguard"
+
+#define NM_SETTING_WIREGUARD_PRIVATE_KEY "private-key"
+#define NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS "private-key-flags"
+#define NM_SETTING_WIREGUARD_LISTEN_PORT "listen-port"
+#define NM_SETTING_WIREGUARD_FWMARK "fwmark"
+
+#define NM_SETTING_WIREGUARD_PEERS "peers"
+
+#define NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY "public-key"
+#define NM_WIREGUARD_PEER_ATTR_ENDPOINT "endpoint"
+#define NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY "preshared-key"
+#define NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS "preshared-key-flags"
+#define NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS "allowed-ips"
+#define NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE "persistent-keepalive"
+
+/*****************************************************************************/
+
+typedef struct _NMSettingWireGuardClass NMSettingWireGuardClass;
+
+NM_AVAILABLE_IN_1_16
+GType nm_setting_wireguard_get_type (void);
+
+NM_AVAILABLE_IN_1_16
+NMSetting *nm_setting_wireguard_new (void);
+
+/*****************************************************************************/
+
+NM_AVAILABLE_IN_1_16
+const char *nm_setting_wireguard_get_private_key (NMSettingWireGuard *self);
+
+NM_AVAILABLE_IN_1_16
+NMSettingSecretFlags nm_setting_wireguard_get_private_key_flags (NMSettingWireGuard *self);
+
+NM_AVAILABLE_IN_1_16
+guint16 nm_setting_wireguard_get_listen_port (NMSettingWireGuard *self);
+
+NM_AVAILABLE_IN_1_16
+guint32 nm_setting_wireguard_get_fwmark (NMSettingWireGuard *self);
+
+/*****************************************************************************/
+
+NM_AVAILABLE_IN_1_16
+guint nm_setting_wireguard_get_peers_len (NMSettingWireGuard *self);
+
+NM_AVAILABLE_IN_1_16
+NMWireGuardPeer *nm_setting_wireguard_get_peer (NMSettingWireGuard *self,
+ guint idx);
+
+NM_AVAILABLE_IN_1_16
+NMWireGuardPeer *nm_setting_wireguard_get_peer_by_public_key (NMSettingWireGuard *self,
+ const char *public_key,
+ guint *out_idx);
+
+NM_AVAILABLE_IN_1_16
+void nm_setting_wireguard_set_peer (NMSettingWireGuard *self,
+ NMWireGuardPeer *peer,
+ guint idx);
+
+NM_AVAILABLE_IN_1_16
+void nm_setting_wireguard_append_peer (NMSettingWireGuard *self,
+ NMWireGuardPeer *peer);
+
+NM_AVAILABLE_IN_1_16
+gboolean nm_setting_wireguard_remove_peer (NMSettingWireGuard *self,
+ guint idx);
+
+NM_AVAILABLE_IN_1_16
+guint nm_setting_wireguard_clear_peers (NMSettingWireGuard *self);
+
+/*****************************************************************************/
+
+G_END_DECLS
+
+#endif /* __NM_SETTING_WIREGUARD_H__ */
diff --git a/libnm-core/nm-setting.c b/libnm-core/nm-setting.c
index 269f83175c..065aad2b3d 100644
--- a/libnm-core/nm-setting.c
+++ b/libnm-core/nm-setting.c
@@ -2067,7 +2067,7 @@ _nm_setting_update_secrets (NMSetting *setting, GVariant *secrets, GError **erro
int success;
success = NM_SETTING_GET_CLASS (setting)->update_one_secret (setting, secret_key, secret_value, &tmp_error);
- g_assert (!((success == NM_SETTING_UPDATE_SECRET_ERROR) ^ (!!tmp_error)));
+ nm_assert (!((success == NM_SETTING_UPDATE_SECRET_ERROR) ^ (!!tmp_error)));
g_variant_unref (secret_value);
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c
index cd354d036e..fd9f87f879 100644
--- a/libnm-core/nm-utils.c
+++ b/libnm-core/nm-utils.c
@@ -38,6 +38,8 @@
#endif
#include "nm-utils/nm-enum-utils.h"
+#include "nm-utils/nm-secret-utils.h"
+#include "systemd/nm-sd-utils-shared.h"
#include "nm-common-macros.h"
#include "nm-utils-private.h"
#include "nm-setting-private.h"
@@ -6809,3 +6811,76 @@ nm_utils_version (void)
return NM_VERSION;
}
+/*****************************************************************************/
+
+/**
+ * _nm_utils_wireguard_decode_key:
+ * @base64_key: the (possibly invalid) base64 encode key.
+ * @required_key_len: the expected (binary) length of the key after
+ * decoding. If the length does not match, the validation fails.
+ * @out_key: (allow-none): an optional output buffer for the binary
+ * key. If given, it will be filled with exactly @required_key_len
+ * bytes.
+ *
+ * Returns: %TRUE if the input key is a valid base64 encoded key
+ * with @required_key_len bytes.
+ */
+gboolean
+_nm_utils_wireguard_decode_key (const char *base64_key,
+ gsize required_key_len,
+ guint8 *out_key)
+{
+ gs_free guint8 *bin_arr = NULL;
+ gsize base64_key_len;
+ gsize bin_len;
+ int r;
+
+ if (!base64_key)
+ return FALSE;
+
+ base64_key_len = strlen (base64_key);
+
+ r = nm_sd_utils_unbase64mem (base64_key, base64_key_len, &bin_arr, &bin_len);
+ if (r < 0)
+ return FALSE;
+ if (bin_len != required_key_len) {
+ nm_explicit_bzero (bin_arr, bin_len);
+ return FALSE;
+ }
+
+ if (nm_utils_memeqzero (bin_arr, required_key_len)) {
+ /* an all zero key is not valid either. That is used to represet an unset key */
+ return FALSE;
+ }
+
+ if (out_key)
+ memcpy (out_key, bin_arr, required_key_len);
+
+ nm_explicit_bzero (bin_arr, bin_len);
+ return TRUE;
+}
+
+gboolean
+_nm_utils_wireguard_normalize_key (const char *base64_key,
+ gsize required_key_len,
+ char **out_base64_key_norm)
+{
+ gs_free guint8 *buf_free = NULL;
+ guint8 buf_static[200];
+ guint8 *buf;
+
+ if (required_key_len > sizeof (buf_static)) {
+ buf_free = g_new (guint8, required_key_len);
+ buf = buf_free;
+ } else
+ buf = buf_static;
+
+ if (!_nm_utils_wireguard_decode_key (base64_key, required_key_len, buf)) {
+ NM_SET_OUT (out_base64_key_norm, NULL);
+ return FALSE;
+ }
+
+ NM_SET_OUT (out_base64_key_norm, g_base64_encode (buf, required_key_len));
+ nm_explicit_bzero (buf, required_key_len);
+ return TRUE;
+}
diff --git a/libnm-core/tests/test-setting.c b/libnm-core/tests/test-setting.c
index 4518c55e5d..e59bc4668c 100644
--- a/libnm-core/tests/test-setting.c
+++ b/libnm-core/tests/test-setting.c
@@ -115,6 +115,30 @@ _connection_new_from_dbus_strict (GVariant *dict,
/*****************************************************************************/
+static char *
+_create_random_ipaddr (int addr_family, gboolean as_service)
+{
+ char delimiter = as_service ? ':' : '/';
+ int num;
+
+ if (addr_family == AF_UNSPEC)
+ addr_family = nmtst_rand_select (AF_INET, AF_INET6);
+
+ g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+
+ if (as_service)
+ num = (nmtst_get_rand_int () % 1000) + 30000;
+ else
+ num = addr_family == AF_INET ? 32 : 128;
+
+ if (addr_family == AF_INET)
+ return g_strdup_printf ("192.168.%u.%u%c%d", nmtst_get_rand_int () % 256, nmtst_get_rand_int () % 256, delimiter, num);
+ else
+ return g_strdup_printf ("a:b:c::%02x:%02x%c%d", nmtst_get_rand_int () % 256, nmtst_get_rand_int () % 256, delimiter, num);
+}
+
+/*****************************************************************************/
+
static void
compare_blob_data (const char *test,
const char *key_path,
@@ -2013,6 +2037,235 @@ test_tc_config_dbus (void)
/*****************************************************************************/
+static GPtrArray *
+_rndt_wg_peers_create (void)
+{
+ GPtrArray *wg_peers;
+ guint i, n;
+
+ wg_peers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_wireguard_peer_unref);
+
+ n = nmtst_get_rand_int () % 10;
+ for (i = 0; i < n; i++) {
+ NMWireGuardPeer *peer;
+ guint8 public_key_buf[NM_WIREGUARD_PUBLIC_KEY_LEN];
+ guint8 preshared_key_buf[NM_WIREGUARD_SYMMETRIC_KEY_LEN];
+ gs_free char *public_key = NULL;
+ gs_free char *preshared_key = NULL;
+ gs_free char *s_endpoint = NULL;
+ guint i_aip, n_aip;
+
+ /* we don't bother to create a valid curve25519 public key. Of course, libnm cannot
+ * check whether the public key is bogus or not. Hence, for our purpose a random
+ * bogus key is good enough. */
+ public_key = g_base64_encode (nmtst_rand_buf (NULL, public_key_buf, sizeof (public_key_buf)), sizeof (public_key_buf));
+
+ preshared_key = g_base64_encode (nmtst_rand_buf (NULL, preshared_key_buf, sizeof (preshared_key_buf)), sizeof (preshared_key_buf));
+
+ s_endpoint = _create_random_ipaddr (AF_UNSPEC, TRUE);
+
+ peer = nm_wireguard_peer_new ();
+ nm_wireguard_peer_set_public_key (peer, public_key);
+
+ nm_wireguard_peer_set_preshared_key (peer, nmtst_rand_select (NULL, preshared_key));
+
+ nm_wireguard_peer_set_preshared_key_flags (peer, nmtst_rand_select (NM_SETTING_SECRET_FLAG_NONE,
+ NM_SETTING_SECRET_FLAG_NOT_SAVED,
+ NM_SETTING_SECRET_FLAG_AGENT_OWNED));
+
+ nm_wireguard_peer_set_persistent_keepalive (peer,
+ nmtst_rand_select ((guint32) 0, nmtst_get_rand_int ()));
+
+ nm_wireguard_peer_set_endpoint (peer, nmtst_rand_select (s_endpoint, NULL));
+
+ n_aip = nmtst_rand_select (0, nmtst_get_rand_int () % 10);
+ for (i_aip = 0; i_aip < n_aip; i_aip++) {
+ gs_free char *aip = NULL;
+
+ aip = _create_random_ipaddr (AF_UNSPEC, FALSE);
+ if (!nm_wireguard_peer_append_allowed_ip (peer, aip, FALSE))
+ g_assert_not_reached ();
+ }
+
+ g_assert (nm_wireguard_peer_is_valid (peer, TRUE, TRUE, NULL));
+
+ nm_wireguard_peer_seal (peer);
+ g_ptr_array_add (wg_peers, peer);
+ }
+
+ return wg_peers;
+}
+
+static const char *
+_rndt_wg_peers_to_keyfile (GPtrArray *wg_peers,
+ gboolean strict,
+ char **out_str)
+{
+ nm_auto_free_gstring GString *gstr = NULL;
+ nm_auto_free_gstring GString *gstr_aip = NULL;
+ guint i, j;
+
+ g_assert (wg_peers);
+ g_assert (out_str && !*out_str);
+
+ nm_gstring_prepare (&gstr);
+ for (i = 0; i < wg_peers->len; i++) {
+ const NMWireGuardPeer *peer = wg_peers->pdata[i];
+ gs_free char *s_endpoint = NULL;
+ gs_free char *s_preshared_key = NULL;
+ gs_free char *s_preshared_key_flags = NULL;
+ gs_free char *s_persistent_keepalive = NULL;
+ gs_free char *s_allowed_ips = NULL;
+
+ if (nm_wireguard_peer_get_endpoint (peer))
+ s_endpoint = g_strdup_printf ("endpoint=%s\n", nm_wireguard_peer_get_endpoint (peer));
+ else if (!strict)
+ s_endpoint = g_strdup_printf ("endpoint=\n");
+
+ if ( nm_wireguard_peer_get_preshared_key (peer)
+ || !strict) {
+ if (nm_wireguard_peer_get_preshared_key_flags (peer) == NM_SETTING_SECRET_FLAG_NONE)
+ s_preshared_key = g_strdup_printf ("preshared-key=%s\n", nm_wireguard_peer_get_preshared_key (peer) ?: "");
+ }
+
+ if ( nm_wireguard_peer_get_preshared_key_flags (peer) != NM_SETTING_SECRET_FLAG_NOT_REQUIRED
+ || !strict)
+ s_preshared_key_flags = g_strdup_printf ("preshared-key-flags=%d\n", (int) nm_wireguard_peer_get_preshared_key_flags (peer));
+
+ if ( nm_wireguard_peer_get_persistent_keepalive (peer) != 0
+ || !strict)
+ s_persistent_keepalive = g_strdup_printf ("persistent-keepalive=%u\n", nm_wireguard_peer_get_persistent_keepalive (peer));
+
+ if ( nm_wireguard_peer_get_allowed_ips_len (peer) > 0
+ || !strict) {
+ nm_gstring_prepare (&gstr_aip);
+ for (j = 0; j < nm_wireguard_peer_get_allowed_ips_len (peer); j++)
+ g_string_append_printf (gstr_aip, "%s;", nm_wireguard_peer_get_allowed_ip (peer, j, NULL));
+ s_allowed_ips = g_strdup_printf ("allowed-ips=%s\n", gstr_aip->str);
+ }
+
+ if ( !s_endpoint
+ && !s_preshared_key
+ && !s_preshared_key_flags
+ && !s_persistent_keepalive
+ && !s_allowed_ips)
+ s_endpoint = g_strdup_printf ("endpoint=\n");
+
+ g_string_append_printf (gstr,
+ "\n"
+ "[wireguard-peer.%s]\n"
+ "%s" /* endpoint */
+ "%s" /* preshared-key */
+ "%s" /* preshared-key-flags */
+ "%s" /* persistent-keepalive */
+ "%s" /* allowed-ips */
+ "",
+ nm_wireguard_peer_get_public_key (peer),
+ s_endpoint ?: "",
+ s_preshared_key ?: "",
+ s_preshared_key_flags ?: "",
+ s_persistent_keepalive ?: "",
+ s_allowed_ips ?: "");
+ }
+
+ return (*out_str = g_string_free (g_steal_pointer (&gstr), FALSE));
+}
+
+static void
+_rndt_wg_peers_assert_equal (NMSettingWireGuard *s_wg,
+ GPtrArray *peers,
+ gboolean consider_persistent_secrets,
+ gboolean consider_all_secrets,
+ gboolean expect_no_secrets)
+{
+ guint i;
+
+ g_assert (NM_IS_SETTING_WIREGUARD (s_wg));
+ g_assert (peers);
+
+ g_assert_cmpint (peers->len, ==, nm_setting_wireguard_get_peers_len (s_wg));
+
+ for (i = 0; i < peers->len; i++) {
+ const NMWireGuardPeer *a = peers->pdata[i];
+ const NMWireGuardPeer *b = nm_setting_wireguard_get_peer (s_wg, i);
+ gboolean consider_secrets;
+
+ g_assert (a);
+ g_assert (b);
+
+ g_assert_cmpint (nm_wireguard_peer_cmp (a, b, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS), ==, 0);
+
+ if ( consider_all_secrets
+ || !nm_wireguard_peer_get_preshared_key (a))
+ consider_secrets = TRUE;
+ else if (nm_wireguard_peer_get_preshared_key (b))
+ consider_secrets = TRUE;
+ else if ( consider_persistent_secrets
+ && nm_wireguard_peer_get_preshared_key_flags (b) == NM_SETTING_SECRET_FLAG_NONE)
+ consider_secrets = TRUE;
+ else
+ consider_secrets = FALSE;
+
+ if (consider_secrets) {
+ g_assert_cmpstr (nm_wireguard_peer_get_preshared_key (a), ==, nm_wireguard_peer_get_preshared_key (b));
+ g_assert_cmpint (nm_wireguard_peer_cmp (a, b, NM_SETTING_COMPARE_FLAG_EXACT), ==, 0);
+ }
+
+ if (expect_no_secrets)
+ g_assert_cmpstr (nm_wireguard_peer_get_preshared_key (b), ==, NULL);
+ }
+}
+
+static void
+_rndt_wg_peers_fix_secrets (NMSettingWireGuard *s_wg,
+ GPtrArray *peers)
+{
+ guint i;
+
+ g_assert (NM_IS_SETTING_WIREGUARD (s_wg));
+ g_assert (peers);
+
+ g_assert_cmpint (peers->len, ==, nm_setting_wireguard_get_peers_len (s_wg));
+
+ for (i = 0; i < peers->len; i++) {
+ const NMWireGuardPeer *a = peers->pdata[i];
+ const NMWireGuardPeer *b = nm_setting_wireguard_get_peer (s_wg, i);
+ nm_auto_unref_wgpeer NMWireGuardPeer *b_clone = NULL;
+
+ g_assert (a);
+ g_assert (b);
+
+ g_assert_cmpint (nm_wireguard_peer_get_preshared_key_flags (a), ==, nm_wireguard_peer_get_preshared_key_flags (b));
+ g_assert_cmpint (nm_wireguard_peer_cmp (a, b, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS), ==, 0);
+
+ if (!nm_streq0 (nm_wireguard_peer_get_preshared_key (a),
+ nm_wireguard_peer_get_preshared_key (b))) {
+ g_assert_cmpstr (nm_wireguard_peer_get_preshared_key (a), !=, NULL);
+ g_assert_cmpstr (nm_wireguard_peer_get_preshared_key (b), ==, NULL);
+ g_assert (NM_IN_SET (nm_wireguard_peer_get_preshared_key_flags (a), NM_SETTING_SECRET_FLAG_AGENT_OWNED,
+ NM_SETTING_SECRET_FLAG_NOT_SAVED));
+ b_clone = nm_wireguard_peer_new_clone (b, TRUE);
+ nm_wireguard_peer_set_preshared_key (b_clone, nm_wireguard_peer_get_preshared_key (a));
+ nm_setting_wireguard_set_peer (s_wg, b_clone, i);
+ b = nm_setting_wireguard_get_peer (s_wg, i);
+ g_assert (b == b_clone);
+ } else {
+ if (nm_wireguard_peer_get_preshared_key (a)) {
+ g_assert (NM_IN_SET (nm_wireguard_peer_get_preshared_key_flags (a), NM_SETTING_SECRET_FLAG_NONE,
+ NM_SETTING_SECRET_FLAG_NOT_REQUIRED));
+ } else {
+ g_assert (NM_IN_SET (nm_wireguard_peer_get_preshared_key_flags (a), NM_SETTING_SECRET_FLAG_AGENT_OWNED,
+ NM_SETTING_SECRET_FLAG_NONE,
+ NM_SETTING_SECRET_FLAG_NOT_SAVED,
+ NM_SETTING_SECRET_FLAG_NOT_REQUIRED));
+ }
+ }
+
+ g_assert_cmpstr (nm_wireguard_peer_get_preshared_key (a), ==, nm_wireguard_peer_get_preshared_key (b));
+ g_assert_cmpint (nm_wireguard_peer_cmp (a, b, NM_SETTING_COMPARE_FLAG_EXACT), ==, 0);
+ }
+}
+
static void
test_roundtrip_conversion (gconstpointer test_data)
{
@@ -2022,7 +2275,18 @@ test_roundtrip_conversion (gconstpointer test_data)
const char *INTERFACE_NAME = nm_sprintf_bufa (100, "ifname%d", MODE);
guint32 ETH_MTU = nmtst_rand_select ((guint32) 0u,
nmtst_get_rand_int ());
+ const char *WG_PRIVATE_KEY = nmtst_get_rand_bool ()
+ ? "yGXGK+5bVnxSJUejH4vbpXbq+ZtaG4NB8IHRK/aVtE0="
+ : NULL;
+ const NMSettingSecretFlags WG_PRIVATE_KEY_FLAGS = nmtst_rand_select (NM_SETTING_SECRET_FLAG_NONE,
+ NM_SETTING_SECRET_FLAG_NOT_SAVED,
+ NM_SETTING_SECRET_FLAG_AGENT_OWNED);
+ const guint WG_LISTEN_PORT = nmtst_rand_select (0u,
+ nmtst_get_rand_int () % 0x10000);
+ const guint WG_FWMARK = nmtst_rand_select (0u,
+ nmtst_get_rand_int ());
gs_unref_ptrarray GPtrArray *kf_data_arr = g_ptr_array_new_with_free_func (g_free);
+ gs_unref_ptrarray GPtrArray *wg_peers = NULL;
const NMConnectionSerializationFlags dbus_serialization_flags[] = {
NM_CONNECTION_SERIALIZE_ALL,
NM_CONNECTION_SERIALIZE_NO_SECRETS,
@@ -2031,9 +2295,12 @@ test_roundtrip_conversion (gconstpointer test_data)
guint dbus_serialization_flags_idx;
gs_unref_object NMConnection *con = NULL;
gs_free_error GError *error = NULL;
+ gs_free char *tmp_str = NULL;
guint kf_data_idx;
NMSettingConnection *s_con = NULL;
NMSettingWired *s_eth = NULL;
+ NMSettingWireGuard *s_wg = NULL;
+ guint i;
switch (MODE) {
case 0:
@@ -2110,6 +2377,116 @@ test_roundtrip_conversion (gconstpointer test_data)
break;
+ case 1:
+ con = nmtst_create_minimal_connection (ID, UUID, "wireguard", &s_con);
+ g_object_set (s_con,
+ NM_SETTING_CONNECTION_INTERFACE_NAME,
+ INTERFACE_NAME,
+ NULL);
+ nmtst_connection_normalize (con);
+
+ s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (con, NM_TYPE_SETTING_WIREGUARD));
+
+ g_ptr_array_add (kf_data_arr,
+ g_strdup_printf ("[connection]\n"
+ "id=%s\n"
+ "uuid=%s\n"
+ "type=wireguard\n"
+ "interface-name=%s\n"
+ "permissions=\n"
+ "\n"
+ "[ipv4]\n"
+ "dns-search=\n"
+ "method=disabled\n"
+ "\n"
+ "[ipv6]\n"
+ "addr-gen-mode=stable-privacy\n"
+ "dns-search=\n"
+ "method=ignore\n"
+ "",
+ ID,
+ UUID,
+ INTERFACE_NAME));
+ break;
+
+ case 2:
+ con = nmtst_create_minimal_connection (ID, UUID, "wireguard", &s_con);
+ g_object_set (s_con,
+ NM_SETTING_CONNECTION_INTERFACE_NAME,
+ INTERFACE_NAME,
+ NULL);
+ nmtst_connection_normalize (con);
+
+ s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (con, NM_TYPE_SETTING_WIREGUARD));
+ g_object_set (s_wg,
+ NM_SETTING_WIREGUARD_PRIVATE_KEY,
+ WG_PRIVATE_KEY,
+ NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS,
+ WG_PRIVATE_KEY_FLAGS,
+ NM_SETTING_WIREGUARD_LISTEN_PORT,
+ WG_LISTEN_PORT,
+ NM_SETTING_WIREGUARD_FWMARK,
+ WG_FWMARK,
+ NULL);
+
+ wg_peers = _rndt_wg_peers_create ();
+
+ for (i = 0; i < wg_peers->len; i++)
+ nm_setting_wireguard_append_peer (s_wg, wg_peers->pdata[i]);
+
+ nm_clear_g_free (&tmp_str);
+
+ g_ptr_array_add (kf_data_arr,
+ g_strdup_printf ("[connection]\n"
+ "id=%s\n"
+ "uuid=%s\n"
+ "type=wireguard\n"
+ "interface-name=%s\n"
+ "permissions=\n"
+ "%s" /* [wireguard] */
+ "%s" /* fwmark */
+ "%s" /* listen-port */
+ "%s" /* private-key-flags */
+ "%s" /* private-key */
+ "%s" /* [wireguard-peers*] */
+ "\n"
+ "[ipv4]\n"
+ "dns-search=\n"
+ "method=disabled\n"
+ "\n"
+ "[ipv6]\n"
+ "addr-gen-mode=stable-privacy\n"
+ "dns-search=\n"
+ "method=ignore\n"
+ "",
+ ID,
+ UUID,
+ INTERFACE_NAME,
+ ( ( (WG_FWMARK != 0)
+ || (WG_LISTEN_PORT != 0)
+ || (WG_PRIVATE_KEY_FLAGS != NM_SETTING_SECRET_FLAG_NONE)
+ || ( WG_PRIVATE_KEY
+ && WG_PRIVATE_KEY_FLAGS == NM_SETTING_SECRET_FLAG_NONE))
+ ? "\n[wireguard]\n"
+ : ""),
+ ( (WG_FWMARK != 0)
+ ? nm_sprintf_bufa (100, "fwmark=%u\n", WG_FWMARK)
+ : ""),
+ ( (WG_LISTEN_PORT != 0)
+ ? nm_sprintf_bufa (100, "listen-port=%u\n", WG_LISTEN_PORT)
+ : ""),
+ ( (WG_PRIVATE_KEY_FLAGS != NM_SETTING_SECRET_FLAG_NONE)
+ ? nm_sprintf_bufa (100, "private-key-flags=%u\n", (guint) WG_PRIVATE_KEY_FLAGS)
+ : ""),
+ ( ( WG_PRIVATE_KEY
+ && WG_PRIVATE_KEY_FLAGS == NM_SETTING_SECRET_FLAG_NONE)
+ ? nm_sprintf_bufa (100, "private-key=%s\n", WG_PRIVATE_KEY)
+ : ""),
+ _rndt_wg_peers_to_keyfile (wg_peers, TRUE, &tmp_str)));
+
+ _rndt_wg_peers_assert_equal (s_wg, wg_peers, TRUE, TRUE, FALSE);
+ break;
+
default:
g_assert_not_reached ();
}
@@ -2131,6 +2508,7 @@ test_roundtrip_conversion (gconstpointer test_data)
/* check that reading any of kf_data_arr yields the same result that we expect. */
for (kf_data_idx = 0; kf_data_idx < kf_data_arr->len; kf_data_idx++) {
gs_unref_object NMConnection *con2 = NULL;
+ NMSettingWireGuard *s_wg2 = NULL;
NMSettingWired *s_eth2 = NULL;
con2 = nmtst_create_connection_from_keyfile (kf_data_arr->pdata[kf_data_idx], "/no/where/file.nmconnection");
@@ -2159,6 +2537,35 @@ test_roundtrip_conversion (gconstpointer test_data)
g_assert_cmpint (nm_setting_wired_get_mtu (s_eth), ==, ETH_MTU);
g_assert_cmpint (nm_setting_wired_get_mtu (s_eth2), ==, ETH_MTU);
break;
+
+ case 1:
+ s_wg2 = NM_SETTING_WIREGUARD (nm_connection_get_setting (con2, NM_TYPE_SETTING_WIREGUARD));
+ g_assert (NM_IS_SETTING_WIREGUARD (s_wg2));
+
+ g_assert_cmpstr (nm_setting_wireguard_get_private_key (s_wg), ==, NULL);
+ g_assert_cmpstr (nm_setting_wireguard_get_private_key (s_wg2), ==, NULL);
+ break;
+
+ case 2:
+ s_wg2 = NM_SETTING_WIREGUARD (nm_connection_get_setting (con2, NM_TYPE_SETTING_WIREGUARD));
+ g_assert (NM_IS_SETTING_WIREGUARD (s_wg2));
+
+ /* the private key was lost due to the secret-flags. Patch it. */
+ if (WG_PRIVATE_KEY_FLAGS != NM_SETTING_SECRET_FLAG_NONE) {
+ g_assert_cmpstr (nm_setting_wireguard_get_private_key (s_wg2), ==, NULL);
+ g_object_set (s_wg2,
+ NM_SETTING_WIREGUARD_PRIVATE_KEY,
+ WG_PRIVATE_KEY,
+ NULL);
+ }
+
+ g_assert_cmpstr (nm_setting_wireguard_get_private_key (s_wg), ==, WG_PRIVATE_KEY);
+ g_assert_cmpstr (nm_setting_wireguard_get_private_key (s_wg2), ==, WG_PRIVATE_KEY);
+
+ _rndt_wg_peers_assert_equal (s_wg2, wg_peers, TRUE, FALSE, FALSE);
+ _rndt_wg_peers_fix_secrets (s_wg2, wg_peers);
+ _rndt_wg_peers_assert_equal (s_wg2, wg_peers, TRUE, TRUE, FALSE);
+ break;
}
nmtst_assert_connection_equals (con, nmtst_get_rand_bool (), con2, nmtst_get_rand_bool ());
@@ -2168,6 +2575,7 @@ test_roundtrip_conversion (gconstpointer test_data)
NMConnectionSerializationFlags flag = dbus_serialization_flags[dbus_serialization_flags_idx];
gs_unref_variant GVariant *con_var = NULL;
gs_unref_object NMConnection *con2 = NULL;
+ NMSettingWireGuard *s_wg2 = NULL;
con_var = nm_connection_to_dbus (con, flag);
g_assert (g_variant_is_of_type (con_var, NM_VARIANT_TYPE_CONNECTION));
@@ -2186,6 +2594,21 @@ test_roundtrip_conversion (gconstpointer test_data)
nmtst_keyfile_assert_data (kf, kf_data_arr->pdata[0], -1);
}
}
+
+ switch (MODE) {
+ case 2:
+ if (flag == NM_CONNECTION_SERIALIZE_ALL) {
+ s_wg2 = NM_SETTING_WIREGUARD (nm_connection_get_setting (con2, NM_TYPE_SETTING_WIREGUARD));
+
+ if (flag == NM_CONNECTION_SERIALIZE_ALL)
+ _rndt_wg_peers_assert_equal (s_wg2, wg_peers, TRUE, TRUE, FALSE);
+ else if (flag == NM_CONNECTION_SERIALIZE_NO_SECRETS)
+ _rndt_wg_peers_assert_equal (s_wg2, wg_peers, FALSE, FALSE, TRUE);
+ else
+ g_assert_not_reached ();
+ }
+ break;
+ }
}
}
@@ -2268,6 +2691,8 @@ main (int argc, char **argv)
#endif
g_test_add_data_func ("/libnm/settings/roundtrip-conversion/general/0", GINT_TO_POINTER (0), test_roundtrip_conversion);
+ g_test_add_data_func ("/libnm/settings/roundtrip-conversion/wireguard/1", GINT_TO_POINTER (1), test_roundtrip_conversion);
+ g_test_add_data_func ("/libnm/settings/roundtrip-conversion/wireguard/2", GINT_TO_POINTER (2), test_roundtrip_conversion);
return g_test_run ();
}
diff --git a/libnm/NetworkManager.h b/libnm/NetworkManager.h
index 1e59d11758..7c70a226fd 100644
--- a/libnm/NetworkManager.h
+++ b/libnm/NetworkManager.h
@@ -105,6 +105,7 @@
#include "nm-setting-vxlan.h"
#include "nm-setting-wimax.h"
#include "nm-setting-wired.h"
+#include "nm-setting-wireguard.h"
#include "nm-setting-wireless.h"
#include "nm-setting-wireless-security.h"
#include "nm-setting-wpan.h"
diff --git a/libnm/libnm.ver b/libnm/libnm.ver
index cc8b211b8c..136b0009de 100644
--- a/libnm/libnm.ver
+++ b/libnm/libnm.ver
@@ -1462,6 +1462,19 @@ global:
nm_setting_wifi_p2p_get_wfd_ies;
nm_setting_wifi_p2p_get_wps_method;
nm_setting_wifi_p2p_new;
+ nm_setting_wireguard_append_peer;
+ nm_setting_wireguard_clear_peers;
+ nm_setting_wireguard_get_fwmark;
+ nm_setting_wireguard_get_listen_port;
+ nm_setting_wireguard_get_peer;
+ nm_setting_wireguard_get_peer_by_public_key;
+ nm_setting_wireguard_get_peers_len;
+ nm_setting_wireguard_get_private_key;
+ nm_setting_wireguard_get_private_key_flags;
+ nm_setting_wireguard_get_type;
+ nm_setting_wireguard_new;
+ nm_setting_wireguard_remove_peer;
+ nm_setting_wireguard_set_peer;
nm_team_link_watcher_get_vlanid;
nm_team_link_watcher_new_arp_ping2;
nm_wifi_p2p_peer_connection_valid;
@@ -1477,4 +1490,28 @@ global:
nm_wifi_p2p_peer_get_strength;
nm_wifi_p2p_peer_get_type;
nm_wifi_p2p_peer_get_wfd_ies;
+ nm_wireguard_peer_append_allowed_ip;
+ nm_wireguard_peer_clear_allowed_ips;
+ nm_wireguard_peer_cmp;
+ nm_wireguard_peer_get_allowed_ip;
+ nm_wireguard_peer_get_allowed_ips_len;
+ nm_wireguard_peer_get_endpoint;
+ nm_wireguard_peer_get_persistent_keepalive;
+ nm_wireguard_peer_get_preshared_key;
+ nm_wireguard_peer_get_preshared_key_flags;
+ nm_wireguard_peer_get_public_key;
+ nm_wireguard_peer_get_type;
+ nm_wireguard_peer_is_sealed;
+ nm_wireguard_peer_is_valid;
+ nm_wireguard_peer_new;
+ nm_wireguard_peer_new_clone;
+ nm_wireguard_peer_ref;
+ nm_wireguard_peer_remove_allowed_ip;
+ nm_wireguard_peer_seal;
+ nm_wireguard_peer_set_endpoint;
+ nm_wireguard_peer_set_persistent_keepalive;
+ nm_wireguard_peer_set_preshared_key;
+ nm_wireguard_peer_set_preshared_key_flags;
+ nm_wireguard_peer_set_public_key;
+ nm_wireguard_peer_unref;
} libnm_1_14_0;
diff --git a/libnm/nm-autoptr.h b/libnm/nm-autoptr.h
index 8abd792e22..40248dcd3e 100644
--- a/libnm/nm-autoptr.h
+++ b/libnm/nm-autoptr.h
@@ -80,6 +80,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (NMSettingVxlan, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (NMSettingWifiP2P, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (NMSettingWimax, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (NMSettingWired, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (NMSettingWireGuard, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (NMSettingWireless, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (NMSettingWirelessSecurity, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (NMSettingWpan, g_object_unref)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 393f84e8ca..148fd546f9 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -97,6 +97,7 @@ libnm-core/nm-setting-vxlan.c
libnm-core/nm-setting-wifi-p2p.c
libnm-core/nm-setting-wimax.c
libnm-core/nm-setting-wired.c
+libnm-core/nm-setting-wireguard.c
libnm-core/nm-setting-wireless-security.c
libnm-core/nm-setting-wireless.c
libnm-core/nm-setting-wpan.c
diff --git a/shared/nm-meta-setting.c b/shared/nm-meta-setting.c
index e7e73bd88e..e666e0b2a2 100644
--- a/shared/nm-meta-setting.c
+++ b/shared/nm-meta-setting.c
@@ -65,6 +65,7 @@
#include "nm-setting-wifi-p2p.h"
#include "nm-setting-wimax.h"
#include "nm-setting-wired.h"
+#include "nm-setting-wireguard.h"
#include "nm-setting-wireless-security.h"
#include "nm-setting-wireless.h"
#include "nm-setting-wpan.h"
@@ -402,6 +403,12 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = {
.setting_name = NM_SETTING_WIRED_SETTING_NAME,
.get_setting_gtype = nm_setting_wired_get_type,
},
+ [NM_META_SETTING_TYPE_WIREGUARD] = {
+ .meta_type = NM_META_SETTING_TYPE_WIREGUARD,
+ .setting_priority = NM_SETTING_PRIORITY_HW_BASE,
+ .setting_name = NM_SETTING_WIREGUARD_SETTING_NAME,
+ .get_setting_gtype = nm_setting_wireguard_get_type,
+ },
[NM_META_SETTING_TYPE_WIRELESS] = {
.meta_type = NM_META_SETTING_TYPE_WIRELESS,
.setting_priority = NM_SETTING_PRIORITY_HW_BASE,
diff --git a/shared/nm-meta-setting.h b/shared/nm-meta-setting.h
index 883d8ca195..18727a1638 100644
--- a/shared/nm-meta-setting.h
+++ b/shared/nm-meta-setting.h
@@ -147,6 +147,7 @@ typedef enum {
NM_META_SETTING_TYPE_VXLAN,
NM_META_SETTING_TYPE_WIFI_P2P,
NM_META_SETTING_TYPE_WIMAX,
+ NM_META_SETTING_TYPE_WIREGUARD,
NM_META_SETTING_TYPE_WPAN,
NM_META_SETTING_TYPE_UNKNOWN,
diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c
index 62ec027494..d08d6ad7af 100644
--- a/src/devices/nm-device-wireguard.c
+++ b/src/devices/nm-device-wireguard.c
@@ -21,24 +21,123 @@
#include "nm-device-wireguard.h"
+#include "nm-setting-wireguard.h"
+#include "nm-core-internal.h"
+#include "nm-utils/nm-secret-utils.h"
#include "nm-device-private.h"
#include "platform/nm-platform.h"
+#include "platform/nmp-object.h"
#include "nm-device-factory.h"
+#include "nm-active-connection.h"
+#include "nm-act-request.h"
+#include "dns/nm-dns-manager.h"
#include "nm-device-logging.h"
_LOG_DECLARE_SELF(NMDeviceWireGuard);
/*****************************************************************************/
+/* TODO: ensure externally-managed works. Both after start of NM and
+ * when adding a wg link with NM running. */
+
+/* TODO: activate profile with peer preshared-key-flags=2. On first activation, the secret is
+ * requested (good). Enter it and connect. Reactivate the profile, now there is no password
+ * prompt, as the secret is cached (good??). */
+
+/* TODO: unlike for other VPNs, we don't inject a direct route to the peers. That means,
+ * you might get a routing sceneraio where the peer (VPN server) is reachable via the VPN.
+ * How we handle adding routes to external gateway for other peers, has severe issues
+* as well. I think the only solution is https://www.wireguard.com/netns/#improving-the-classic-solutions */
+
+/*****************************************************************************/
+
+G_STATIC_ASSERT (NM_WIREGUARD_PUBLIC_KEY_LEN == NMP_WIREGUARD_PUBLIC_KEY_LEN);
+G_STATIC_ASSERT (NM_WIREGUARD_SYMMETRIC_KEY_LEN == NMP_WIREGUARD_SYMMETRIC_KEY_LEN);
+
+/*****************************************************************************/
+
+#define LINK_CONFIG_RATE_LIMIT_NSEC (50 * NM_UTILS_NS_PER_MSEC)
+
+/* a special @next_try_at_nsec timestamp indicating that we should try again as soon as possible. */
+#define NEXT_TRY_AT_NSEC_ASAP ((gint64) G_MAXINT64)
+
+/* a special @next_try_at_nsec timestamp that is
+ * - positive (indicating resolve-checks are enabled)
+ * - already in the past (we use the absolute timestamp of 1nsec for that). */
+#define NEXT_TRY_AT_NSEC_PAST ((gint64) 1)
+
+/* like %NEXT_TRY_AT_NSEC_ASAP, but used for indicating to retry ASAP for a @retry_in_msec value.
+ * That is a relative time duraction, contrary to @next_try_at_nsec which is an absolute
+ * timestamp. */
+#define RETRY_IN_MSEC_ASAP ((gint64) G_MAXINT64)
+
+#define RETRY_IN_MSEC_MAX ((gint64) (30 * 60 * 1000))
+
+typedef enum {
+ LINK_CONFIG_MODE_FULL,
+ LINK_CONFIG_MODE_REAPPLY,
+ LINK_CONFIG_MODE_ASSUME,
+ LINK_CONFIG_MODE_ENDPOINTS,
+} LinkConfigMode;
+
+typedef struct {
+ GCancellable *cancellable;
+
+ NMSockAddrUnion sockaddr;
+
+ /* the timestamp (in nm_utils_get_monotonic_timestamp_ns() scale) when we want
+ * to retry resolving the endpoint (again).
+ *
+ * It may be set to %NEXT_TRY_AT_NSEC_ASAP to indicate to re-resolve as soon as possible.
+ *
+ * A @sockaddr is either fixed or it has
+ * - @cancellable set to indicate an ongoing request
+ * - @next_try_at_nsec set to a positive value, indicating when
+ * we ought to retry. */
+ gint64 next_try_at_nsec;
+
+ guint resolv_fail_count;
+} PeerEndpointResolveData;
+
+typedef struct {
+ NMWireGuardPeer *peer;
+
+ NMDeviceWireGuard *self;
+
+ CList lst_peers;
+
+ PeerEndpointResolveData ep_resolv;
+
+ /* dirty flag used during _peers_update_all(). */
+ bool dirty_update_all:1;
+} PeerData;
+
NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWireGuard,
PROP_PUBLIC_KEY,
PROP_LISTEN_PORT,
PROP_FWMARK,
);
+typedef struct {
+
+ NMDnsManager *dns_manager;
+
+ NMPlatformLnkWireGuard lnk_curr;
+ NMActRequestGetSecretsCallId *secrets_call_id;
+
+ CList lst_peers_head;
+ GHashTable *peers;
+
+ gint64 resolve_next_try_at;
+ guint resolve_next_try_id;
+
+ gint64 link_config_last_at;
+ guint link_config_delayed_id;
+} NMDeviceWireGuardPrivate;
+
struct _NMDeviceWireGuard {
NMDevice parent;
- NMPlatformLnkWireGuard props;
+ NMDeviceWireGuardPrivate _priv;
};
struct _NMDeviceWireGuardClass {
@@ -47,25 +146,710 @@ struct _NMDeviceWireGuardClass {
G_DEFINE_TYPE (NMDeviceWireGuard, nm_device_wireguard, NM_TYPE_DEVICE)
-/******************************************************************/
+#define NM_DEVICE_WIREGUARD_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceWireGuard, NM_IS_DEVICE_WIREGUARD, NMDevice)
+
+/*****************************************************************************/
+
+static void _peers_resolve_start (NMDeviceWireGuard *self,
+ PeerData *peer_data);
+
+static void _peers_resolve_retry_reschedule (NMDeviceWireGuard *self,
+ gint64 new_next_try_at_nsec);
+
+static gboolean link_config_delayed_resolver_cb (gpointer user_data);
+
+static gboolean link_config_delayed_ratelimit_cb (gpointer user_data);
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_link_config_mode_to_string, LinkConfigMode,
+ NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT (NULL),
+ NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_FULL, "full"),
+ NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_REAPPLY, "reapply"),
+ NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_ASSUME, "assume"),
+ NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_ENDPOINTS, "endpoints"),
+);
+
+/*****************************************************************************/
+
+static gboolean
+_peer_data_equal (gconstpointer ptr_a, gconstpointer ptr_b)
+{
+ const PeerData *peer_data_a = ptr_a;
+ const PeerData *peer_data_b = ptr_b;
+
+ return nm_streq (nm_wireguard_peer_get_public_key (peer_data_a->peer),
+ nm_wireguard_peer_get_public_key (peer_data_b->peer));
+}
+
+static guint
+_peer_data_hash (gconstpointer ptr)
+{
+ const PeerData *peer_data = ptr;
+
+ return nm_hash_str (nm_wireguard_peer_get_public_key (peer_data->peer));
+}
-static GVariant *
-get_public_key_as_variant (const NMDeviceWireGuard *self)
+static PeerData *
+_peers_find (NMDeviceWireGuardPrivate *priv,
+ NMWireGuardPeer *peer)
{
- return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
- self->props.public_key, sizeof (self->props.public_key), 1);
+ nm_assert (peer);
+
+ G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (PeerData, peer) == 0);
+
+ return g_hash_table_lookup (priv->peers, &peer);
}
static void
+_peers_remove (NMDeviceWireGuardPrivate *priv,
+ PeerData *peer_data)
+{
+ nm_assert (peer_data);
+ nm_assert (g_hash_table_lookup (priv->peers, peer_data) == peer_data);
+
+ if (!g_hash_table_remove (priv->peers, peer_data))
+ nm_assert_not_reached ();
+
+ c_list_unlink_stale (&peer_data->lst_peers);
+ nm_wireguard_peer_unref (peer_data->peer);
+ nm_clear_g_cancellable (&peer_data->ep_resolv.cancellable);
+ g_slice_free (PeerData, peer_data);
+
+ if (c_list_is_empty (&peer_data->lst_peers)) {
+ nm_clear_g_source (&priv->resolve_next_try_id);
+ nm_clear_g_source (&priv->link_config_delayed_id);
+ }
+}
+
+static PeerData *
+_peers_add (NMDeviceWireGuard *self,
+ NMWireGuardPeer *peer)
+{
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+ PeerData *peer_data;
+
+ nm_assert (peer);
+ nm_assert (nm_wireguard_peer_is_sealed (peer));
+ nm_assert (!_peers_find (priv, peer));
+
+ peer_data = g_slice_new (PeerData);
+ *peer_data = (PeerData) {
+ .self = self,
+ .peer = nm_wireguard_peer_ref (peer),
+ .ep_resolv = {
+ .sockaddr = NM_SOCK_ADDR_UNION_INIT_UNSPEC,
+ },
+ };
+
+ c_list_link_tail (&priv->lst_peers_head, &peer_data->lst_peers);
+ if (!nm_g_hash_table_add (priv->peers, peer_data))
+ nm_assert_not_reached ();
+ return peer_data;
+}
+
+static gboolean
+_peers_resolve_retry_timeout (gpointer user_data)
+{
+ NMDeviceWireGuard *self = user_data;
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+ PeerData *peer_data;
+ gint64 now;
+ gint64 next;
+
+ priv->resolve_next_try_id = 0;
+
+ _LOGT (LOGD_DEVICE, "wireguard-peers: rechecking peer endpoints...");
+
+ now = nm_utils_get_monotonic_timestamp_ns ();
+ next = G_MAXINT64;
+ c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+ if (peer_data->ep_resolv.next_try_at_nsec <= 0)
+ continue;
+
+ if (peer_data->ep_resolv.cancellable) {
+ /* we are currently resolving a name. We don't need the global
+ * watchdog to guard this peer. No need to adjust @next for
+ * this one, when the currently ongoing resolving completes, we
+ * may reschedule. Skip. */
+ continue;
+ }
+
+ if ( peer_data->ep_resolv.next_try_at_nsec == NEXT_TRY_AT_NSEC_ASAP
+ || now >= peer_data->ep_resolv.next_try_at_nsec) {
+ _peers_resolve_start (self, peer_data);
+ /* same here. Now we are resolving. We don't need the global
+ * watchdog. Skip w.r.t. finding @next. */
+ continue;
+ }
+
+ if (next > peer_data->ep_resolv.next_try_at_nsec)
+ next = peer_data->ep_resolv.next_try_at_nsec;
+ }
+ if (next < G_MAXINT64)
+ _peers_resolve_retry_reschedule (self, next);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+_peers_resolve_retry_reschedule (NMDeviceWireGuard *self,
+ gint64 new_next_try_at_nsec)
+{
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+ guint32 interval_ms;
+ gint64 now;
+
+ nm_assert (new_next_try_at_nsec > 0);
+ nm_assert (new_next_try_at_nsec != NEXT_TRY_AT_NSEC_ASAP);
+
+ if ( priv->resolve_next_try_id
+ && priv->resolve_next_try_at <= new_next_try_at_nsec) {
+ /* we already have an earlier timeout scheduled (possibly for
+ * another peer that expires sooner). Don't reschedule now.
+ * Even if the scheduled timeout expires too early, we will
+ * compute the right next-timeout and reschedule then. */
+ return;
+ }
+
+ now = nm_utils_get_monotonic_timestamp_ns ();
+
+ /* schedule at most one day ahead. No problem if we expire earlier
+ * than expected. Also, rate-limit to 500 msec. */
+ interval_ms = NM_CLAMP ((new_next_try_at_nsec - now) / NM_UTILS_NS_PER_MSEC,
+ (gint64) 500,
+ (gint64) (24*60*60*1000));
+
+ _LOGT (LOGD_DEVICE, "wireguard-peers: schedule rechecking peer endpoints in %u msec",
+ interval_ms);
+
+ nm_clear_g_source (&priv->resolve_next_try_id);
+ priv->resolve_next_try_at = new_next_try_at_nsec;
+ priv->resolve_next_try_id = g_timeout_add (interval_ms,
+ _peers_resolve_retry_timeout,
+ self);
+}
+
+static void
+_peers_resolve_retry_reschedule_for_peer (NMDeviceWireGuard *self,
+ PeerData *peer_data,
+ gint64 retry_in_msec)
+{
+ nm_assert (retry_in_msec >= 0);
+
+ if (retry_in_msec == RETRY_IN_MSEC_ASAP) {
+ _peers_resolve_start (self, peer_data);
+ return;
+ }
+
+ peer_data->ep_resolv.next_try_at_nsec = nm_utils_get_monotonic_timestamp_ns ()
+ + (retry_in_msec * NM_UTILS_NS_PER_MSEC);
+ _peers_resolve_retry_reschedule (self, peer_data->ep_resolv.next_try_at_nsec);
+}
+
+static gint64
+_peers_retry_in_msec (PeerData *peer_data,
+ gboolean after_failure)
+{
+ if (peer_data->ep_resolv.next_try_at_nsec == NEXT_TRY_AT_NSEC_ASAP) {
+ peer_data->ep_resolv.resolv_fail_count = 0;
+ return RETRY_IN_MSEC_ASAP;
+ }
+
+ if (after_failure) {
+ if (peer_data->ep_resolv.resolv_fail_count < G_MAXUINT)
+ peer_data->ep_resolv.resolv_fail_count++;
+ } else
+ peer_data->ep_resolv.resolv_fail_count = 0;
+
+ if (!after_failure)
+ return RETRY_IN_MSEC_MAX;
+
+ if (peer_data->ep_resolv.resolv_fail_count > 20)
+ return RETRY_IN_MSEC_MAX;
+
+ /* double the retry-time, starting with one second. */
+ return NM_MIN (RETRY_IN_MSEC_MAX,
+ (1u << peer_data->ep_resolv.resolv_fail_count) * 500);
+}
+
+static void
+_peers_resolve_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NMDeviceWireGuard *self;
+ PeerData *peer_data;
+ gs_free_error GError *resolv_error = NULL;
+ GList *list;
+ gboolean changed = FALSE;
+ NMSockAddrUnion sockaddr;
+ gint64 retry_in_msec;
+ char s_sockaddr[100];
+ char s_retry[100];
+
+ list = g_resolver_lookup_by_name_finish (G_RESOLVER (source_object), res, &resolv_error);
+
+ if (nm_utils_error_is_cancelled (resolv_error, FALSE))
+ return;
+
+ peer_data = user_data;
+ self = peer_data->self;
+
+ g_clear_object (&peer_data->ep_resolv.cancellable);
+
+ nm_assert ((!resolv_error) != (!list));
+
+#define _retry_in_msec_to_string(retry_in_msec, s_retry) \
+ ({ \
+ gint64 _retry_in_msec = (retry_in_msec); \
+ \
+ _retry_in_msec == RETRY_IN_MSEC_ASAP \
+ ? "right away" \
+ : nm_sprintf_buf (s_retry, "in %"G_GINT64_FORMAT" msec", _retry_in_msec); \
+ })
+
+ if ( resolv_error
+ && !g_error_matches (resolv_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND)) {
+ retry_in_msec = _peers_retry_in_msec (peer_data, TRUE);
+
+ _LOGT (LOGD_DEVICE, "wireguard-peer[%s]: failure to resolve endpoint \"%s\": %s (retry %s)",
+ nm_wireguard_peer_get_public_key (peer_data->peer),
+ nm_wireguard_peer_get_endpoint (peer_data->peer),
+ resolv_error->message,
+ _retry_in_msec_to_string (retry_in_msec, s_retry));
+
+ _peers_resolve_retry_reschedule_for_peer (self, peer_data, retry_in_msec);
+ return;
+ }
+
+ sockaddr = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
+
+ if (!resolv_error) {
+ GList *iter;
+
+ for (iter = list; iter; iter = iter->next) {
+ GInetAddress *a = iter->data;
+ GSocketFamily f = g_inet_address_get_family (a);
+
+ if (f == G_SOCKET_FAMILY_IPV4) {
+ nm_assert (g_inet_address_get_native_size (a) == sizeof (struct in_addr));
+ sockaddr.in = (struct sockaddr_in) {
+ .sin_family = AF_INET,
+ .sin_port = htons (nm_sock_addr_endpoint_get_port (_nm_wireguard_peer_get_endpoint (peer_data->peer))),
+ };
+ memcpy (&sockaddr.in.sin_addr, g_inet_address_to_bytes (a), sizeof (struct in_addr));
+ break;
+ }
+ if (f == G_SOCKET_FAMILY_IPV6) {
+ nm_assert (g_inet_address_get_native_size (a) == sizeof (struct in6_addr));
+ sockaddr.in6 = (struct sockaddr_in6) {
+ .sin6_family = AF_INET6,
+ .sin6_port = htons (nm_sock_addr_endpoint_get_port (_nm_wireguard_peer_get_endpoint (peer_data->peer))),
+ .sin6_scope_id = 0,
+ .sin6_flowinfo = 0,
+ };
+ memcpy (&sockaddr.in6.sin6_addr, g_inet_address_to_bytes (a), sizeof (struct in6_addr));
+ break;
+ }
+ }
+
+ g_list_free_full (list, g_object_unref);
+ }
+
+ if (sockaddr.sa.sa_family == AF_UNSPEC) {
+ /* we failed to resolve the name. There is no need to reset the previous
+ * sockaddr. Either it was already AF_UNSPEC, or we had a good name
+ * from resolving before. In that case, we don't want to throw away
+ * a possibly good IP address, since WireGuard supports automatic roaming
+ * anyway. Either the IP address is still good (and we would wrongly
+ * reject it), or it isn't -- in which case it does not hurt much. */
+ } else {
+ if (nm_sock_addr_union_cmp (&peer_data->ep_resolv.sockaddr, &sockaddr) != 0)
+ changed = TRUE;
+ peer_data->ep_resolv.sockaddr = sockaddr;
+ }
+
+ if ( resolv_error
+ || peer_data->ep_resolv.sockaddr.sa.sa_family == AF_UNSPEC) {
+ /* while it technically did not fail, something is probably odd. Retry frequently to
+ * resolve the name, like we would do for normal failures. */
+ retry_in_msec = _peers_retry_in_msec (peer_data, TRUE);
+ _LOGT (LOGD_DEVICE, "wireguard-peer[%s]: no %sresults for endpoint \"%s\" (retry %s)",
+ nm_wireguard_peer_get_public_key (peer_data->peer),
+ resolv_error ? "" : "suitable ",
+ nm_wireguard_peer_get_endpoint (peer_data->peer),
+ _retry_in_msec_to_string (retry_in_msec, s_retry));
+ } else {
+ retry_in_msec = _peers_retry_in_msec (peer_data, FALSE);
+ _LOGT (LOGD_DEVICE, "wireguard-peer[%s]: endpoint \"%s\" resolved to %s (retry %s)",
+ nm_wireguard_peer_get_public_key (peer_data->peer),
+ nm_wireguard_peer_get_endpoint (peer_data->peer),
+ nm_sock_addr_union_to_string (&peer_data->ep_resolv.sockaddr, s_sockaddr, sizeof (s_sockaddr)),
+ _retry_in_msec_to_string (retry_in_msec, s_retry));
+ }
+
+ _peers_resolve_retry_reschedule_for_peer (self, peer_data, retry_in_msec);
+
+ if (changed) {
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+ /* schedule the job in the background, to give multiple resolve events time
+ * to complete. */
+ nm_clear_g_source (&priv->link_config_delayed_id);
+ priv->link_config_delayed_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE + 1,
+ link_config_delayed_resolver_cb,
+ self,
+ NULL);
+ }
+}
+
+static void
+_peers_resolve_start (NMDeviceWireGuard *self,
+ PeerData *peer_data)
+{
+ gs_unref_object GResolver *resolver = NULL;
+ const char *host;
+
+ resolver = g_resolver_get_default ();
+
+ nm_assert (!peer_data->ep_resolv.cancellable);
+
+ peer_data->ep_resolv.cancellable = g_cancellable_new ();
+
+ /* set a special next-try timestamp. It is positive, and indicates
+ * that we are in the process of trying.
+ * This timestamp however already lies in the past, but that is correct,
+ * because we are currently in the process of trying. We will determine
+ * a next-try timestamp once the try completes. */
+ peer_data->ep_resolv.next_try_at_nsec = NEXT_TRY_AT_NSEC_PAST;
+
+ host = nm_sock_addr_endpoint_get_host (_nm_wireguard_peer_get_endpoint (peer_data->peer));
+
+ g_resolver_lookup_by_name_async (resolver,
+ host,
+ peer_data->ep_resolv.cancellable,
+ _peers_resolve_cb,
+ peer_data);
+
+ _LOGT (LOGD_DEVICE, "wireguard-peer[%s]: resolving name \"%s\" for endpoint \"%s\"...",
+ nm_wireguard_peer_get_public_key (peer_data->peer),
+ host,
+ nm_wireguard_peer_get_endpoint (peer_data->peer));
+}
+
+static void
+_peers_resolve_reresolve_all (NMDeviceWireGuard *self)
+{
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+ PeerData *peer_data;
+
+ c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+ if (peer_data->ep_resolv.cancellable) {
+ /* remember to retry when the currently ongoing request completes. */
+ peer_data->ep_resolv.next_try_at_nsec = NEXT_TRY_AT_NSEC_ASAP;
+ } else if (peer_data->ep_resolv.next_try_at_nsec <= 0) {
+ /* this peer does not require resolving the name. Skip it. */
+ } else {
+ /* we have a next-try scheduled. Restart right away. */
+ peer_data->ep_resolv.resolv_fail_count = 0;
+ _peers_resolve_start (self, peer_data);
+ }
+ }
+}
+
+static gboolean
+_peers_update (NMDeviceWireGuard *self,
+ PeerData *peer_data,
+ NMWireGuardPeer *peer,
+ gboolean force_update)
+{
+ nm_auto_unref_wgpeer NMWireGuardPeer *old_peer = NULL;
+ NMSockAddrEndpoint *old_endpoint;
+ NMSockAddrEndpoint *endpoint;
+ gboolean endpoint_changed = FALSE;
+ gboolean changed;
+ NMSockAddrUnion sockaddr;
+ gboolean sockaddr_fixed;
+ char sockaddr_sbuf[100];
+
+ nm_assert (peer);
+ nm_assert (nm_wireguard_peer_is_sealed (peer));
+
+ if ( peer == peer_data->peer
+ && !force_update)
+ return FALSE;
+
+ changed = (nm_wireguard_peer_cmp (peer,
+ peer_data->peer,
+ NM_SETTING_COMPARE_FLAG_EXACT) != 0);
+
+ old_peer = peer_data->peer;
+ peer_data->peer = nm_wireguard_peer_ref (peer);
+
+ old_endpoint = old_peer ? _nm_wireguard_peer_get_endpoint (old_peer) : NULL;
+ endpoint = peer ? _nm_wireguard_peer_get_endpoint (peer) : NULL;
+
+ endpoint_changed = ( endpoint != old_endpoint
+ && ( !old_endpoint
+ || !endpoint
+ || !nm_streq (nm_sock_addr_endpoint_get_endpoint (old_endpoint),
+ nm_sock_addr_endpoint_get_endpoint (endpoint))));
+
+ if ( !force_update
+ && !endpoint_changed) {
+ /* nothing to do. */
+ return changed;
+ }
+
+ sockaddr = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
+ sockaddr_fixed = TRUE;
+ if ( endpoint
+ && nm_sock_addr_endpoint_get_host (endpoint)) {
+ if (!nm_sock_addr_endpoint_get_fixed_sockaddr (endpoint, &sockaddr)) {
+ /* we have an endpoint, but it's not a static IP address. We need to resolve
+ * the names. */
+ sockaddr_fixed = FALSE;
+ }
+ }
+
+ if (nm_sock_addr_union_cmp (&peer_data->ep_resolv.sockaddr, &sockaddr) != 0)
+ changed = TRUE;
+
+ nm_clear_g_cancellable (&peer_data->ep_resolv.cancellable);
+
+ peer_data->ep_resolv = (PeerEndpointResolveData) {
+ .sockaddr = sockaddr,
+ .resolv_fail_count = 0,
+ .cancellable = NULL,
+ .next_try_at_nsec = 0,
+ };
+
+ if (!endpoint) {
+ _LOGT (LOGD_DEVICE, "wireguard-peer[%s]: no endpoint configured",
+ nm_wireguard_peer_get_public_key (peer_data->peer));
+ } else if (!nm_sock_addr_endpoint_get_host (endpoint)) {
+ _LOGT (LOGD_DEVICE, "wireguard-peer[%s]: invalid endpoint \"%s\"",
+ nm_wireguard_peer_get_public_key (peer_data->peer),
+ nm_sock_addr_endpoint_get_endpoint (endpoint));
+ } else if (sockaddr_fixed) {
+ _LOGT (LOGD_DEVICE, "wireguard-peer[%s]: fixed endpoint \"%s\" (%s)",
+ nm_wireguard_peer_get_public_key (peer_data->peer),
+ nm_sock_addr_endpoint_get_endpoint (endpoint),
+ nm_sock_addr_union_to_string (&peer_data->ep_resolv.sockaddr, sockaddr_sbuf, sizeof (sockaddr_sbuf)));
+ } else
+ _peers_resolve_start (self, peer_data);
+
+ return changed;
+}
+
+static void
+_peers_remove_all (NMDeviceWireGuardPrivate *priv)
+{
+ PeerData *peer_data;
+
+ while ((peer_data = c_list_first_entry (&priv->lst_peers_head, PeerData, lst_peers)))
+ _peers_remove (priv, peer_data);
+}
+
+static void
+_peers_update_all (NMDeviceWireGuard *self,
+ NMSettingWireGuard *s_wg,
+ gboolean *out_peers_removed)
+{
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+ PeerData *peer_data_safe;
+ PeerData *peer_data;
+ guint i, n;
+ gboolean peers_removed = FALSE;
+
+ c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers)
+ peer_data->dirty_update_all = TRUE;
+
+ n = nm_setting_wireguard_get_peers_len (s_wg);
+ for (i = 0; i < n; i++) {
+ NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i);
+ gboolean added = FALSE;
+
+ peer_data = _peers_find (priv, peer);
+ if (!peer_data) {
+ peer_data = _peers_add (self, peer);
+ added = TRUE;
+ }
+ _peers_update (self, peer_data, peer, added);
+ peer_data->dirty_update_all = FALSE;
+ }
+
+ c_list_for_each_entry_safe (peer_data, peer_data_safe, &priv->lst_peers_head, lst_peers) {
+ if (peer_data->dirty_update_all) {
+ _peers_remove (priv, peer_data);
+ peers_removed = TRUE;
+ }
+ }
+
+ NM_SET_OUT (out_peers_removed, peers_removed);
+}
+
+static void
+_peers_get_platform_list (NMDeviceWireGuardPrivate *priv,
+ LinkConfigMode config_mode,
+ NMPWireGuardPeer **out_peers,
+ NMPlatformWireGuardChangePeerFlags **out_peer_flags,
+ guint *out_len,
+ GArray **out_allowed_ips_data)
+{
+ gs_free NMPWireGuardPeer *plpeers = NULL;
+ gs_free NMPlatformWireGuardChangePeerFlags *plpeer_flags = NULL;
+ gs_unref_array GArray *allowed_ips = NULL;
+ PeerData *peer_data;
+ guint i_good;
+ guint n_aip;
+ guint i_aip;
+ guint len;
+ guint i;
+
+ nm_assert (out_peers && !*out_peers);
+ nm_assert (out_peer_flags && !*out_peer_flags);
+ nm_assert (out_len && *out_len == 0);
+ nm_assert (out_allowed_ips_data && !*out_allowed_ips_data);
+
+ len = g_hash_table_size (priv->peers);
+
+ nm_assert (len == c_list_length (&priv->lst_peers_head));
+
+ if (len == 0)
+ return;
+
+ plpeers = g_new0 (NMPWireGuardPeer, len);
+ plpeer_flags = g_new0 (NMPlatformWireGuardChangePeerFlags, len);
+
+ i_good = 0;
+ c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+ NMPlatformWireGuardChangePeerFlags *plf = &plpeer_flags[i_good];
+ NMPWireGuardPeer *plp = &plpeers[i_good];
+ NMSettingSecretFlags psk_secret_flags;
+
+ if (!_nm_utils_wireguard_decode_key (nm_wireguard_peer_get_public_key (peer_data->peer),
+ sizeof (plp->public_key),
+ plp->public_key))
+ continue;
+
+ *plf = NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE;
+
+ plp->persistent_keepalive_interval = nm_wireguard_peer_get_persistent_keepalive (peer_data->peer);
+ if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+ LINK_CONFIG_MODE_REAPPLY))
+ *plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL;
+
+ /* if the peer has an endpoint but it is not yet resolved (not ready),
+ * we still configure it and leave the endpoint unspecified. Later,
+ * when we can resolve the endpoint, we will update. */
+ plp->endpoint = peer_data->ep_resolv.sockaddr;
+ if (plp->endpoint.sa.sa_family == AF_UNSPEC) {
+ /* we don't actually ever clear endpoints, if we don't have better information. */
+ } else
+ *plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT;
+
+ if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+ LINK_CONFIG_MODE_REAPPLY)) {
+ psk_secret_flags = nm_wireguard_peer_get_preshared_key_flags (peer_data->peer);
+ if (!NM_FLAGS_HAS (psk_secret_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
+ if ( !_nm_utils_wireguard_decode_key (nm_wireguard_peer_get_preshared_key (peer_data->peer),
+ sizeof (plp->preshared_key),
+ plp->preshared_key)
+ && config_mode == LINK_CONFIG_MODE_FULL)
+ goto skip;
+ }
+ *plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY;
+ }
+
+ if ( NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+ LINK_CONFIG_MODE_REAPPLY)
+ && ((n_aip = nm_wireguard_peer_get_allowed_ips_len (peer_data->peer)) > 0)) {
+ if (!allowed_ips)
+ allowed_ips = g_array_new (FALSE, FALSE, sizeof (NMPWireGuardAllowedIP));
+
+ *plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS
+ | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS;
+
+ plp->_construct_idx_start = allowed_ips->len;
+ for (i_aip = 0; i_aip < n_aip; i_aip++) {
+ const char *aip;
+ NMIPAddr addrbin = { };
+ int addr_family;
+ gboolean valid;
+ int prefix;
+
+ aip = nm_wireguard_peer_get_allowed_ip (peer_data->peer, i_aip, &valid);
+ if ( !valid
+ || !nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC,
+ aip,
+ &addr_family,
+ &addrbin,
+ &prefix)) {
+ /* the address is really not expected to be invalid, because then
+ * the connection would not verify. Anyway, silently skip it. */
+ continue;
+ }
+
+ if (prefix == -1)
+ prefix = addr_family == AF_INET ? 32 : 128;
+
+ g_array_append_val (allowed_ips,
+ ((NMPWireGuardAllowedIP) {
+ .family = addr_family,
+ .mask = prefix,
+ .addr = addrbin,
+ }));
+ }
+ plp->_construct_idx_end = allowed_ips->len;
+ }
+
+ i_good++;
+ continue;
+
+skip:
+ memset (plp, 0, sizeof (*plp));
+ }
+
+ if (i_good == 0)
+ return;
+
+ for (i = 0; i < i_good; i++) {
+ NMPWireGuardPeer *plp = &plpeers[i];
+ guint l;
+
+ if (plp->_construct_idx_end == 0) {
+ nm_assert (plp->_construct_idx_start == 0);
+ plp->allowed_ips = NULL;
+ plp->allowed_ips_len = 0;
+ } else {
+ nm_assert (plp->_construct_idx_start < plp->_construct_idx_end);
+ l = plp->_construct_idx_end - plp->_construct_idx_start;
+ plp->allowed_ips = &g_array_index (allowed_ips, NMPWireGuardAllowedIP, plp->_construct_idx_start);
+ plp->allowed_ips_len = l;
+ }
+ }
+ *out_peers = g_steal_pointer (&plpeers);
+ *out_peer_flags = g_steal_pointer (&plpeer_flags);;
+ *out_len = i_good;
+ *out_allowed_ips_data = g_steal_pointer (&allowed_ips);
+}
+
+/*****************************************************************************/
+
+static void
update_properties (NMDevice *device)
{
NMDeviceWireGuard *self;
+ NMDeviceWireGuardPrivate *priv;
const NMPlatformLink *plink;
const NMPlatformLnkWireGuard *props = NULL;
int ifindex;
g_return_if_fail (NM_IS_DEVICE_WIREGUARD (device));
self = NM_DEVICE_WIREGUARD (device);
+ priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
ifindex = nm_device_get_ifindex (device);
props = nm_platform_link_get_lnk_wireguard (nm_device_get_platform (device), ifindex, &plink);
@@ -78,16 +862,16 @@ update_properties (NMDevice *device)
#define CHECK_PROPERTY_CHANGED(field, prop) \
G_STMT_START { \
- if (self->props.field != props->field) { \
- self->props.field = props->field; \
+ if (priv->lnk_curr.field != props->field) { \
+ priv->lnk_curr.field = props->field; \
_notify (self, prop); \
} \
} G_STMT_END
#define CHECK_PROPERTY_CHANGED_ARRAY(field, prop) \
G_STMT_START { \
- if (memcmp (&self->props.field, &props->field, sizeof (props->field)) != 0) { \
- memcpy (&self->props.field, &props->field, sizeof (props->field)); \
+ if (memcmp (&priv->lnk_curr.field, &props->field, sizeof (priv->lnk_curr.field)) != 0) { \
+ memcpy (&priv->lnk_curr.field, &props->field, sizeof (priv->lnk_curr.field)); \
_notify (self, prop); \
} \
} G_STMT_END
@@ -107,24 +891,482 @@ link_changed (NMDevice *device,
update_properties (device);
}
+static NMDeviceCapabilities
+get_generic_capabilities (NMDevice *dev)
+{
+ return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+create_and_realize (NMDevice *device,
+ NMConnection *connection,
+ NMDevice *parent,
+ const NMPlatformLink **out_plink,
+ GError **error)
+{
+ const char *iface = nm_device_get_iface (device);
+ int r;
+
+ g_return_val_if_fail (iface, FALSE);
+
+ r = nm_platform_link_wireguard_add (nm_device_get_platform (device), iface, out_plink);
+ if (r < 0) {
+ g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
+ "Failed to create WireGuard interface '%s' for '%s': %s",
+ iface,
+ nm_connection_get_id (connection),
+ nm_strerror (r));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+_secrets_cancel (NMDeviceWireGuard *self)
+{
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+ if (priv->secrets_call_id)
+ nm_act_request_cancel_secrets (NULL, priv->secrets_call_id);
+ nm_assert (!priv->secrets_call_id);
+}
+
+static void
+_secrets_cb (NMActRequest *req,
+ NMActRequestGetSecretsCallId *call_id,
+ NMSettingsConnection *connection,
+ GError *error,
+ gpointer user_data)
+{
+ NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (user_data);
+ NMDevice *device = NM_DEVICE (self);
+ NMDeviceWireGuardPrivate *priv;
+
+ g_return_if_fail (NM_IS_DEVICE_WIREGUARD (self));
+ g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+ priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+ g_return_if_fail (priv->secrets_call_id == call_id);
+
+ priv->secrets_call_id = NULL;
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+
+ g_return_if_fail (req == nm_device_get_act_request (device));
+ g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
+ g_return_if_fail (nm_act_request_get_settings_connection (req) == connection);
+
+ if (error) {
+ _LOGW (LOGD_ETHER, "%s", error->message);
+ nm_device_state_changed (device,
+ NM_DEVICE_STATE_FAILED,
+ NM_DEVICE_STATE_REASON_NO_SECRETS);
+ } else
+ nm_device_activate_schedule_stage1_device_prepare (device);
+}
+
+static void
+_secrets_get_secrets (NMDeviceWireGuard *self,
+ const char *setting_name,
+ NMSecretAgentGetSecretsFlags flags,
+ const char *const*hints)
+{
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+ NMActRequest *req;
+
+ _secrets_cancel (self);
+
+ req = nm_device_get_act_request (NM_DEVICE (self));
+ g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+ priv->secrets_call_id = nm_act_request_get_secrets (req,
+ TRUE,
+ setting_name,
+ flags,
+ hints,
+ _secrets_cb,
+ self);
+ g_return_if_fail (priv->secrets_call_id);
+}
+
+static NMActStageReturn
+_secrets_handle_auth_or_fail (NMDeviceWireGuard *self,
+ NMActRequest *req,
+ gboolean new_secrets)
+{
+ NMConnection *applied_connection;
+ const char *setting_name;
+ gs_unref_ptrarray GPtrArray *hints = NULL;
+
+ if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
+ return NM_ACT_STAGE_RETURN_FAILURE;
+
+ nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
+
+ nm_active_connection_clear_secrets (NM_ACTIVE_CONNECTION (req));
+
+ applied_connection = nm_act_request_get_applied_connection (req);
+ setting_name = nm_connection_need_secrets (applied_connection, &hints);
+ if (!setting_name) {
+ _LOGI (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets.");
+ return NM_ACT_STAGE_RETURN_FAILURE;
+ }
+
+ if (hints)
+ g_ptr_array_add (hints, NULL);
+
+ _secrets_get_secrets (self,
+ setting_name,
+ NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+ | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0),
+ ( hints
+ ? (const char *const*) hints->pdata
+ : NULL));
+ return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+/*****************************************************************************/
+
+static void
+_dns_config_changed (NMDnsManager *dns_manager, NMDeviceWireGuard *self)
+{
+ /* when the DNS configuration changes, we re-resolve the peer addresses.
+ *
+ * Possibly, we should also do that when the default-route changes, but it's
+ * hard to figure out when that happens. */
+ _peers_resolve_reresolve_all (self);
+}
+
+/*****************************************************************************/
+
+static NMActStageReturn
+link_config (NMDeviceWireGuard *self,
+ const char *reason,
+ LinkConfigMode config_mode,
+ NMDeviceStateReason *out_failure_reason)
+{
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+ nm_auto_bzero_secret_ptr NMSecretPtr wg_lnk_clear_private_key = NM_SECRET_PTR_INIT ();
+ NMSettingWireGuard *s_wg;
+ NMConnection *connection;
+ NMActStageReturn ret;
+ gs_unref_array GArray *allowed_ips_data = NULL;
+ NMPlatformLnkWireGuard wg_lnk;
+ gs_free NMPWireGuardPeer *plpeers = NULL;
+ gs_free NMPlatformWireGuardChangePeerFlags *plpeer_flags = NULL;
+ guint plpeers_len = 0;
+ const char *setting_name;
+ gboolean peers_removed;
+ NMPlatformWireGuardChangeFlags wg_change_flags;
+ int ifindex;
+ int r;
+
+ NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+
+ connection = nm_device_get_applied_connection (NM_DEVICE (self));
+ s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
+ g_return_val_if_fail (s_wg, NM_ACT_STAGE_RETURN_FAILURE);
+
+ priv->link_config_last_at = nm_utils_get_monotonic_timestamp_ns ();
+
+ _LOGT (LOGD_DEVICE, "wireguard link config (%s, %s)...",
+ reason, _link_config_mode_to_string (config_mode));
+
+ if (!priv->dns_manager) {
+ priv->dns_manager = g_object_ref (nm_dns_manager_get ());
+ g_signal_connect (priv->dns_manager, NM_DNS_MANAGER_CONFIG_CHANGED, G_CALLBACK (_dns_config_changed), self);
+ }
+
+ if ( NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL)
+ && (setting_name = nm_connection_need_secrets (connection, NULL))) {
+ NMActRequest *req = nm_device_get_act_request (NM_DEVICE (self));
+
+ _LOGD (LOGD_DEVICE,
+ "Activation: connection '%s' has security, but secrets are required.",
+ nm_connection_get_id (connection));
+
+ ret = _secrets_handle_auth_or_fail (self, req, FALSE);
+ if (ret != NM_ACT_STAGE_RETURN_SUCCESS) {
+ if (ret != NM_ACT_STAGE_RETURN_POSTPONE) {
+ nm_assert (ret == NM_ACT_STAGE_RETURN_FAILURE);
+ NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+ }
+ return ret;
+ }
+ }
+
+ ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self));
+ if (ifindex <= 0) {
+ NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+ return NM_ACT_STAGE_RETURN_FAILURE;
+ }
+
+ _peers_update_all (self, s_wg, &peers_removed);
+
+ wg_lnk = (NMPlatformLnkWireGuard) { };
+
+ wg_change_flags = NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE;
+
+ if ( NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL)
+ || ( NM_IN_SET (config_mode, LINK_CONFIG_MODE_REAPPLY)
+ && peers_removed))
+ wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS;
+
+ if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+ LINK_CONFIG_MODE_REAPPLY)) {
+
+ wg_lnk.listen_port = nm_setting_wireguard_get_listen_port (s_wg),
+ wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT;
+
+ wg_lnk.fwmark = nm_setting_wireguard_get_fwmark (s_wg),
+ wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK;
+
+ if (_nm_utils_wireguard_decode_key (nm_setting_wireguard_get_private_key (s_wg),
+ sizeof (wg_lnk.private_key),
+ wg_lnk.private_key)) {
+ wg_lnk_clear_private_key = NM_SECRET_PTR_ARRAY (wg_lnk.private_key);
+ wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY;
+ } else {
+ if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL)) {
+ _LOGD (LOGD_DEVICE, "the provided private-key is invalid");
+ NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+ return NM_ACT_STAGE_RETURN_FAILURE;
+ }
+ }
+ }
+
+ _peers_get_platform_list (priv,
+ config_mode,
+ &plpeers,
+ &plpeer_flags,
+ &plpeers_len,
+ &allowed_ips_data);
+
+ r = nm_platform_link_wireguard_change (nm_device_get_platform (NM_DEVICE (self)),
+ ifindex,
+ &wg_lnk,
+ plpeers,
+ plpeer_flags,
+ plpeers_len,
+ wg_change_flags);
+
+ nm_explicit_bzero (plpeers, sizeof (plpeers) * plpeers_len);
+
+ if (r < 0) {
+ NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+ return NM_ACT_STAGE_RETURN_FAILURE;
+ }
+
+ return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static void
+link_config_delayed (NMDeviceWireGuard *self,
+ const char *reason)
+{
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+ gint64 now;
+
+ priv->link_config_delayed_id = 0;
+
+ if (priv->link_config_last_at != 0) {
+ now = nm_utils_get_monotonic_timestamp_ns ();
+ if (now < priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC) {
+ /* we ratelimit calls to link_config(), because we call this whenver a resolver
+ * completes. */
+ _LOGT (LOGD_DEVICE, "wireguard link config (%s) (postponed)", reason);
+ priv->link_config_delayed_id = g_timeout_add (NM_MAX ((priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC - now) / NM_UTILS_NS_PER_MSEC,
+ (gint64) 1),
+ link_config_delayed_ratelimit_cb,
+ self);
+ return;
+ }
+ }
+
+ link_config (self, reason, LINK_CONFIG_MODE_ENDPOINTS, NULL);
+}
-/******************************************************************/
+static gboolean
+link_config_delayed_ratelimit_cb (gpointer user_data)
+{
+ link_config_delayed (user_data, "after-ratelimiting");
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+link_config_delayed_resolver_cb (gpointer user_data)
+{
+ link_config_delayed (user_data, "resolver-update");
+ return G_SOURCE_REMOVE;
+}
+
+static NMActStageReturn
+act_stage2_config (NMDevice *device,
+ NMDeviceStateReason *out_failure_reason)
+{
+ NMDeviceSysIfaceState sys_iface_state;
+ NMDeviceStateReason failure_reason;
+ NMActStageReturn ret;
+
+ sys_iface_state = nm_device_sys_iface_state_get (device);
+
+ if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_EXTERNAL) {
+ NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+ return NM_ACT_STAGE_RETURN_SUCCESS;
+ }
+
+ ret = link_config (NM_DEVICE_WIREGUARD (device),
+ "configure",
+ (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME)
+ ? LINK_CONFIG_MODE_ASSUME
+ : LINK_CONFIG_MODE_FULL,
+ &failure_reason);
+
+ if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME) {
+ /* this never fails. */
+ NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+ return NM_ACT_STAGE_RETURN_SUCCESS;
+ }
+
+ if (ret != NM_ACT_STAGE_RETURN_FAILURE) {
+ NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+ return ret;
+ }
+
+ nm_device_state_changed (device,
+ NM_DEVICE_STATE_FAILED,
+ failure_reason);
+ NM_SET_OUT (out_failure_reason, failure_reason);
+ return NM_ACT_STAGE_RETURN_FAILURE;
+}
+
+static void
+device_state_changed (NMDevice *device,
+ NMDeviceState new_state,
+ NMDeviceState old_state,
+ NMDeviceStateReason reason)
+{
+ NMDeviceWireGuardPrivate *priv;
+
+ if (new_state <= NM_DEVICE_STATE_ACTIVATED)
+ return;
+
+ priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (device);
+
+ _peers_remove_all (priv);
+ _secrets_cancel (NM_DEVICE_WIREGUARD (device));
+}
+
+/*****************************************************************************/
+
+static gboolean
+can_reapply_change (NMDevice *device,
+ const char *setting_name,
+ NMSetting *s_old,
+ NMSetting *s_new,
+ GHashTable *diffs,
+ GError **error)
+{
+ if (nm_streq (setting_name, NM_SETTING_WIREGUARD_SETTING_NAME)) {
+ /* we allow reapplying all WireGuard settings. */
+ return TRUE;
+ }
+
+ return NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->can_reapply_change (device,
+ setting_name,
+ s_old,
+ s_new,
+ diffs,
+ error);
+}
+
+static void
+reapply_connection (NMDevice *device,
+ NMConnection *con_old,
+ NMConnection *con_new)
+{
+ NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->reapply_connection (device,
+ con_old,
+ con_new);
+
+ link_config (NM_DEVICE_WIREGUARD (device),
+ "reapply",
+ LINK_CONFIG_MODE_REAPPLY,
+ NULL);
+}
+
+/*****************************************************************************/
+
+static void
+update_connection (NMDevice *device, NMConnection *connection)
+{
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (device);
+ NMSettingWireGuard *s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
+ const NMPObject *obj_wg;
+ const NMPObjectLnkWireGuard *olnk_wg;
+ guint i;
+
+ if (!s_wg) {
+ s_wg = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ());
+ nm_connection_add_setting (connection, NM_SETTING (s_wg));
+ }
+
+ g_object_set (s_wg,
+ NM_SETTING_WIREGUARD_FWMARK,
+ (guint) priv->lnk_curr.fwmark,
+ NM_SETTING_WIREGUARD_LISTEN_PORT,
+ (guint) priv->lnk_curr.listen_port,
+ NULL);
+
+ obj_wg = NMP_OBJECT_UP_CAST (nm_platform_link_get_lnk_wireguard (nm_device_get_platform (device),
+ nm_device_get_ip_ifindex (device),
+ NULL));
+ if (!obj_wg)
+ return;
+
+ olnk_wg = &obj_wg->_lnk_wireguard;
+
+ for (i = 0; i < olnk_wg->peers_len; i++) {
+ nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
+ const NMPWireGuardPeer *ppeer = &olnk_wg->peers[i];
+
+ peer = nm_wireguard_peer_new ();
+
+ _nm_wireguard_peer_set_public_key_bin (peer, ppeer->public_key);
+
+ nm_setting_wireguard_append_peer (s_wg, peer);
+ }
+}
+
+/*****************************************************************************/
static void
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
switch (prop_id) {
case PROP_PUBLIC_KEY:
- g_value_take_variant (value, get_public_key_as_variant (self));
+ g_value_take_variant (value,
+ g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+ priv->lnk_curr.public_key,
+ sizeof (priv->lnk_curr.public_key),
+ 1));
break;
case PROP_LISTEN_PORT:
- g_value_set_uint (value, self->props.listen_port);
+ g_value_set_uint (value, priv->lnk_curr.listen_port);
break;
case PROP_FWMARK:
- g_value_set_uint (value, self->props.fwmark);
+ g_value_set_uint (value, priv->lnk_curr.fwmark);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -132,9 +1374,44 @@ get_property (GObject *object, guint prop_id,
}
}
+/*****************************************************************************/
+
static void
nm_device_wireguard_init (NMDeviceWireGuard *self)
{
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+ c_list_init (&priv->lst_peers_head);
+ priv->peers = g_hash_table_new (_peer_data_hash, _peer_data_equal);
+}
+
+static void
+dispose (GObject *object)
+{
+ NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+ _secrets_cancel (self);
+
+ _peers_remove_all (priv);
+
+ G_OBJECT_CLASS (nm_device_wireguard_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+ NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+ nm_explicit_bzero (priv->lnk_curr.private_key, sizeof (priv->lnk_curr.private_key));
+
+ if (priv->dns_manager) {
+ g_signal_handlers_disconnect_by_func (priv->dns_manager, _dns_config_changed, self);
+ g_object_unref (priv->dns_manager);
+ }
+
+ G_OBJECT_CLASS (nm_device_wireguard_parent_class)->finalize (object);
}
static const NMDBusInterfaceInfoExtended interface_info_device_wireguard = {
@@ -156,13 +1433,24 @@ nm_device_wireguard_class_init (NMDeviceWireGuardClass *klass)
NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_wireguard);
- device_class->connection_type_supported = NULL;
+ device_class->connection_type_supported = NM_SETTING_WIREGUARD_SETTING_NAME;
+ device_class->connection_type_check_compatible = NM_SETTING_WIREGUARD_SETTING_NAME;
device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD);
+ device_class->state_changed = device_state_changed;
+ device_class->create_and_realize = create_and_realize;
+ device_class->act_stage2_config = act_stage2_config;
+ device_class->act_stage2_config_also_for_external_or_assume = TRUE;
+ device_class->get_generic_capabilities = get_generic_capabilities;
device_class->link_changed = link_changed;
+ device_class->update_connection = update_connection;
+ device_class->can_reapply_change = can_reapply_change;
+ device_class->reapply_connection = reapply_connection;
obj_properties[PROP_PUBLIC_KEY] =
g_param_spec_variant (NM_DEVICE_WIREGUARD_PUBLIC_KEY,
@@ -207,6 +1495,7 @@ create_device (NMDeviceFactory *factory,
}
NM_DEVICE_FACTORY_DEFINE_INTERNAL (WIREGUARD, WireGuard, wireguard,
- NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD),
+ NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD)
+ NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_WIREGUARD_SETTING_NAME),
factory_class->create_device = create_device;
)
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 6aa6da8e5c..a241fd573d 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -4363,8 +4363,7 @@ realize_start_setup (NMDevice *self,
* NetworkManager might down the interface or remove the 127.0.0.1 address. */
nm_device_set_unmanaged_flags (self,
NM_UNMANAGED_BY_TYPE,
- is_loopback (self)
- || NM_IS_DEVICE_WIREGUARD (self));
+ is_loopback (self));
nm_device_set_unmanaged_by_user_udev (self);
nm_device_set_unmanaged_by_user_conf (self);