From b521f426ab228b003e1e87348e116291d23dc88b Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 27 Dec 2018 16:48:30 +0100 Subject: [PATCH 1/7] libnm,cli: add NMSettingWireGuard For now only add the core settings, no peers' data. To support peers and the allowed-ips of the peers is more complicated and will be done later. It's more complicated because these are nested lists (allowed-ips) inside a list (peers). That is quite unusual and to conveniently support that in D-Bus API, in keyfile format, in libnm, and nmcli, is a effort. Also, it's further complicated by the fact that each peer has a secret (the preshared-key). Thus we probably need secret flags for each peer, which is a novelty as well (until now we require a fixed set of secrets per profile that is well known). --- Makefile.am | 2 + clients/cli/connections.c | 1 + clients/common/nm-meta-setting-desc.c | 29 ++ clients/common/nm-secret-agent-simple.c | 58 +++- clients/common/settings-docs.h.in | 4 + docs/libnm/libnm-docs.xml | 1 + libnm-core/meson.build | 2 + libnm-core/nm-connection.c | 27 +- libnm-core/nm-core-enum-types.c.template | 1 + libnm-core/nm-core-internal.h | 11 + libnm-core/nm-core-types.h | 1 + libnm-core/nm-setting-wireguard.c | 404 +++++++++++++++++++++++ libnm-core/nm-setting-wireguard.h | 80 +++++ libnm-core/nm-utils.c | 75 +++++ libnm/NetworkManager.h | 1 + libnm/libnm.ver | 6 + libnm/nm-autoptr.h | 1 + po/POTFILES.in | 1 + shared/nm-meta-setting.c | 7 + shared/nm-meta-setting.h | 1 + src/devices/nm-device-wireguard.c | 7 + 21 files changed, 710 insertions(+), 10 deletions(-) create mode 100644 libnm-core/nm-setting-wireguard.c create mode 100644 libnm-core/nm-setting-wireguard.h 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/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 @@ -7504,6 +7504,28 @@ static const NMMetaPropertyInfo *const property_infos_WIRED[] = { NULL }; +#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[] = { @@ -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..6b72d2aa00 100644 --- a/clients/common/nm-secret-agent-simple.c +++ b/clients/common/nm-secret-agent-simple.c @@ -405,8 +405,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 +435,56 @@ 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++) { + const char *name = request->hints[i]; + gs_free char *peer_name = NULL; + + if (nm_streq (name, NM_SETTING_WIREGUARD_PRIVATE_KEY)) + continue; + + /* TODO: add support for WireGuard peers and their preshared-key. */ + g_set_error (error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_FAILED, + _("Cannot service unknown WireGuard hint '%s' for secrets request %s"), + name, + request->request_id); + return FALSE; + } + } + + *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 +870,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); 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())]]> + 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..54d852a7fd 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" @@ -768,4 +769,14 @@ gboolean _nm_connection_find_secret (NMConnection *self, /*****************************************************************************/ +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-setting-wireguard.c b/libnm-core/nm-setting-wireguard.c new file mode 100644 index 0000000000..724dda55d1 --- /dev/null +++ b/libnm-core/nm-setting-wireguard.c @@ -0,0 +1,404 @@ +/* + * 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. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE ( + PROP_PRIVATE_KEY, + PROP_PRIVATE_KEY_FLAGS, + PROP_LISTEN_PORT, + PROP_FWMARK, +); + +typedef struct { + char *private_key; + 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) + +/*****************************************************************************/ + +/** + * 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 gboolean +verify (NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingWireGuard *s_wg = NM_SETTING_WIREGUARD (setting); + + 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; + + 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); + + 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; + } + + return TRUE; +} + +static GPtrArray * +need_secrets (NMSetting *setting) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting); + GPtrArray *secrets = NULL; + + if ( !priv->private_key + || !priv->private_key_valid) { + secrets = g_ptr_array_new_full (1, NULL); + g_ptr_array_add (secrets, NM_SETTING_WIREGUARD_PRIVATE_KEY); + } + + return secrets; +} + +/*****************************************************************************/ + +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) +{ +} + +/** + * 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); + + 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); + + 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; + + /** + * 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); + + g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit (setting_class, NM_META_SETTING_TYPE_WIREGUARD); +} diff --git a/libnm-core/nm-setting-wireguard.h b/libnm-core/nm-setting-wireguard.h new file mode 100644 index 0000000000..1127b11382 --- /dev/null +++ b/libnm-core/nm-setting-wireguard.h @@ -0,0 +1,80 @@ +/* + * 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 can be included directly." +#endif + +#include "nm-setting.h" + +G_BEGIN_DECLS + +/*****************************************************************************/ + +#define NM_WIREGUARD_PUBLIC_KEY_LEN 32 +#define NM_WIREGUARD_SYMMETRIC_KEY_LEN 32 + +/*****************************************************************************/ + +#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" + +/*****************************************************************************/ + +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); + +/*****************************************************************************/ + +G_END_DECLS + +#endif /* __NM_SETTING_WIREGUARD_H__ */ 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/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..7689072eeb 100644 --- a/libnm/libnm.ver +++ b/libnm/libnm.ver @@ -1462,6 +1462,12 @@ global: nm_setting_wifi_p2p_get_wfd_ies; nm_setting_wifi_p2p_get_wps_method; nm_setting_wifi_p2p_new; + nm_setting_wireguard_get_fwmark; + nm_setting_wireguard_get_listen_port; + nm_setting_wireguard_get_private_key; + nm_setting_wireguard_get_private_key_flags; + nm_setting_wireguard_get_type; + nm_setting_wireguard_new; nm_team_link_watcher_get_vlanid; nm_team_link_watcher_new_arp_ping2; nm_wifi_p2p_peer_connection_valid; 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..0ca8cf3a5a 100644 --- a/src/devices/nm-device-wireguard.c +++ b/src/devices/nm-device-wireguard.c @@ -21,6 +21,8 @@ #include "nm-device-wireguard.h" +#include "nm-setting-wireguard.h" + #include "nm-device-private.h" #include "platform/nm-platform.h" #include "nm-device-factory.h" @@ -30,6 +32,11 @@ _LOG_DECLARE_SELF(NMDeviceWireGuard); /*****************************************************************************/ +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); + +/*****************************************************************************/ + NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWireGuard, PROP_PUBLIC_KEY, PROP_LISTEN_PORT, From e148ec07d5d3c1e5d570913006cddc6ca060e3d9 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 30 Jan 2019 12:36:13 +0100 Subject: [PATCH 2/7] libnm: add NMWireGuardPeer and libnm support for peers --- clients/common/nm-secret-agent-simple.c | 80 +- clients/common/nm-secret-agent-simple.h | 1 + libnm-core/nm-core-internal.h | 12 + libnm-core/nm-keyfile-utils.h | 3 +- libnm-core/nm-keyfile.c | 228 ++- libnm-core/nm-setting-wireguard.c | 1964 ++++++++++++++++++++++- libnm-core/nm-setting-wireguard.h | 121 ++ libnm-core/nm-setting.c | 2 +- libnm/libnm.ver | 31 + 9 files changed, 2426 insertions(+), 16 deletions(-) diff --git a/clients/common/nm-secret-agent-simple.c b/clients/common/nm-secret-agent-simple.c index 6b72d2aa00..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 @@ -464,19 +490,37 @@ add_wireguard_secrets (RequestData *request, } if (request->hints) { + for (i = 0; request->hints[i]; i++) { + NMWireGuardPeer *peer; const char *name = request->hints[i]; - gs_free char *peer_name = NULL; + gs_free char *public_key = NULL; if (nm_streq (name, NM_SETTING_WIREGUARD_PRIVATE_KEY)) continue; - /* TODO: add support for WireGuard peers and their preshared-key. */ - g_set_error (error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_FAILED, - _("Cannot service unknown WireGuard hint '%s' for secrets request %s"), - name, - request->request_id); - return FALSE; + 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))); } } @@ -1034,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++) { @@ -1065,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; } } @@ -1074,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/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h index 54d852a7fd..4a5796bd6e 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -634,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; @@ -769,6 +778,9 @@ 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); 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 #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" @@ -2901,6 +2902,137 @@ out: nm_connection_add_setting (info->connection, g_steal_pointer (&setting)); } +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) { @@ -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 index 724dda55d1..19b418547d 100644 --- a/libnm-core/nm-setting-wireguard.c +++ b/libnm-core/nm-setting-wireguard.c @@ -38,6 +38,817 @@ /*****************************************************************************/ +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, @@ -47,6 +858,8 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE ( typedef struct { char *private_key; + GPtrArray *peers_arr; + GHashTable *peers_hash; NMSettingSecretFlags private_key_flags; guint32 fwmark; guint16 listen_port; @@ -75,6 +888,32 @@ G_DEFINE_TYPE (NMSettingWireGuard, nm_setting_wireguard, NM_TYPE_SETTING) /*****************************************************************************/ +#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 @@ -141,10 +980,597 @@ nm_setting_wireguard_get_listen_port (NMSettingWireGuard *self) /*****************************************************************************/ +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; @@ -156,6 +1582,19 @@ verify (NMSetting *setting, NMConnection *connection, GError **error) 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; @@ -201,6 +1640,7 @@ 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) { @@ -210,6 +1650,19 @@ verify_secrets (NMSetting *setting, NMConnection *connection, GError **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, FALSE, TRUE, error)) { + g_prefix_error (error, + "%s.%s[%u]: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PEERS, + i); + return FALSE; + } + } + return TRUE; } @@ -218,16 +1671,485 @@ 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, NULL); - g_ptr_array_add (secrets, NM_SETTING_WIREGUARD_PRIVATE_KEY); + 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 @@ -298,6 +2220,10 @@ set_property (GObject *object, guint prop_id, 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); } /** @@ -322,6 +2248,10 @@ finalize (GObject *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); } @@ -330,14 +2260,24 @@ 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->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: @@ -398,7 +2338,19 @@ nm_setting_wireguard_class_init (NMSettingWireGuardClass *klass) | 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 (setting_class, NM_META_SETTING_TYPE_WIREGUARD); + _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 index 1127b11382..3810aa3048 100644 --- a/libnm-core/nm-setting-wireguard.h +++ b/libnm-core/nm-setting-wireguard.h @@ -25,6 +25,7 @@ #endif #include "nm-setting.h" +#include "nm-utils.h" G_BEGIN_DECLS @@ -35,6 +36,87 @@ G_BEGIN_DECLS /*****************************************************************************/ +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)) @@ -49,6 +131,15 @@ G_BEGIN_DECLS #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; @@ -75,6 +166,36 @@ 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/libnm.ver b/libnm/libnm.ver index 7689072eeb..136b0009de 100644 --- a/libnm/libnm.ver +++ b/libnm/libnm.ver @@ -1462,12 +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; @@ -1483,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; From 395a78618b6d42c059b52aee129a7777922469eb Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 3 Jan 2019 13:38:02 +0100 Subject: [PATCH 3/7] libnm/tests: add tests for creating wireguard connection profiles --- libnm-core/tests/test-setting.c | 425 ++++++++++++++++++++++++++++++++ 1 file changed, 425 insertions(+) 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 (); } From debd022a6d23e372d7940231e12f17bc5578c3d4 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 8 Jan 2019 11:10:25 +0100 Subject: [PATCH 4/7] examples: add python example script "nm-wg-set" for modifying WireGuard profile Use the script to test how GObject introspection with libnm's WireGuard support works. Also, since support for WireGuard peers is not yet implemented in nmcli (or other clients), this script is rather useful. --- Makefile.examples | 1 + examples/python/gi/nm-wg-set | 423 +++++++++++++++++++++++++++++++++++ 2 files changed, 424 insertions(+) create mode 100755 examples/python/gi/nm-wg-set diff --git a/Makefile.examples b/Makefile.examples index d49db6ce86..92923a8db6 100644 --- a/Makefile.examples +++ b/Makefile.examples @@ -177,6 +177,7 @@ EXTRA_DIST += \ examples/python/gi/get_ips.py \ examples/python/gi/list-connections.py \ examples/python/gi/nm-connection-update-stable-id.py \ + examples/python/gi/nm-wg-set \ examples/python/gi/setting-user-data.py \ examples/python/gi/show-wifi-networks.py \ examples/python/gi/update-ip4-method.py \ diff --git a/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 + # . + + print('interface: %s' % (s_con.get_interface_name())) + print('uuid: %s' % (conn.get_uuid())) + print('id: %s' % (conn.get_id())) + print('private-key: %s' % ('')) + 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, '' 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) + From 7f455f05193d0e800fea46ee31c4db8ba2f733b4 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sat, 29 Dec 2018 00:04:20 +0100 Subject: [PATCH 5/7] core/wireguard: add basic support for creating wireguard devices Configuring peers (and allowed-ips of the peers) is not yet supported. --- src/devices/nm-device-wireguard.c | 345 ++++++++++++++++++++++++++++-- src/devices/nm-device.c | 3 +- 2 files changed, 327 insertions(+), 21 deletions(-) diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c index 0ca8cf3a5a..0694ded274 100644 --- a/src/devices/nm-device-wireguard.c +++ b/src/devices/nm-device-wireguard.c @@ -22,10 +22,13 @@ #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 "nm-device-factory.h" +#include "nm-active-connection.h" +#include "nm-act-request.h" #include "nm-device-logging.h" _LOG_DECLARE_SELF(NMDeviceWireGuard); @@ -43,9 +46,15 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWireGuard, PROP_FWMARK, ); +typedef struct { + NMPlatformLnkWireGuard lnk_curr; + NMPlatformLnkWireGuard lnk_want; + NMActRequestGetSecretsCallId *secrets_call_id; +} NMDeviceWireGuardPrivate; + struct _NMDeviceWireGuard { NMDevice parent; - NMPlatformLnkWireGuard props; + NMDeviceWireGuardPrivate _priv; }; struct _NMDeviceWireGuardClass { @@ -54,25 +63,22 @@ 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 GVariant * -get_public_key_as_variant (const NMDeviceWireGuard *self) -{ - return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, - self->props.public_key, sizeof (self->props.public_key), 1); -} +/*****************************************************************************/ 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); @@ -85,16 +91,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 @@ -114,24 +120,292 @@ 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 NMActStageReturn +link_config (NMDeviceWireGuard *self, + gboolean allow_rate_limit, + gboolean fail_state_on_failure, + const char *reason, + NMDeviceStateReason *out_failure_reason) +{ + NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self); + NMSettingWireGuard *s_wg; + NMConnection *connection; + NMActStageReturn ret; + const char *setting_name; + NMDeviceStateReason failure_reason; + int ifindex; + int r; + + 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); + + setting_name = nm_connection_need_secrets (connection, NULL); + if (setting_name) { + 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_POSTPONE) + return ret; + if (ret != NM_ACT_STAGE_RETURN_SUCCESS) { + failure_reason = NM_DEVICE_STATE_REASON_NO_SECRETS; + goto out_ret; + } + } + + ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self)); + if (ifindex <= 0) { + failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + goto out_ret_fail; + } + + priv->lnk_want = (NMPlatformLnkWireGuard) { + .listen_port = nm_setting_wireguard_get_listen_port (s_wg), + .fwmark = nm_setting_wireguard_get_fwmark (s_wg), + }; + + if (!_nm_utils_wireguard_decode_key (nm_setting_wireguard_get_private_key (s_wg), + sizeof (priv->lnk_want.private_key), + priv->lnk_want.private_key)) { + _LOGD (LOGD_DEVICE, "the provided private-key is invalid"); + failure_reason = NM_DEVICE_STATE_REASON_NO_SECRETS; + goto out_ret_fail; + } + + r = nm_platform_link_wireguard_change (nm_device_get_platform (NM_DEVICE (self)), + ifindex, + &priv->lnk_want, + NULL, + 0, + TRUE); + if (r < 0) { + failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; + goto out_ret_fail; + } + + NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE); + return NM_ACT_STAGE_RETURN_SUCCESS; + +out_ret_fail: + ret = NM_ACT_STAGE_RETURN_FAILURE; +out_ret: + NM_SET_OUT (out_failure_reason, failure_reason); + if (fail_state_on_failure) { + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_FAILED, + failure_reason); + } + return ret; +} + +static NMActStageReturn +act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + return link_config (NM_DEVICE_WIREGUARD (device), FALSE, TRUE, "configure", out_failure_reason); +} + +static void +device_state_changed (NMDevice *device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + if (new_state <= NM_DEVICE_STATE_ACTIVATED) + return; + + _secrets_cancel (NM_DEVICE_WIREGUARD (device)); +} + +/*****************************************************************************/ + +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)); + + 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); +} + +/*****************************************************************************/ 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); @@ -139,11 +413,35 @@ get_property (GObject *object, guint prop_id, } } +/*****************************************************************************/ + static void nm_device_wireguard_init (NMDeviceWireGuard *self) { } +static void +dispose (GObject *object) +{ + NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object); + + _secrets_cancel (self); + + 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_want.private_key, sizeof (priv->lnk_want.private_key)); + nm_explicit_bzero (priv->lnk_curr.private_key, sizeof (priv->lnk_curr.private_key)); + + G_OBJECT_CLASS (nm_device_wireguard_parent_class)->finalize (object); +} + static const NMDBusInterfaceInfoExtended interface_info_device_wireguard = { .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT ( NM_DBUS_INTERFACE_DEVICE_WIREGUARD, @@ -163,13 +461,21 @@ 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->get_generic_capabilities = get_generic_capabilities; device_class->link_changed = link_changed; + device_class->update_connection = update_connection; obj_properties[PROP_PUBLIC_KEY] = g_param_spec_variant (NM_DEVICE_WIREGUARD_PUBLIC_KEY, @@ -214,6 +520,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); From 2148d0948202cf036868ed4ec87e297a37c91df5 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Sat, 12 Jan 2019 12:33:35 +0100 Subject: [PATCH 6/7] core/wireguard: add support for WireGuard peers That is slightly complex, because we need to (DNS) resolve the endpoints, and we also have to retry periodically. For example, initially we may be unable to resolve an endpoint, but later we may be. What is also interesting is that during assume and reapply, we may not have all information in the profile. Most notably, the private keys will be missing. We need to cope with that and not reconfigure keys. However, we still need to resolve names and update the endpoints. --- src/devices/nm-device-wireguard.c | 1057 +++++++++++++++++++++++++++-- 1 file changed, 1016 insertions(+), 41 deletions(-) diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c index 0694ded274..d08d6ad7af 100644 --- a/src/devices/nm-device-wireguard.c +++ b/src/devices/nm-device-wireguard.c @@ -26,20 +26,92 @@ #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, @@ -47,9 +119,20 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWireGuard, ); typedef struct { + + NMDnsManager *dns_manager; + NMPlatformLnkWireGuard lnk_curr; - NMPlatformLnkWireGuard lnk_want; 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 { @@ -67,6 +150,694 @@ G_DEFINE_TYPE (NMDeviceWireGuard, nm_device_wireguard, NM_TYPE_DEVICE) /*****************************************************************************/ +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 PeerData * +_peers_find (NMDeviceWireGuardPrivate *priv, + NMWireGuardPeer *peer) +{ + 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) { @@ -263,28 +1034,58 @@ _secrets_handle_auth_or_fail (NMDeviceWireGuard *self, /*****************************************************************************/ +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, - gboolean allow_rate_limit, - gboolean fail_state_on_failure, 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; - NMDeviceStateReason failure_reason; + 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); - setting_name = nm_connection_need_secrets (connection, NULL); - if (setting_name) { + 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, @@ -292,63 +1093,158 @@ link_config (NMDeviceWireGuard *self, nm_connection_get_id (connection)); ret = _secrets_handle_auth_or_fail (self, req, FALSE); - if (ret == NM_ACT_STAGE_RETURN_POSTPONE) - return ret; if (ret != NM_ACT_STAGE_RETURN_SUCCESS) { - failure_reason = NM_DEVICE_STATE_REASON_NO_SECRETS; - goto out_ret; + 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) { - failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; - goto out_ret_fail; + NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; } - priv->lnk_want = (NMPlatformLnkWireGuard) { - .listen_port = nm_setting_wireguard_get_listen_port (s_wg), - .fwmark = nm_setting_wireguard_get_fwmark (s_wg), - }; + _peers_update_all (self, s_wg, &peers_removed); - if (!_nm_utils_wireguard_decode_key (nm_setting_wireguard_get_private_key (s_wg), - sizeof (priv->lnk_want.private_key), - priv->lnk_want.private_key)) { - _LOGD (LOGD_DEVICE, "the provided private-key is invalid"); - failure_reason = NM_DEVICE_STATE_REASON_NO_SECRETS; - goto out_ret_fail; + 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, - &priv->lnk_want, - NULL, - 0, - TRUE); + &wg_lnk, + plpeers, + plpeer_flags, + plpeers_len, + wg_change_flags); + + nm_explicit_bzero (plpeers, sizeof (plpeers) * plpeers_len); + if (r < 0) { - failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED; - goto out_ret_fail; + NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; } - NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE); return NM_ACT_STAGE_RETURN_SUCCESS; +} -out_ret_fail: - ret = NM_ACT_STAGE_RETURN_FAILURE; -out_ret: - NM_SET_OUT (out_failure_reason, failure_reason); - if (fail_state_on_failure) { - nm_device_state_changed (NM_DEVICE (self), - NM_DEVICE_STATE_FAILED, - failure_reason); +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; + } } - return ret; + + 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) +act_stage2_config (NMDevice *device, + NMDeviceStateReason *out_failure_reason) { - return link_config (NM_DEVICE_WIREGUARD (device), FALSE, TRUE, "configure", 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 @@ -357,19 +1253,65 @@ device_state_changed (NMDevice *device, 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 ()); @@ -382,6 +1324,25 @@ update_connection (NMDevice *device, NMConnection *connection) 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); + } } /*****************************************************************************/ @@ -418,15 +1379,22 @@ 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); } @@ -436,9 +1404,13 @@ finalize (GObject *object) NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object); NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self); - nm_explicit_bzero (priv->lnk_want.private_key, sizeof (priv->lnk_want.private_key)); 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); } @@ -473,9 +1445,12 @@ nm_device_wireguard_class_init (NMDeviceWireGuardClass *klass) 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, From 6d5aa85181e2904f7776f993738774c47a43849d Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 11 Feb 2019 15:49:34 +0100 Subject: [PATCH 7/7] release: update NEWS --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 5e6e2473be..e140a4ad60 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,9 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! * Use a new type of secret-keys that combines the secret value with /etc/machine-id. This way when cloning a VM it suffices to change machine-id to generate different addresses. +* Add support for WireGuard VPN tunnels to NetworkManager. D-Bus API and libnm + support all options. nmcli supports creating and managing WireGuard profiles, + with the exception of configuring and showing peers. The following changes were backported to 1.14.x releases between 1.14.0 and 1.14.2 are also present in NetworkManager-1.14: