diff options
author | Thomas Haller <thaller@redhat.com> | 2019-02-22 13:54:48 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2019-02-22 13:54:48 +0100 |
commit | 4aaa0ed482f4354b5819883a1627d9968e642f1c (patch) | |
tree | 14e912ca5d5c87c971ab963c255e3774cc240402 | |
parent | 1d47643d954d69aa6be86a0f308111da9f926389 (diff) | |
parent | 6d5aa85181e2904f7776f993738774c47a43849d (diff) | |
download | NetworkManager-4aaa0ed482f4354b5819883a1627d9968e642f1c.tar.gz |
wireguard: merge branch 'th/wireguard-pt3'
https://github.com/NetworkManager/NetworkManager/pull/295
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 \ @@ -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); |