about summary refs log tree commit diff
path: root/libnm-core
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2019-03-12 15:16:42 +0100
committerSebastien Bacher <seb128@ubuntu.com>2019-03-12 15:16:42 +0100
commitcc4ab276f923ded9f415c1c2bf192994367ab0bb (patch)
treeac3a7775665992b27d07eb44186d38ff961a8cd7 /libnm-core
parentbb1cf58350bb34463e9ffc5f96ac4f9b6bf46d28 (diff)
parentdd428301eb6f02542015121d7b08d9997f137e50 (diff)
Update upstream source from tag 'upstream/1.15.91'
Update to upstream version '1.15.91'
with Debian dir 74de38245314cab529c6c94cd9d0b874ce0c2994
Diffstat (limited to 'libnm-core')
-rw-r--r--libnm-core/meson.build11
-rw-r--r--libnm-core/nm-connection.c152
-rw-r--r--libnm-core/nm-core-enum-types.c.template4
-rw-r--r--libnm-core/nm-core-internal.h98
-rw-r--r--libnm-core/nm-core-types.h10
-rw-r--r--libnm-core/nm-crypto.c1
-rw-r--r--libnm-core/nm-dbus-interface.h95
-rw-r--r--libnm-core/nm-dbus-types.xml41
-rw-r--r--libnm-core/nm-dbus-utils.c2
-rw-r--r--libnm-core/nm-errors.c3
-rw-r--r--libnm-core/nm-errors.h2
-rw-r--r--libnm-core/nm-keyfile-utils.c4
-rw-r--r--libnm-core/nm-keyfile-utils.h3
-rw-r--r--libnm-core/nm-keyfile.c225
-rw-r--r--libnm-core/nm-property-compare.c5
-rw-r--r--libnm-core/nm-setting-8021x.c4
-rw-r--r--libnm-core/nm-setting-8021x.h9
-rw-r--r--libnm-core/nm-setting-adsl.c2
-rw-r--r--libnm-core/nm-setting-bluetooth.c1
-rw-r--r--libnm-core/nm-setting-bond.c19
-rw-r--r--libnm-core/nm-setting-bridge-port.c1
-rw-r--r--libnm-core/nm-setting-bridge.c3
-rw-r--r--libnm-core/nm-setting-cdma.c2
-rw-r--r--libnm-core/nm-setting-connection.c4
-rw-r--r--libnm-core/nm-setting-dcb.c6
-rw-r--r--libnm-core/nm-setting-gsm.c2
-rw-r--r--libnm-core/nm-setting-ip-config.c3
-rw-r--r--libnm-core/nm-setting-ip-config.h4
-rw-r--r--libnm-core/nm-setting-ip4-config.c2
-rw-r--r--libnm-core/nm-setting-ip6-config.c1
-rw-r--r--libnm-core/nm-setting-macsec.c1
-rw-r--r--libnm-core/nm-setting-macvlan.c1
-rw-r--r--libnm-core/nm-setting-olpc-mesh.c2
-rw-r--r--libnm-core/nm-setting-pppoe.c2
-rw-r--r--libnm-core/nm-setting-private.h5
-rw-r--r--libnm-core/nm-setting-serial.c2
-rw-r--r--libnm-core/nm-setting-sriov.c2
-rw-r--r--libnm-core/nm-setting-team-port.c17
-rw-r--r--libnm-core/nm-setting-team.c38
-rw-r--r--libnm-core/nm-setting-team.h6
-rw-r--r--libnm-core/nm-setting-tun.c1
-rw-r--r--libnm-core/nm-setting-vlan.c1
-rw-r--r--libnm-core/nm-setting-vpn.c76
-rw-r--r--libnm-core/nm-setting-vxlan.c1
-rw-r--r--libnm-core/nm-setting-wifi-p2p.c317
-rw-r--r--libnm-core/nm-setting-wifi-p2p.h69
-rw-r--r--libnm-core/nm-setting-wimax.c1
-rw-r--r--libnm-core/nm-setting-wired.c6
-rw-r--r--libnm-core/nm-setting-wired.h27
-rw-r--r--libnm-core/nm-setting-wireguard.c2499
-rw-r--r--libnm-core/nm-setting-wireguard.h213
-rw-r--r--libnm-core/nm-setting-wireless-security.c62
-rw-r--r--libnm-core/nm-setting-wireless-security.h2
-rw-r--r--libnm-core/nm-setting-wireless.c1
-rw-r--r--libnm-core/nm-setting-wireless.h30
-rw-r--r--libnm-core/nm-setting.c184
-rw-r--r--libnm-core/nm-setting.h59
-rw-r--r--libnm-core/nm-utils-private.h12
-rw-r--r--libnm-core/nm-utils.c677
-rw-r--r--libnm-core/nm-utils.h6
-rw-r--r--libnm-core/nm-vpn-plugin-info.c4
-rw-r--r--libnm-core/tests/meson.build1
-rw-r--r--libnm-core/tests/test-crypto.c1
-rw-r--r--libnm-core/tests/test-general.c158
-rw-r--r--libnm-core/tests/test-secrets.c2
-rw-r--r--libnm-core/tests/test-setting.c430
-rw-r--r--libnm-core/tests/test-settings-defaults.c2
67 files changed, 5038 insertions, 599 deletions
diff --git a/libnm-core/meson.build b/libnm-core/meson.build
index 812f12bb..d10dd1c5 100644
--- a/libnm-core/meson.build
+++ b/libnm-core/meson.build
@@ -45,8 +45,10 @@ libnm_core_headers = files(
   'nm-setting-vlan.h',
   'nm-setting-vpn.h',
   'nm-setting-vxlan.h',
+  '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',
@@ -100,8 +102,10 @@ libnm_core_settings_sources = files(
   'nm-setting-vlan.c',
   'nm-setting-vpn.c',
   'nm-setting-vxlan.c',
+  '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',
@@ -138,9 +142,11 @@ libnm_core_enum = gnome.mkenums(
 deps = [
   dl_dep,
   libudev_dep,
-  shared_dep,
   uuid_dep,
+  glib_dep,
   shared_c_siphash_dep,
+  shared_nm_utils_base_dep,
+  shared_nm_utils_udev_dep,
 ]
 
 cflags = [
@@ -183,7 +189,6 @@ libnm_core_sources_all =  libnm_core_sources
 libnm_core_sources_all += libnm_core_enum
 libnm_core_sources_all += shared_nm_meta_setting_c
 libnm_core_sources_all += shared_nm_ethtool_utils_c
-libnm_core_sources_all += shared_files_libnm_core
 libnm_core_sources_all += [version_header]
 
 libnm_core = static_library(
@@ -198,7 +203,7 @@ nm_core_dep = declare_dependency(
   sources: libnm_core_enum[1],
   include_directories: libnm_core_inc,
   dependencies: [
-    shared_dep,
+    shared_nm_utils_base_dep,
     shared_c_siphash_dep,
     libnm_systemd_shared_dep,
   ],
diff --git a/libnm-core/nm-connection.c b/libnm-core/nm-connection.c
index 724e8557..ea7730bf 100644
--- a/libnm-core/nm-connection.c
+++ b/libnm-core/nm-connection.c
@@ -24,7 +24,6 @@
 
 #include "nm-connection.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 
 #include "nm-connection-private.h"
@@ -231,6 +230,18 @@ nm_connection_get_setting (NMConnection *connection, GType setting_type)
 	return _connection_get_setting_check (connection, setting_type);
 }
 
+NMSettingIPConfig *
+nm_connection_get_setting_ip_config (NMConnection *connection,
+                                     int addr_family)
+{
+	nm_assert_addr_family (addr_family);
+
+	return NM_SETTING_IP_CONFIG (_connection_get_setting (connection,
+	                                                        (addr_family == AF_INET)
+	                                                      ? NM_TYPE_SETTING_IP4_CONFIG
+	                                                      : NM_TYPE_SETTING_IP6_CONFIG));
+}
+
 /**
  * nm_connection_get_setting_by_name:
  * @connection: a #NMConnection
@@ -674,8 +685,10 @@ _nm_connection_find_base_type_setting (NMConnection *connection)
 {
 	NMConnectionPrivate *priv = NM_CONNECTION_GET_PRIVATE (connection);
 	GHashTableIter iter;
-	NMSetting *setting = NULL, *s_iter;
-	NMSettingPriority setting_prio, s_iter_prio;
+	NMSetting *setting = NULL;
+	NMSetting *s_iter;
+	NMSettingPriority setting_prio = NM_SETTING_PRIORITY_USER;
+	NMSettingPriority s_iter_prio;
 
 	g_hash_table_iter_init (&iter, priv->settings);
 	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &s_iter)) {
@@ -904,25 +917,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.
 			  */
@@ -965,6 +977,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.
 			 */
@@ -1845,7 +1868,7 @@ nm_connection_clear_secrets (NMConnection *connection)
 /**
  * nm_connection_clear_secrets_with_flags:
  * @connection: the #NMConnection
- * @func: (scope call): (allow-none): function to be called to determine whether a
+ * @func: (scope call) (allow-none): function to be called to determine whether a
  *     specific secret should be cleared or not. If %NULL, all secrets are cleared.
  * @user_data: caller-supplied data passed to @func
  *
@@ -1871,6 +1894,108 @@ nm_connection_clear_secrets_with_flags (NMConnection *connection,
 	g_signal_emit (connection, signals[SECRETS_CLEARED], 0);
 }
 
+/*****************************************************************************/
+
+/* Returns always a non-NULL, floating variant that must
+ * be unrefed by the caller. */
+GVariant *
+_nm_connection_for_each_secret (NMConnection *self,
+                                GVariant *secrets,
+                                gboolean remove_non_secrets,
+                                _NMConnectionForEachSecretFunc callback,
+                                gpointer callback_data)
+{
+	GVariantBuilder secrets_builder;
+	GVariantBuilder setting_builder;
+	GVariantIter secrets_iter;
+	GVariantIter *setting_iter;
+	const char *setting_name;
+
+	/* This function, given a dict of dicts representing new secrets of
+	 * an NMConnection, walks through each toplevel dict (which represents a
+	 * NMSetting), and for each setting, walks through that setting dict's
+	 * properties.  For each property that's a secret, it will check that
+	 * secret's flags in the backing NMConnection object, and call a supplied
+	 * callback.
+	 *
+	 * The one complexity is that the VPN setting's 'secrets' property is
+	 * *also* a dict (since the key/value pairs are arbitrary and known
+	 * only to the VPN plugin itself).  That means we have three levels of
+	 * dicts that we potentially have to traverse here.  The differences
+	 * are handled by the virtual for_each_secret() function.
+	 */
+
+	g_return_val_if_fail (callback, NULL);
+
+	g_variant_iter_init (&secrets_iter, secrets);
+	g_variant_builder_init (&secrets_builder, NM_VARIANT_TYPE_CONNECTION);
+	while (g_variant_iter_next (&secrets_iter, "{&sa{sv}}", &setting_name, &setting_iter)) {
+		_nm_unused nm_auto_free_variant_iter GVariantIter *setting_iter_free = setting_iter;
+		NMSetting *setting;
+		const char *secret_name;
+		GVariant *val;
+
+		setting = nm_connection_get_setting_by_name (self, setting_name);
+		if (!setting)
+			continue;
+
+		g_variant_builder_init (&setting_builder, NM_VARIANT_TYPE_SETTING);
+		while (g_variant_iter_next (setting_iter, "{&sv}", &secret_name, &val)) {
+			_nm_unused gs_unref_variant GVariant *val_free = val;
+
+			NM_SETTING_GET_CLASS (setting)->for_each_secret (setting,
+			                                                 secret_name,
+			                                                 val,
+			                                                 remove_non_secrets,
+			                                                 callback,
+			                                                 callback_data,
+			                                                 &setting_builder);
+		}
+
+		g_variant_builder_add (&secrets_builder, "{sa{sv}}", setting_name, &setting_builder);
+	}
+
+	return g_variant_builder_end (&secrets_builder);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+	NMConnectionFindSecretFunc find_func;
+	gpointer find_func_data;
+	gboolean found;
+} FindSecretData;
+
+static gboolean
+find_secret_for_each_func (NMSettingSecretFlags flags,
+                           gpointer user_data)
+{
+	FindSecretData *data = user_data;
+
+	if (!data->found)
+		data->found = data->find_func (flags, data->find_func_data);
+	return FALSE;
+}
+
+gboolean
+_nm_connection_find_secret (NMConnection *self,
+                            GVariant *secrets,
+                            NMConnectionFindSecretFunc callback,
+                            gpointer callback_data)
+{
+	gs_unref_variant GVariant *dummy = NULL;
+	FindSecretData data = {
+		.find_func      = callback,
+		.find_func_data = callback_data,
+		.found          = FALSE,
+	};
+
+	dummy = _nm_connection_for_each_secret (self, secrets, FALSE, find_secret_for_each_func, &data);
+	return data.found;
+}
+
+/*****************************************************************************/
+
 /**
  * nm_connection_to_dbus:
  * @connection: the #NMConnection
@@ -1955,7 +2080,7 @@ _for_each_sort (NMSetting **p_a, NMSetting **p_b, void *unused)
 /**
  * nm_connection_get_settings:
  * @connection: the #NMConnection instance
- * @out_length: (allow-none): (out): the length of the returned array
+ * @out_length: (allow-none) (out): the length of the returned array
  *
  * Retrieves the settings in @connection.
  *
@@ -2316,7 +2441,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 605fa34a..94744827 100644
--- a/libnm-core/nm-core-enum-types.c.template
+++ b/libnm-core/nm-core-enum-types.c.template
@@ -4,7 +4,6 @@
 #include "nm-core-enum-types.h"
 #include "nm-default.h"
 
-#include "nm-version-macros.h"
 #include "nm-connection.h"
 #include "nm-core-types.h"
 #include "nm-dbus-interface.h"
@@ -44,14 +43,17 @@
 #include "nm-setting-vlan.h"
 #include "nm-setting-vpn.h"
 #include "nm-setting-vxlan.h"
+#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"
 #include "nm-setting.h"
 #include "nm-simple-connection.h"
 #include "nm-utils.h"
+#include "nm-version-macros.h"
 #include "nm-version.h"
 #include "nm-vpn-dbus-interface.h"
 #include "nm-vpn-editor-plugin.h"
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h
index 6af293a0..6e2a5b5e 100644
--- a/libnm-core/nm-core-internal.h
+++ b/libnm-core/nm-core-internal.h
@@ -37,6 +37,8 @@
 
 #include "nm-connection.h"
 #include "nm-core-enum-types.h"
+#include "nm-core-types-internal.h"
+#include "nm-meta-setting.h"
 #include "nm-setting-6lowpan.h"
 #include "nm-setting-8021x.h"
 #include "nm-setting-adsl.h"
@@ -52,7 +54,6 @@
 #include "nm-setting-gsm.h"
 #include "nm-setting-infiniband.h"
 #include "nm-setting-ip-tunnel.h"
-#include "nm-setting-proxy.h"
 #include "nm-setting-ip4-config.h"
 #include "nm-setting-ip6-config.h"
 #include "nm-setting-macsec.h"
@@ -65,6 +66,7 @@
 #include "nm-setting-ovs-port.h"
 #include "nm-setting-ppp.h"
 #include "nm-setting-pppoe.h"
+#include "nm-setting-proxy.h"
 #include "nm-setting-serial.h"
 #include "nm-setting-sriov.h"
 #include "nm-setting-tc-config.h"
@@ -74,8 +76,10 @@
 #include "nm-setting-vlan.h"
 #include "nm-setting-vpn.h"
 #include "nm-setting-vxlan.h"
+#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"
@@ -83,9 +87,7 @@
 #include "nm-simple-connection.h"
 #include "nm-utils.h"
 #include "nm-vpn-dbus-interface.h"
-#include "nm-core-types-internal.h"
 #include "nm-vpn-editor-plugin.h"
-#include "nm-meta-setting.h"
 
 /* IEEE 802.1D-1998 timer values */
 #define NM_BR_MIN_HELLO_TIME    1
@@ -128,7 +130,7 @@
 
 /*****************************************************************************/
 
-#define NM_SETTING_SECRET_FLAGS_ALL \
+#define NM_SETTING_SECRET_FLAG_ALL \
 	((NMSettingSecretFlags) (  NM_SETTING_SECRET_FLAG_NONE \
 	                         | NM_SETTING_SECRET_FLAG_AGENT_OWNED \
 	                         | NM_SETTING_SECRET_FLAG_NOT_SAVED \
@@ -137,7 +139,7 @@
 static inline gboolean
 _nm_setting_secret_flags_valid (NMSettingSecretFlags flags)
 {
-	return !NM_FLAGS_ANY (flags, ~NM_SETTING_SECRET_FLAGS_ALL);
+	return !NM_FLAGS_ANY (flags, ~NM_SETTING_SECRET_FLAG_ALL);
 }
 
 /*****************************************************************************/
@@ -247,27 +249,6 @@ guint nm_setting_ethtool_init_features (NMSettingEthtool *setting,
 guint8 *_nm_utils_hwaddr_aton (const char *asc, gpointer buffer, gsize buffer_length, gsize *out_length);
 const char *nm_utils_hwaddr_ntoa_buf (gconstpointer addr, gsize addr_len, gboolean upper_case, char *buf, gsize buf_len);
 
-char *_nm_utils_bin2hexstr_full (gconstpointer addr, gsize length, const char delimiter, gboolean upper_case, char *out);
-
-guint8 *_nm_utils_hexstr2bin_full (const char *hexstr,
-                                   gboolean allow_0x_prefix,
-                                   gboolean delimiter_required,
-                                   const char *delimiter_candidates,
-                                   gsize required_len,
-                                   guint8 *buffer,
-                                   gsize buffer_len,
-                                   gsize *out_len);
-
-#define _nm_utils_hexstr2bin_buf(hexstr, allow_0x_prefix, delimiter_required, delimiter_candidates, buffer) \
-    _nm_utils_hexstr2bin_full ((hexstr), (allow_0x_prefix), (delimiter_required), (delimiter_candidates), G_N_ELEMENTS (buffer), (buffer), G_N_ELEMENTS (buffer), NULL)
-
-guint8 *_nm_utils_hexstr2bin_alloc (const char *hexstr,
-                                    gboolean allow_0x_prefix,
-                                    gboolean delimiter_required,
-                                    const char *delimiter_candidates,
-                                    gsize required_len,
-                                    gsize *out_len);
-
 GSList *    _nm_utils_hash_values_to_slist (GHashTable *hash);
 
 GHashTable *_nm_utils_copy_strdict (GHashTable *strdict);
@@ -468,6 +449,11 @@ gboolean _nm_utils_generate_mac_address_mask_parse (const char *value,
 
 /*****************************************************************************/
 
+NMSettingIPConfig *nm_connection_get_setting_ip_config (NMConnection *connection,
+                                                        int addr_family);
+
+/*****************************************************************************/
+
 typedef enum {
 	NM_BOND_OPTION_TYPE_INT,
 	NM_BOND_OPTION_TYPE_STRING,
@@ -613,7 +599,36 @@ gboolean _nm_setting_sriov_sort_vfs (NMSettingSriov *setting);
 
 /*****************************************************************************/
 
-typedef struct _NMSettInfoSetting NMSettInfoSetting;
+typedef struct _NMSockAddrEndpoint NMSockAddrEndpoint;
+
+NMSockAddrEndpoint *nm_sock_addr_endpoint_new (const char *endpoint);
+
+NMSockAddrEndpoint *nm_sock_addr_endpoint_ref (NMSockAddrEndpoint *self);
+void nm_sock_addr_endpoint_unref (NMSockAddrEndpoint *self);
+
+const char *nm_sock_addr_endpoint_get_endpoint (NMSockAddrEndpoint *self);
+const char *nm_sock_addr_endpoint_get_host (NMSockAddrEndpoint *self);
+gint32 nm_sock_addr_endpoint_get_port (NMSockAddrEndpoint *self);
+
+gboolean nm_sock_addr_endpoint_get_fixed_sockaddr (NMSockAddrEndpoint *self,
+                                                   gpointer sockaddr);
+
+#define nm_auto_unref_sockaddrendpoint nm_auto(_nm_auto_unref_sockaddrendpoint)
+NM_AUTO_DEFINE_FCN_VOID0 (NMSockAddrEndpoint *, _nm_auto_unref_sockaddrendpoint, nm_sock_addr_endpoint_unref)
+
+/*****************************************************************************/
+
+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;
 
 typedef GVariant *(*NMSettingPropertyGetFunc)           (NMSetting     *setting,
                                                          const char    *property);
@@ -637,7 +652,7 @@ typedef GVariant *(*NMSettingPropertyTransformToFunc)   (const GValue *from);
 typedef void      (*NMSettingPropertyTransformFromFunc) (GVariant *from,
                                                           GValue *to);
 
-typedef struct {
+struct _NMSettInfoProperty {
 	const char *name;
 	GParamSpec *param_spec;
 	const GVariantType *dbus_type;
@@ -649,7 +664,7 @@ typedef struct {
 
 	NMSettingPropertyTransformToFunc   to_dbus;
 	NMSettingPropertyTransformFromFunc from_dbus;
-} NMSettInfoProperty;
+};
 
 typedef struct {
 	const GVariantType *(*get_variant_type) (const struct _NMSettInfoSetting *sett_info,
@@ -731,4 +746,29 @@ GBytes *_nm_setting_802_1x_cert_value_to_bytes (NMSetting8021xCKScheme scheme,
 
 /*****************************************************************************/
 
+GVariant *_nm_connection_for_each_secret (NMConnection *self,
+                                          GVariant *secrets,
+                                          gboolean remove_non_secrets,
+                                          _NMConnectionForEachSecretFunc callback,
+                                          gpointer callback_data);
+
+typedef gboolean (*NMConnectionFindSecretFunc) (NMSettingSecretFlags flags,
+                                                gpointer user_data);
+
+gboolean _nm_connection_find_secret (NMConnection *self,
+                                     GVariant *secrets,
+                                     NMConnectionFindSecretFunc callback,
+                                     gpointer callback_data);
+
+/*****************************************************************************/
+
+#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_base64secret_normalize (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 89d99579..f20ffc09 100644
--- a/libnm-core/nm-core-types.h
+++ b/libnm-core/nm-core-types.h
@@ -43,12 +43,11 @@ typedef struct _NMSettingDummy            NMSettingDummy;
 typedef struct _NMSettingEthtool          NMSettingEthtool;
 typedef struct _NMSettingGeneric          NMSettingGeneric;
 typedef struct _NMSettingGsm              NMSettingGsm;
-typedef struct _NMSettingInfiniband       NMSettingInfiniband;
-typedef struct _NMSettingIPConfig         NMSettingIPConfig;
-typedef struct _NMSettingIPTunnel         NMSettingIPTunnel;
-typedef struct _NMSettingProxy            NMSettingProxy;
 typedef struct _NMSettingIP4Config        NMSettingIP4Config;
 typedef struct _NMSettingIP6Config        NMSettingIP6Config;
+typedef struct _NMSettingIPConfig         NMSettingIPConfig;
+typedef struct _NMSettingIPTunnel         NMSettingIPTunnel;
+typedef struct _NMSettingInfiniband       NMSettingInfiniband;
 typedef struct _NMSettingMacsec           NMSettingMacsec;
 typedef struct _NMSettingMacvlan          NMSettingMacvlan;
 typedef struct _NMSettingMatch            NMSettingMatch;
@@ -59,6 +58,7 @@ typedef struct _NMSettingOvsPatch         NMSettingOvsPatch;
 typedef struct _NMSettingOvsPort          NMSettingOvsPort;
 typedef struct _NMSettingPpp              NMSettingPpp;
 typedef struct _NMSettingPppoe            NMSettingPppoe;
+typedef struct _NMSettingProxy            NMSettingProxy;
 typedef struct _NMSettingSerial           NMSettingSerial;
 typedef struct _NMSettingSriov            NMSettingSriov;
 typedef struct _NMSettingTCConfig         NMSettingTCConfig;
@@ -69,8 +69,10 @@ typedef struct _NMSettingUser             NMSettingUser;
 typedef struct _NMSettingVlan             NMSettingVlan;
 typedef struct _NMSettingVpn              NMSettingVpn;
 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-crypto.c b/libnm-core/nm-crypto.c
index 1af2ea28..67f206c4 100644
--- a/libnm-core/nm-crypto.c
+++ b/libnm-core/nm-crypto.c
@@ -25,7 +25,6 @@
 
 #include "nm-crypto.h"
 
-#include <string.h>
 #include <strings.h>
 #include <unistd.h>
 #include <stdlib.h>
diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h
index aac4d7ea..038d2c9e 100644
--- a/libnm-core/nm-dbus-interface.h
+++ b/libnm-core/nm-dbus-interface.h
@@ -38,48 +38,52 @@
  */
 #define NM_DBUS_SERVICE                        "org.freedesktop.NetworkManager"
 
-#define NM_DBUS_PATH                           "/org/freedesktop/NetworkManager"
 #define NM_DBUS_INTERFACE                      "org.freedesktop.NetworkManager"
+#define NM_DBUS_INTERFACE_ACCESS_POINT         NM_DBUS_INTERFACE ".AccessPoint"
+#define NM_DBUS_INTERFACE_ACTIVE_CONNECTION    NM_DBUS_INTERFACE ".Connection.Active"
+#define NM_DBUS_INTERFACE_CHECKPOINT           NM_DBUS_INTERFACE ".Checkpoint"
 #define NM_DBUS_INTERFACE_DEVICE               NM_DBUS_INTERFACE ".Device"
-#define NM_DBUS_INTERFACE_DEVICE_WIRED         NM_DBUS_INTERFACE_DEVICE ".Wired"
+#define NM_DBUS_INTERFACE_DEVICE_6LOWPAN       NM_DBUS_INTERFACE_DEVICE ".Lowpan"
 #define NM_DBUS_INTERFACE_DEVICE_ADSL          NM_DBUS_INTERFACE_DEVICE ".Adsl"
-#define NM_DBUS_INTERFACE_DEVICE_WIRELESS      NM_DBUS_INTERFACE_DEVICE ".Wireless"
 #define NM_DBUS_INTERFACE_DEVICE_BLUETOOTH     NM_DBUS_INTERFACE_DEVICE ".Bluetooth"
-#define NM_DBUS_INTERFACE_DEVICE_OLPC_MESH     NM_DBUS_INTERFACE_DEVICE ".OlpcMesh"
-#define NM_DBUS_INTERFACE_DEVICE_OVS_INTERFACE NM_DBUS_INTERFACE_DEVICE ".OvsInterface"
-#define NM_DBUS_INTERFACE_DEVICE_OVS_PORT      NM_DBUS_INTERFACE_DEVICE ".OvsPort"
-#define NM_DBUS_INTERFACE_DEVICE_OVS_BRIDGE    NM_DBUS_INTERFACE_DEVICE ".OvsBridge"
-#define NM_DBUS_PATH_ACCESS_POINT              NM_DBUS_PATH "/AccessPoint"
-#define NM_DBUS_INTERFACE_ACCESS_POINT         NM_DBUS_INTERFACE ".AccessPoint"
-#define NM_DBUS_INTERFACE_DEVICE_MODEM         NM_DBUS_INTERFACE_DEVICE ".Modem"
-#define NM_DBUS_INTERFACE_DEVICE_WIMAX         NM_DBUS_INTERFACE_DEVICE ".WiMax"
-#define NM_DBUS_INTERFACE_WIMAX_NSP            NM_DBUS_INTERFACE ".WiMax.Nsp"
-#define NM_DBUS_PATH_WIMAX_NSP                 NM_DBUS_PATH "/Nsp"
-#define NM_DBUS_INTERFACE_ACTIVE_CONNECTION    NM_DBUS_INTERFACE ".Connection.Active"
-#define NM_DBUS_INTERFACE_IP4_CONFIG           NM_DBUS_INTERFACE ".IP4Config"
-#define NM_DBUS_INTERFACE_DHCP4_CONFIG         NM_DBUS_INTERFACE ".DHCP4Config"
-#define NM_DBUS_INTERFACE_IP6_CONFIG           NM_DBUS_INTERFACE ".IP6Config"
-#define NM_DBUS_INTERFACE_DHCP6_CONFIG         NM_DBUS_INTERFACE ".DHCP6Config"
-#define NM_DBUS_INTERFACE_DEVICE_INFINIBAND    NM_DBUS_INTERFACE_DEVICE ".Infiniband"
 #define NM_DBUS_INTERFACE_DEVICE_BOND          NM_DBUS_INTERFACE_DEVICE ".Bond"
-#define NM_DBUS_INTERFACE_DEVICE_DUMMY         NM_DBUS_INTERFACE_DEVICE ".Dummy"
-#define NM_DBUS_INTERFACE_DEVICE_TEAM          NM_DBUS_INTERFACE_DEVICE ".Team"
-#define NM_DBUS_INTERFACE_DEVICE_VLAN          NM_DBUS_INTERFACE_DEVICE ".Vlan"
 #define NM_DBUS_INTERFACE_DEVICE_BRIDGE        NM_DBUS_INTERFACE_DEVICE ".Bridge"
+#define NM_DBUS_INTERFACE_DEVICE_DUMMY         NM_DBUS_INTERFACE_DEVICE ".Dummy"
 #define NM_DBUS_INTERFACE_DEVICE_GENERIC       NM_DBUS_INTERFACE_DEVICE ".Generic"
-#define NM_DBUS_INTERFACE_DEVICE_VETH          NM_DBUS_INTERFACE_DEVICE ".Veth"
-#define NM_DBUS_INTERFACE_DEVICE_TUN           NM_DBUS_INTERFACE_DEVICE ".Tun"
+#define NM_DBUS_INTERFACE_DEVICE_GRE           NM_DBUS_INTERFACE_DEVICE ".Gre"
+#define NM_DBUS_INTERFACE_DEVICE_INFINIBAND    NM_DBUS_INTERFACE_DEVICE ".Infiniband"
+#define NM_DBUS_INTERFACE_DEVICE_IP_TUNNEL     NM_DBUS_INTERFACE_DEVICE ".IPTunnel"
 #define NM_DBUS_INTERFACE_DEVICE_MACSEC        NM_DBUS_INTERFACE_DEVICE ".Macsec"
 #define NM_DBUS_INTERFACE_DEVICE_MACVLAN       NM_DBUS_INTERFACE_DEVICE ".Macvlan"
+#define NM_DBUS_INTERFACE_DEVICE_MODEM         NM_DBUS_INTERFACE_DEVICE ".Modem"
+#define NM_DBUS_INTERFACE_DEVICE_OLPC_MESH     NM_DBUS_INTERFACE_DEVICE ".OlpcMesh"
+#define NM_DBUS_INTERFACE_DEVICE_OVS_BRIDGE    NM_DBUS_INTERFACE_DEVICE ".OvsBridge"
+#define NM_DBUS_INTERFACE_DEVICE_OVS_INTERFACE NM_DBUS_INTERFACE_DEVICE ".OvsInterface"
+#define NM_DBUS_INTERFACE_DEVICE_OVS_PORT      NM_DBUS_INTERFACE_DEVICE ".OvsPort"
 #define NM_DBUS_INTERFACE_DEVICE_PPP           NM_DBUS_INTERFACE_DEVICE ".Ppp"
+#define NM_DBUS_INTERFACE_DEVICE_STATISTICS    NM_DBUS_INTERFACE_DEVICE ".Statistics"
+#define NM_DBUS_INTERFACE_DEVICE_TEAM          NM_DBUS_INTERFACE_DEVICE ".Team"
+#define NM_DBUS_INTERFACE_DEVICE_TUN           NM_DBUS_INTERFACE_DEVICE ".Tun"
+#define NM_DBUS_INTERFACE_DEVICE_VETH          NM_DBUS_INTERFACE_DEVICE ".Veth"
+#define NM_DBUS_INTERFACE_DEVICE_VLAN          NM_DBUS_INTERFACE_DEVICE ".Vlan"
 #define NM_DBUS_INTERFACE_DEVICE_VXLAN         NM_DBUS_INTERFACE_DEVICE ".Vxlan"
+#define NM_DBUS_INTERFACE_DEVICE_WIFI_P2P      NM_DBUS_INTERFACE_DEVICE ".WifiP2P"
+#define NM_DBUS_INTERFACE_DEVICE_WIMAX         NM_DBUS_INTERFACE_DEVICE ".WiMax"
+#define NM_DBUS_INTERFACE_DEVICE_WIRED         NM_DBUS_INTERFACE_DEVICE ".Wired"
 #define NM_DBUS_INTERFACE_DEVICE_WIREGUARD     NM_DBUS_INTERFACE_DEVICE ".WireGuard"
-#define NM_DBUS_INTERFACE_DEVICE_GRE           NM_DBUS_INTERFACE_DEVICE ".Gre"
-#define NM_DBUS_INTERFACE_DEVICE_IP_TUNNEL     NM_DBUS_INTERFACE_DEVICE ".IPTunnel"
+#define NM_DBUS_INTERFACE_DEVICE_WIRELESS      NM_DBUS_INTERFACE_DEVICE ".Wireless"
 #define NM_DBUS_INTERFACE_DEVICE_WPAN          NM_DBUS_INTERFACE_DEVICE ".Wpan"
-#define NM_DBUS_INTERFACE_DEVICE_6LOWPAN       NM_DBUS_INTERFACE_DEVICE ".Lowpan"
-#define NM_DBUS_INTERFACE_DEVICE_STATISTICS    NM_DBUS_INTERFACE_DEVICE ".Statistics"
-#define NM_DBUS_INTERFACE_CHECKPOINT           NM_DBUS_INTERFACE ".Checkpoint"
+#define NM_DBUS_INTERFACE_DHCP4_CONFIG         NM_DBUS_INTERFACE ".DHCP4Config"
+#define NM_DBUS_INTERFACE_DHCP6_CONFIG         NM_DBUS_INTERFACE ".DHCP6Config"
+#define NM_DBUS_INTERFACE_IP4_CONFIG           NM_DBUS_INTERFACE ".IP4Config"
+#define NM_DBUS_INTERFACE_IP6_CONFIG           NM_DBUS_INTERFACE ".IP6Config"
+#define NM_DBUS_INTERFACE_WIFI_P2P_PEER        NM_DBUS_INTERFACE ".WifiP2PPeer"
+#define NM_DBUS_INTERFACE_WIMAX_NSP            NM_DBUS_INTERFACE ".WiMax.Nsp"
+
+#define NM_DBUS_PATH                           "/org/freedesktop/NetworkManager"
+#define NM_DBUS_PATH_ACCESS_POINT              NM_DBUS_PATH "/AccessPoint"
+#define NM_DBUS_PATH_WIFI_P2P_PEER             NM_DBUS_PATH "/WifiP2PPeer"
+#define NM_DBUS_PATH_WIMAX_NSP                 NM_DBUS_PATH "/Nsp"
 
 #define NM_DBUS_INTERFACE_SETTINGS        "org.freedesktop.NetworkManager.Settings"
 #define NM_DBUS_PATH_SETTINGS             "/org/freedesktop/NetworkManager/Settings"
@@ -218,6 +222,7 @@ typedef enum {
  * @NM_DEVICE_TYPE_WPAN: a IEEE 802.15.4 (WPAN) MAC Layer Device
  * @NM_DEVICE_TYPE_6LOWPAN: 6LoWPAN interface
  * @NM_DEVICE_TYPE_WIREGUARD: a WireGuard interface
+ * @NM_DEVICE_TYPE_WIFI_P2P: an 802.11 Wi-Fi P2P device (Since: 1.16)
  *
  * #NMDeviceType values indicate the type of hardware represented by a
  * device object.
@@ -253,6 +258,7 @@ typedef enum {
 	NM_DEVICE_TYPE_WPAN          = 27,
 	NM_DEVICE_TYPE_6LOWPAN       = 28,
 	NM_DEVICE_TYPE_WIREGUARD     = 29,
+	NM_DEVICE_TYPE_WIFI_P2P      = 30,
 } NMDeviceType;
 
 /**
@@ -344,6 +350,8 @@ typedef enum { /*< underscore_name=nm_802_11_ap_flags, flags >*/
  * supported
  * @NM_802_11_AP_SEC_KEY_MGMT_802_1X: 802.1x authentication and key management
  * is supported
+ * @NM_802_11_AP_SEC_KEY_MGMT_SAE: WPA/RSN Simultaneous Authentication of Equals is
+ * supported
  *
  * 802.11 access point security and authentication flags.  These flags describe
  * the current security requirements of an access point as determined from the
@@ -361,6 +369,7 @@ typedef enum { /*< underscore_name=nm_802_11_ap_security_flags, flags >*/
 	NM_802_11_AP_SEC_GROUP_CCMP      = 0x00000080,
 	NM_802_11_AP_SEC_KEY_MGMT_PSK    = 0x00000100,
 	NM_802_11_AP_SEC_KEY_MGMT_802_1X = 0x00000200,
+	NM_802_11_AP_SEC_KEY_MGMT_SAE    = 0x00000400,
 } NM80211ApSecurityFlags;
 
 /**
@@ -563,6 +572,7 @@ typedef enum {
  * @NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE: a duplicate IP address was detected
  * @NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED: The selected IP method is not supported
  * @NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED: configuration of SR-IOV parameters failed
+ * @NM_DEVICE_STATE_REASON_PEER_NOT_FOUND: The Wi-Fi P2P peer could not be found
  *
  * Device state change reason codes
  */
@@ -634,6 +644,7 @@ typedef enum {
 	NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE           = 64,
 	NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED          = 65,
 	NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED     = 66,
+	NM_DEVICE_STATE_REASON_PEER_NOT_FOUND                 = 67,
 } NMDeviceStateReason;
 
 /**
@@ -972,13 +983,13 @@ typedef enum { /*< flags >*/
 typedef enum { /*< flags >*/
 	NM_ACTIVATION_STATE_FLAG_NONE                                 = 0,
 
-	NM_ACTIVATION_STATE_FLAG_IS_MASTER                            = (1LL <<  0),
-	NM_ACTIVATION_STATE_FLAG_IS_SLAVE                             = (1LL <<  1),
-	NM_ACTIVATION_STATE_FLAG_LAYER2_READY                         = (1LL <<  2),
-	NM_ACTIVATION_STATE_FLAG_IP4_READY                            = (1LL <<  3),
-	NM_ACTIVATION_STATE_FLAG_IP6_READY                            = (1LL <<  4),
-	NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES                    = (1LL <<  5),
-	NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY = (1LL <<  6),
+	NM_ACTIVATION_STATE_FLAG_IS_MASTER                            = 0x1,
+	NM_ACTIVATION_STATE_FLAG_IS_SLAVE                             = 0x2,
+	NM_ACTIVATION_STATE_FLAG_LAYER2_READY                         = 0x4,
+	NM_ACTIVATION_STATE_FLAG_IP4_READY                            = 0x8,
+	NM_ACTIVATION_STATE_FLAG_IP6_READY                            = 0x10,
+	NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES                    = 0x20,
+	NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY = 0x40,
 } NMActivationStateFlags;
 
 /**
@@ -1017,12 +1028,12 @@ typedef enum { /*< flags >*/
  */
 typedef enum { /*< flags >*/
 	NM_SETTINGS_UPDATE2_FLAG_NONE                       = 0,
-	NM_SETTINGS_UPDATE2_FLAG_TO_DISK                    = (1LL <<  0),
-	NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY                  = (1LL <<  1),
-	NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED         = (1LL <<  2),
-	NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY             = (1LL <<  3),
-	NM_SETTINGS_UPDATE2_FLAG_VOLATILE                   = (1LL <<  4),
-	NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT          = (1LL <<  5),
+	NM_SETTINGS_UPDATE2_FLAG_TO_DISK                    = 0x1,
+	NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY                  = 0x2,
+	NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED         = 0x4,
+	NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY             = 0x8,
+	NM_SETTINGS_UPDATE2_FLAG_VOLATILE                   = 0x10,
+	NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT          = 0x20,
 } NMSettingsUpdate2Flags;
 
 /**
diff --git a/libnm-core/nm-dbus-types.xml b/libnm-core/nm-dbus-types.xml
index b824f78a..a65e1d08 100644
--- a/libnm-core/nm-dbus-types.xml
+++ b/libnm-core/nm-dbus-types.xml
@@ -307,6 +307,11 @@
               <entry role="enum_member_value"><para>= <literal>29</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>a WireGuard interface</para><para></para></entry>
             </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_DEVICE_TYPE_WIFI_P2P</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>30</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>an 802.11 Wi-Fi P2P device (Since: 1.16)</para><para></para></entry>
+            </row>
           </tbody>
         </tgroup>
       </informaltable>
@@ -552,6 +557,11 @@
               <entry role="enum_member_value"><para>= <literal>0x00000200</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>802.1x authentication and key management is supported</para><para></para></entry>
             </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_802_11_AP_SEC_KEY_MGMT_SAE</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x00000400</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>WPA/RSN Simultaneous Authentication of Equals is supported</para><para></para></entry>
+            </row>
           </tbody>
         </tgroup>
       </informaltable>
@@ -1152,6 +1162,11 @@
               <entry role="enum_member_value"><para>= <literal>66</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>configuration of SR-IOV parameters failed</para><para></para></entry>
             </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_DEVICE_STATE_REASON_PEER_NOT_FOUND</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>67</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>The Wi-Fi P2P peer could not be found</para><para></para></entry>
+            </row>
           </tbody>
         </tgroup>
       </informaltable>
@@ -1694,37 +1709,37 @@
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_ACTIVATION_STATE_FLAG_IS_MASTER</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x1</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>the device is a master.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_ACTIVATION_STATE_FLAG_IS_SLAVE</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x2</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>the device is a slave.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_ACTIVATION_STATE_FLAG_LAYER2_READY</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x4</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>layer2 is activated and ready.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_ACTIVATION_STATE_FLAG_IP4_READY</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x8</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>IPv4 setting is completed.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_ACTIVATION_STATE_FLAG_IP6_READY</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x10</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>IPv6 setting is completed.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x20</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>The master has any slave devices attached. This only makes sense if the device is a master.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x40</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>the lifetime of the activation is bound to the visilibity of the connection profile, which in turn depends on "connection.permissions" and whether a session for the user exists. Since: 1.16</para><para></para></entry>
             </row>
           </tbody>
@@ -1754,32 +1769,32 @@
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_TO_DISK</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x1</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>to persist the connection to disk.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x2</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>to make the connection in-memory only. If the connection was previously persistent, the corresponding file on disk is not deleted but merely the connection is decoupled from the file on disk. If you later delete an in-memory connection, the connection on disk will be deleted as well.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x4</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>this is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, but if the connection has a corresponding file on disk, the association between the connection and the file is forgotten but the file is not modified. The difference to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY is if you later save the connection again to disk, a new file name will be chosen without overwriting the remaining file on disk. Also, if you delete the connection later, the file on disk will not be deleted.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x8</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>this is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, but if the connection has a corresponding file on disk, the file on disk will be deleted.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_VOLATILE</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x10</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>This can be specified with either %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED or %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY. After making the connection in-memory only, the connection is marked as volatile. That means, if the connection is currently not active it will be deleted right away. Otherwise, it is marked to for deletion once the connection deactivates. A volatile connection cannot autoactivate again (because it's about to be deleted), but a manual activation will clear the volatile flag.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT</para><para></para></entry>
-              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x20</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>usually, when the connection has autoconnect enabled and is modified, it becomes eligible to autoconnect right away. Setting this flag, disables autoconnect until the connection is manually activated.</para><para></para></entry>
             </row>
           </tbody>
diff --git a/libnm-core/nm-dbus-utils.c b/libnm-core/nm-dbus-utils.c
index 69f4bb2f..8c455971 100644
--- a/libnm-core/nm-dbus-utils.c
+++ b/libnm-core/nm-dbus-utils.c
@@ -20,8 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "nm-core-internal.h"
 
 typedef struct {
diff --git a/libnm-core/nm-errors.c b/libnm-core/nm-errors.c
index 351bc1d0..beef95c7 100644
--- a/libnm-core/nm-errors.c
+++ b/libnm-core/nm-errors.c
@@ -20,9 +20,8 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "nm-errors.h"
+
 #include "nm-vpn-dbus-interface.h"
 #include "nm-core-internal.h"
 
diff --git a/libnm-core/nm-errors.h b/libnm-core/nm-errors.h
index bdd6c223..ef73790e 100644
--- a/libnm-core/nm-errors.h
+++ b/libnm-core/nm-errors.h
@@ -145,6 +145,7 @@ GQuark nm_crypto_error_quark (void);
  * @NM_DEVICE_ERROR_VERSION_ID_MISMATCH: the version id did not match.
  * @NM_DEVICE_ERROR_MISSING_DEPENDENCIES: the requested operation could not
  *   be completed due to missing dependencies.
+ * @NM_DEVICE_ERROR_INVALID_ARGUMENT: invalid argument. Since: 1.16
  *
  * Device-related errors.
  *
@@ -163,6 +164,7 @@ typedef enum {
 	NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND, /*< nick=SpecificObjectNotFound >*/
 	NM_DEVICE_ERROR_VERSION_ID_MISMATCH,       /*< nick=VersionIdMismatch >*/
 	NM_DEVICE_ERROR_MISSING_DEPENDENCIES,      /*< nick=MissingDependencies >*/
+	NM_DEVICE_ERROR_INVALID_ARGUMENT,          /*< nick=InvalidArgument >*/
 } NMDeviceError;
 
 #define NM_DEVICE_ERROR nm_device_error_quark ()
diff --git a/libnm-core/nm-keyfile-utils.c b/libnm-core/nm-keyfile-utils.c
index 9543ebde..e243150d 100644
--- a/libnm-core/nm-keyfile-utils.c
+++ b/libnm-core/nm-keyfile-utils.c
@@ -20,10 +20,10 @@
 
 #include "nm-default.h"
 
+#include "nm-keyfile-utils.h"
+
 #include <stdlib.h>
-#include <string.h>
 
-#include "nm-keyfile-utils.h"
 #include "nm-keyfile-internal.h"
 #include "nm-setting-wired.h"
 #include "nm-setting-wireless.h"
diff --git a/libnm-core/nm-keyfile-utils.h b/libnm-core/nm-keyfile-utils.h
index 0467230e..9403dfa3 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 e2068130..05c6bf97 100644
--- a/libnm-core/nm-keyfile.c
+++ b/libnm-core/nm-keyfile.c
@@ -23,17 +23,16 @@
 
 #include "nm-keyfile-internal.h"
 
-#include <errno.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <arpa/inet.h>
-#include <string.h>
 #include <linux/pkt_sched.h>
 
 #include "nm-utils/nm-secret-utils.h"
+#include "systemd/nm-sd-utils-shared.h"
 #include "nm-common-macros.h"
 #include "nm-core-internal.h"
 #include "nm-keyfile-utils.h"
@@ -2904,6 +2903,132 @@ out:
 }
 
 static void
+_read_setting_wireguard_peer (KeyfileReaderInfo *info)
+{
+	gs_unref_object NMSettingWireGuard *s_wg_new = NULL;
+	nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
+	gs_free_error GError *error = NULL;
+	NMSettingWireGuard *s_wg;
+	gs_free char *str = NULL;
+	const char *cstr = NULL;
+	const char *key;
+	gint64 i64;
+	gs_strfreev char **sa = NULL;
+	gsize n_sa;
+
+	peer = nm_wireguard_peer_new ();
+
+	nm_assert (g_str_has_prefix (info->group, NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER));
+	cstr = &info->group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)];
+	if (   !nm_utils_base64secret_normalize (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, TRUE);
+	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_wireguard_peer_set_preshared_key (peer, str, FALSE)) {
+			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;
+		}
+		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]) {
+		if (!nm_wireguard_peer_set_endpoint (peer, str, FALSE)) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("key '%s.%s' is not not a valid endpoint"),
+			                  info->group, key))
+				return;
+		}
+	}
+	nm_clear_g_free (&str);
+
+	key = NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS;
+	sa = nm_keyfile_plugin_kf_get_string_list (info->keyfile, info->group, key, &n_sa, NULL);
+	if (n_sa > 0) {
+		gboolean has_error = FALSE;
+		gsize i;
+
+		for (i = 0; i < n_sa; i++) {
+			if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC, sa[i], NULL, NULL, NULL)) {
+				has_error = TRUE;
+				continue;
+			}
+			nm_wireguard_peer_append_allowed_ip (peer, sa[i], TRUE);
+		}
+		if (has_error) {
+			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+			                  _("key '%s.%s' has invalid allowed-ips"),
+			                  info->group, key))
+				return;
+		}
+	}
+	nm_clear_pointer (&sa, g_strfreev);
+
+	if (info->error)
+		return;
+
+	if (!nm_wireguard_peer_is_valid (peer, TRUE, TRUE, &error)) {
+		if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+		                  _("peer '%s' is invalid: %s"),
+		                  info->group, error->message))
+			return;
+		return;
+	}
+
+	s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (info->connection, NM_TYPE_SETTING_WIREGUARD));
+	if (!s_wg) {
+		s_wg_new = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ());
+		s_wg = s_wg_new;
+	}
+
+	nm_setting_wireguard_append_peer (s_wg, peer);
+
+	if (s_wg_new) {
+		nm_connection_add_setting (info->connection,
+		                           NM_SETTING (g_steal_pointer (&s_wg_new)));
+	}
+}
+
+static void
 _read_setting_vpn_secrets (KeyfileReaderInfo *info)
 {
 	gs_strfreev char **keys = NULL;
@@ -3023,7 +3148,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;
@@ -3200,6 +3327,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,
@@ -3277,6 +3490,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-property-compare.c b/libnm-core/nm-property-compare.c
index 2a3ee397..d3a19ec7 100644
--- a/libnm-core/nm-property-compare.c
+++ b/libnm-core/nm-property-compare.c
@@ -22,11 +22,10 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-#include <netinet/in.h>
-
 #include "nm-property-compare.h"
 
+#include <netinet/in.h>
+
 static int
 _nm_property_compare_collection (GVariant *value1, GVariant *value2)
 {
diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c
index 19741fdf..8a23fb34 100644
--- a/libnm-core/nm-setting-8021x.c
+++ b/libnm-core/nm-setting-8021x.c
@@ -24,8 +24,6 @@
 
 #include "nm-setting-8021x.h"
 
-#include <string.h>
-
 #include "nm-utils/nm-secret-utils.h"
 #include "nm-utils.h"
 #include "nm-crypto.h"
@@ -202,7 +200,7 @@ G_DEFINE_TYPE (NMSetting8021x, nm_setting_802_1x, NM_TYPE_SETTING)
  * nm_setting_802_1x_check_cert_scheme:
  * @pdata: (allow-none): the data pointer
  * @length: the length of the data
- * @error: (allow-none): (out): validation reason
+ * @error: (allow-none) (out): validation reason
  *
  * Determines and verifies the blob type.
  * When setting certificate properties of NMSetting8021x
diff --git a/libnm-core/nm-setting-8021x.h b/libnm-core/nm-setting-8021x.h
index 82ef5eba..eb7b1948 100644
--- a/libnm-core/nm-setting-8021x.h
+++ b/libnm-core/nm-setting-8021x.h
@@ -90,12 +90,11 @@ typedef enum { /*< underscore_name=nm_setting_802_1x_ck_scheme >*/
  */
 typedef enum { /*< underscore_name=nm_setting_802_1x_auth_flags >*/
 	NM_SETTING_802_1X_AUTH_FLAGS_NONE                = 0,
-	NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_0_DISABLE     = (1 << 0),
-	NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_1_DISABLE     = (1 << 1),
-	NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_2_DISABLE     = (1 << 2),
+	NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_0_DISABLE     = 0x1,
+	NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_1_DISABLE     = 0x2,
+	NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_2_DISABLE     = 0x4,
 
-	_NM_SETTING_802_1X_AUTH_FLAGS_LAST, /*< skip >*/
-	NM_SETTING_802_1X_AUTH_FLAGS_ALL                 = (((_NM_SETTING_802_1X_AUTH_FLAGS_LAST - 1) << 1) - 1),
+	NM_SETTING_802_1X_AUTH_FLAGS_ALL                 = 0x7,
 } NMSetting8021xAuthFlags;
 
 #define NM_TYPE_SETTING_802_1X            (nm_setting_802_1x_get_type ())
diff --git a/libnm-core/nm-setting-adsl.c b/libnm-core/nm-setting-adsl.c
index b7f7d746..b2de7027 100644
--- a/libnm-core/nm-setting-adsl.c
+++ b/libnm-core/nm-setting-adsl.c
@@ -23,8 +23,6 @@
 
 #include "nm-setting-adsl.h"
 
-#include <string.h>
-
 #include "nm-setting-ppp.h"
 #include "nm-setting-private.h"
 #include "nm-utils.h"
diff --git a/libnm-core/nm-setting-bluetooth.c b/libnm-core/nm-setting-bluetooth.c
index a3192840..bc4f8bd0 100644
--- a/libnm-core/nm-setting-bluetooth.c
+++ b/libnm-core/nm-setting-bluetooth.c
@@ -24,7 +24,6 @@
 
 #include "nm-setting-bluetooth.h"
 
-#include <string.h>
 #include <net/ethernet.h>
 
 #include "nm-connection-private.h"
diff --git a/libnm-core/nm-setting-bond.c b/libnm-core/nm-setting-bond.c
index fa55c06f..51ce2deb 100644
--- a/libnm-core/nm-setting-bond.c
+++ b/libnm-core/nm-setting-bond.c
@@ -23,9 +23,7 @@
 
 #include "nm-setting-bond.h"
 
-#include <string.h>
 #include <stdlib.h>
-#include <errno.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
@@ -177,19 +175,14 @@ nm_setting_bond_get_option (NMSettingBond *setting,
 static gboolean
 validate_int (const char *name, const char *value, const BondDefault *def)
 {
-	long num;
-	guint i;
+	guint64 num;
 
-	for (i = 0; i < strlen (value); i++) {
-		if (!g_ascii_isdigit (value[i]) && value[i] != '-')
-			return FALSE;
-	}
-
-	errno = 0;
-	num = strtol (value, NULL, 10);
-	if (errno)
+	if (!NM_STRCHAR_ALL (value, ch, g_ascii_isdigit (ch)))
 		return FALSE;
-	if (num < def->min || num > def->max)
+
+	num = _nm_utils_ascii_str_to_uint64 (value, 10, def->min, def->max, G_MAXUINT64);
+	if (   num == G_MAXUINT64
+	    && errno != 0)
 		return FALSE;
 
 	return TRUE;
diff --git a/libnm-core/nm-setting-bridge-port.c b/libnm-core/nm-setting-bridge-port.c
index b8670afd..614c883c 100644
--- a/libnm-core/nm-setting-bridge-port.c
+++ b/libnm-core/nm-setting-bridge-port.c
@@ -23,7 +23,6 @@
 
 #include "nm-setting-bridge-port.h"
 
-#include <string.h>
 #include <ctype.h>
 #include <stdlib.h>
 
diff --git a/libnm-core/nm-setting-bridge.c b/libnm-core/nm-setting-bridge.c
index 328c1fd3..e418013c 100644
--- a/libnm-core/nm-setting-bridge.c
+++ b/libnm-core/nm-setting-bridge.c
@@ -23,7 +23,6 @@
 
 #include "nm-setting-bridge.h"
 
-#include <string.h>
 #include <ctype.h>
 #include <stdlib.h>
 
@@ -201,7 +200,7 @@ nm_setting_bridge_get_multicast_snooping (NMSettingBridge *setting)
 	return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_snooping;
 }
 
-static inline gboolean
+static gboolean
 check_range (guint32 val,
              guint32 min,
              guint32 max,
diff --git a/libnm-core/nm-setting-cdma.c b/libnm-core/nm-setting-cdma.c
index 1e5cd81e..b05daaff 100644
--- a/libnm-core/nm-setting-cdma.c
+++ b/libnm-core/nm-setting-cdma.c
@@ -23,8 +23,6 @@
 
 #include "nm-setting-cdma.h"
 
-#include <string.h>
-
 #include "nm-utils.h"
 #include "nm-setting-private.h"
 #include "nm-core-enum-types.h"
diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c
index a32044d2..e8d008e3 100644
--- a/libnm-core/nm-setting-connection.c
+++ b/libnm-core/nm-setting-connection.c
@@ -24,8 +24,6 @@
 
 #include "nm-setting-connection.h"
 
-#include <string.h>
-
 #include "nm-utils.h"
 #include "nm-utils-private.h"
 #include "nm-core-enum-types.h"
@@ -1633,7 +1631,7 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	 * at runtime. Currently supported are "${CONNECTION}", "${DEVICE}", "${MAC}",
 	 * "${BOOT}", "${RANDOM}".
 	 * These effectively create unique IDs per-connection, per-device, per-boot,
-	 * or every time. Note that "${DEVICE}" corresponds the the interface name of the
+	 * or every time. Note that "${DEVICE}" corresponds to the interface name of the
 	 * device and "${MAC}" is the permanent MAC address of the device.
 	 * Any unrecognized patterns following '$' are treated verbatim, however
 	 * are reserved for future use. You are thus advised to avoid '$' or
diff --git a/libnm-core/nm-setting-dcb.c b/libnm-core/nm-setting-dcb.c
index 42c7d247..419d9825 100644
--- a/libnm-core/nm-setting-dcb.c
+++ b/libnm-core/nm-setting-dcb.c
@@ -23,8 +23,6 @@
 
 #include "nm-setting-dcb.h"
 
-#include <string.h>
-
 #include "nm-utils.h"
 #include "nm-utils-private.h"
 #include "nm-setting-private.h"
@@ -716,7 +714,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 
 G_STATIC_ASSERT (sizeof (guint) == sizeof (gboolean));
 
-static inline void
+static void
 set_array_from_gvalue (const GValue *v, uint *a, size_t len)
 {
 	GArray *src = g_value_get_boxed (v);
@@ -731,7 +729,7 @@ set_array_from_gvalue (const GValue *v, uint *a, size_t len)
 }
 #define SET_ARRAY_FROM_GVALUE(v, a)  set_array_from_gvalue (v, a, G_N_ELEMENTS (a))
 
-static inline void
+static void
 set_gvalue_from_array (GValue *v, uint *a, size_t len)
 {
 	GArray *src = g_array_sized_new (FALSE, TRUE, sizeof (guint), len);
diff --git a/libnm-core/nm-setting-gsm.c b/libnm-core/nm-setting-gsm.c
index 68d29032..e6784e42 100644
--- a/libnm-core/nm-setting-gsm.c
+++ b/libnm-core/nm-setting-gsm.c
@@ -24,8 +24,6 @@
 
 #include "nm-setting-gsm.h"
 
-#include <string.h>
-
 #include "nm-utils.h"
 #include "nm-setting-private.h"
 #include "nm-core-enum-types.h"
diff --git a/libnm-core/nm-setting-ip-config.c b/libnm-core/nm-setting-ip-config.c
index 8fff030e..5250ae43 100644
--- a/libnm-core/nm-setting-ip-config.c
+++ b/libnm-core/nm-setting-ip-config.c
@@ -24,7 +24,6 @@
 
 #include "nm-setting-ip-config.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 
 #include "nm-setting-ip4-config.h"
@@ -1126,7 +1125,7 @@ _nm_ip_route_get_attributes_direct (NMIPRoute *route)
  * @route: the #NMIPRoute
  * @sorted: whether to sort the names. Otherwise, their order is
  *   undefined and unstable.
- * @out_length: (allow-none): (out): the number of elements
+ * @out_length: (allow-none) (out): the number of elements
  *
  * Gets an array of attribute names defined on @route.
  *
diff --git a/libnm-core/nm-setting-ip-config.h b/libnm-core/nm-setting-ip-config.h
index 882a88b6..39cb36a4 100644
--- a/libnm-core/nm-setting-ip-config.h
+++ b/libnm-core/nm-setting-ip-config.h
@@ -94,9 +94,9 @@ void         nm_ip_route_unref               (NMIPRoute  *route);
 gboolean     nm_ip_route_equal               (NMIPRoute  *route,
                                               NMIPRoute  *other);
 
-enum {
+enum { /*< flags >*/
 	NM_IP_ROUTE_EQUAL_CMP_FLAGS_NONE         = 0,
-	NM_IP_ROUTE_EQUAL_CMP_FLAGS_WITH_ATTRS   = (1LL <<  0),
+	NM_IP_ROUTE_EQUAL_CMP_FLAGS_WITH_ATTRS   = 0x1,
 };
 
 NM_AVAILABLE_IN_1_10
diff --git a/libnm-core/nm-setting-ip4-config.c b/libnm-core/nm-setting-ip4-config.c
index 59529bc2..dd3b79b7 100644
--- a/libnm-core/nm-setting-ip4-config.c
+++ b/libnm-core/nm-setting-ip4-config.c
@@ -23,8 +23,6 @@
 
 #include "nm-setting-ip4-config.h"
 
-#include <string.h>
-
 #include "nm-setting-private.h"
 
 /**
diff --git a/libnm-core/nm-setting-ip6-config.c b/libnm-core/nm-setting-ip6-config.c
index bf3acf96..d6c085ac 100644
--- a/libnm-core/nm-setting-ip6-config.c
+++ b/libnm-core/nm-setting-ip6-config.c
@@ -23,7 +23,6 @@
 
 #include "nm-setting-ip6-config.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 
 #include "nm-setting-private.h"
diff --git a/libnm-core/nm-setting-macsec.c b/libnm-core/nm-setting-macsec.c
index 3d93ac5a..040aa17d 100644
--- a/libnm-core/nm-setting-macsec.c
+++ b/libnm-core/nm-setting-macsec.c
@@ -23,7 +23,6 @@
 #include "nm-setting-macsec.h"
 
 #include <stdlib.h>
-#include <string.h>
 
 #include "nm-utils/nm-secret-utils.h"
 
diff --git a/libnm-core/nm-setting-macvlan.c b/libnm-core/nm-setting-macvlan.c
index a25e1055..332dc007 100644
--- a/libnm-core/nm-setting-macvlan.c
+++ b/libnm-core/nm-setting-macvlan.c
@@ -24,7 +24,6 @@
 #include "nm-setting-macvlan.h"
 
 #include <stdlib.h>
-#include <string.h>
 
 #include "nm-utils.h"
 #include "nm-setting-connection.h"
diff --git a/libnm-core/nm-setting-olpc-mesh.c b/libnm-core/nm-setting-olpc-mesh.c
index eb87e1ff..f6a32dbb 100644
--- a/libnm-core/nm-setting-olpc-mesh.c
+++ b/libnm-core/nm-setting-olpc-mesh.c
@@ -24,8 +24,6 @@
 
 #include "nm-setting-olpc-mesh.h"
 
-#include <string.h>
-
 #include "nm-utils.h"
 #include "nm-utils-private.h"
 #include "nm-setting-private.h"
diff --git a/libnm-core/nm-setting-pppoe.c b/libnm-core/nm-setting-pppoe.c
index e3166888..e2d516c5 100644
--- a/libnm-core/nm-setting-pppoe.c
+++ b/libnm-core/nm-setting-pppoe.c
@@ -24,8 +24,6 @@
 
 #include "nm-setting-pppoe.h"
 
-#include <string.h>
-
 #include "nm-setting-ppp.h"
 #include "nm-setting-private.h"
 #include "nm-core-enum-types.h"
diff --git a/libnm-core/nm-setting-private.h b/libnm-core/nm-setting-private.h
index 8f61b0ff..2af74ec4 100644
--- a/libnm-core/nm-setting-private.h
+++ b/libnm-core/nm-setting-private.h
@@ -39,6 +39,8 @@ int _nm_setting_compare_priority (gconstpointer a, gconstpointer b);
 
 /*****************************************************************************/
 
+void _nm_setting_emit_property_changed (NMSetting *setting);
+
 typedef enum NMSettingUpdateSecretResult {
 	NM_SETTING_UPDATE_SECRET_ERROR              = FALSE,
 	NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED   = TRUE,
@@ -99,9 +101,6 @@ gboolean _nm_setting_verify_secret_string (const char *str,
 gboolean _nm_setting_aggregate (NMSetting *setting,
                                 NMConnectionAggregateType type,
                                 gpointer arg);
-gboolean _nm_setting_vpn_aggregate (NMSettingVpn *setting,
-                                    NMConnectionAggregateType type,
-                                    gpointer arg);
 
 gboolean _nm_setting_slave_type_is_valid (const char *slave_type, const char **out_port_type);
 
diff --git a/libnm-core/nm-setting-serial.c b/libnm-core/nm-setting-serial.c
index 8b8bb76d..36bc4f69 100644
--- a/libnm-core/nm-setting-serial.c
+++ b/libnm-core/nm-setting-serial.c
@@ -24,8 +24,6 @@
 
 #include "nm-setting-serial.h"
 
-#include <string.h>
-
 #include "nm-setting-private.h"
 
 /**
diff --git a/libnm-core/nm-setting-sriov.c b/libnm-core/nm-setting-sriov.c
index 068d2185..9c7306e0 100644
--- a/libnm-core/nm-setting-sriov.c
+++ b/libnm-core/nm-setting-sriov.c
@@ -919,7 +919,7 @@ vfs_to_dbus (NMSetting *setting, const char *property)
 			GVariantBuilder vf_builder;
 			const guint *vlan_ids;
 			const char **name;
-			guint num_vlans;
+			guint num_vlans = 0;
 
 			g_variant_builder_init (&vf_builder, G_VARIANT_TYPE_VARDICT);
 			g_variant_builder_add (&vf_builder, "{sv}", "index",
diff --git a/libnm-core/nm-setting-team-port.c b/libnm-core/nm-setting-team-port.c
index 5277e9c0..1db8f22a 100644
--- a/libnm-core/nm-setting-team-port.c
+++ b/libnm-core/nm-setting-team-port.c
@@ -23,7 +23,6 @@
 
 #include "nm-setting-team-port.h"
 
-#include <string.h>
 #include <ctype.h>
 #include <stdlib.h>
 
@@ -42,7 +41,6 @@
 
 /*****************************************************************************/
 
-/* Keep aligned with _prop_to_keys[] */
 NM_GOBJECT_PROPERTIES_DEFINE (NMSettingTeamPort,
 	PROP_CONFIG,
 	PROP_QUEUE_ID,
@@ -53,15 +51,14 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingTeamPort,
 	PROP_LINK_WATCHERS,
 );
 
-/* Keep aligned with team-port properties enum */
 static const _NMUtilsTeamPropertyKeys _prop_to_keys[_PROPERTY_ENUMS_LAST] = {
-	[PROP_CONFIG] =        { NULL, NULL, NULL, 0 },
-	[PROP_QUEUE_ID] =      { "queue_id", NULL, NULL, NM_SETTING_TEAM_PORT_QUEUE_ID_DEFAULT },
-	[PROP_PRIO] =          { "prio", NULL, NULL, 0 },
-	[PROP_STICKY] =        { "sticky", NULL, NULL, 0 },
-	[PROP_LACP_PRIO] =     { "lacp_prio", NULL, NULL, NM_SETTING_TEAM_PORT_LACP_PRIO_DEFAULT },
-	[PROP_LACP_KEY] =      { "lacp_key", NULL, NULL, 0 },
-	[PROP_LINK_WATCHERS] = { "link_watch", NULL, NULL, 0 }
+	[PROP_CONFIG] =        { },
+	[PROP_QUEUE_ID] =      { .key1 = "queue_id",   .default_int = NM_SETTING_TEAM_PORT_QUEUE_ID_DEFAULT, },
+	[PROP_PRIO] =          { .key1 = "prio",       },
+	[PROP_STICKY] =        { .key1 = "sticky",     },
+	[PROP_LACP_PRIO] =     { .key1 = "lacp_prio",  .default_int = NM_SETTING_TEAM_PORT_LACP_PRIO_DEFAULT, },
+	[PROP_LACP_KEY] =      { .key1 = "lacp_key",   },
+	[PROP_LINK_WATCHERS] = { .key1 = "link_watch", },
 };
 
 typedef struct {
diff --git a/libnm-core/nm-setting-team.c b/libnm-core/nm-setting-team.c
index 3161c05f..42ee3eec 100644
--- a/libnm-core/nm-setting-team.c
+++ b/libnm-core/nm-setting-team.c
@@ -23,7 +23,6 @@
 
 #include "nm-setting-team.h"
 
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-utils.h"
@@ -84,10 +83,10 @@ struct NMTeamLinkWatcher {
 			int init_wait;
 			int interval;
 			int missed_max;
-			int vlanid;
 			char *target_host;
 			char *source_host;
 			NMTeamLinkWatcherArpPingFlags flags;
+			int vlanid;
 		} arp_ping;
 	};
 };
@@ -625,7 +624,6 @@ nm_team_link_watcher_get_flags (NMTeamLinkWatcher *watcher)
 
 /*****************************************************************************/
 
-/* Keep aligned with _prop_to_keys[] */
 NM_GOBJECT_PROPERTIES_DEFINE (NMSettingTeam,
 	PROP_CONFIG,
 	PROP_NOTIFY_PEERS_COUNT,
@@ -645,25 +643,23 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingTeam,
 	PROP_LINK_WATCHERS,
 );
 
-/* Keep aligned with team properties enum */
 static const _NMUtilsTeamPropertyKeys _prop_to_keys[_PROPERTY_ENUMS_LAST] = {
-	[PROP_CONFIG] =                      { NULL, NULL, NULL, 0 },
-	[PROP_NOTIFY_PEERS_COUNT] =          { "notify_peers", "count", NULL, 0 },
-	[PROP_NOTIFY_PEERS_INTERVAL] =       { "notify_peers", "interval", NULL, 0 },
-	[PROP_MCAST_REJOIN_COUNT] =          { "mcast_rejoin", "count", NULL, 0 },
-	[PROP_MCAST_REJOIN_INTERVAL] =       { "mcast_rejoin", "interval", NULL, 0 },
-	[PROP_RUNNER] =                      { "runner", "name", NULL,
-	                                       {.default_str = NM_SETTING_TEAM_RUNNER_DEFAULT} },
-	[PROP_RUNNER_HWADDR_POLICY] =        { "runner", "hwaddr_policy", NULL, 0 },
-	[PROP_RUNNER_TX_HASH] =              { "runner", "tx_hash", NULL, 0 },
-	[PROP_RUNNER_TX_BALANCER] =          { "runner", "tx_balancer", "name", 0 },
-	[PROP_RUNNER_TX_BALANCER_INTERVAL] = { "runner", "tx_balancer", "balancing_interval", -1 },
-	[PROP_RUNNER_ACTIVE] =               { "runner", "active", NULL, 0 },
-	[PROP_RUNNER_FAST_RATE] =            { "runner", "fast_rate", NULL, 0 },
-	[PROP_RUNNER_SYS_PRIO] =             { "runner", "sys_prio", NULL, -1 },
-	[PROP_RUNNER_MIN_PORTS] =            { "runner", "min_ports", NULL, -1 },
-	[PROP_RUNNER_AGG_SELECT_POLICY] =    { "runner", "agg_select_policy", NULL, 0 },
-	[PROP_LINK_WATCHERS] =               { "link_watch", NULL, NULL, 0 }
+	[PROP_CONFIG] =                      { },
+	[PROP_NOTIFY_PEERS_COUNT] =          { .key1 = "notify_peers", .key2 = "count",                                           },
+	[PROP_NOTIFY_PEERS_INTERVAL] =       { .key1 = "notify_peers", .key2 = "interval",                                        },
+	[PROP_MCAST_REJOIN_COUNT] =          { .key1 = "mcast_rejoin", .key2 = "count",                                           },
+	[PROP_MCAST_REJOIN_INTERVAL] =       { .key1 = "mcast_rejoin", .key2 = "interval",                                        },
+	[PROP_RUNNER] =                      { .key1 = "runner",       .key2 = "name",                                            .default_str = NM_SETTING_TEAM_RUNNER_DEFAULT, },
+	[PROP_RUNNER_HWADDR_POLICY] =        { .key1 = "runner",       .key2 = "hwaddr_policy",                                   },
+	[PROP_RUNNER_TX_HASH] =              { .key1 = "runner",       .key2 = "tx_hash",                                         },
+	[PROP_RUNNER_TX_BALANCER] =          { .key1 = "runner",       .key2 = "tx_balancer",       .key3 = "name", },
+	[PROP_RUNNER_TX_BALANCER_INTERVAL] = { .key1 = "runner",       .key2 = "tx_balancer",       .key3 = "balancing_interval", .default_int = -1 },
+	[PROP_RUNNER_ACTIVE] =               { .key1 = "runner",       .key2 = "active",                                          },
+	[PROP_RUNNER_FAST_RATE] =            { .key1 = "runner",       .key2 = "fast_rate",                                       },
+	[PROP_RUNNER_SYS_PRIO] =             { .key1 = "runner",       .key2 = "sys_prio",                                        .default_int = -1, },
+	[PROP_RUNNER_MIN_PORTS] =            { .key1 = "runner",       .key2 = "min_ports",                                       .default_int = -1, },
+	[PROP_RUNNER_AGG_SELECT_POLICY] =    { .key1 = "runner",       .key2 = "agg_select_policy",                               },
+	[PROP_LINK_WATCHERS] =               { .key1 = "link_watch",                                                              },
 };
 
 typedef struct {
diff --git a/libnm-core/nm-setting-team.h b/libnm-core/nm-setting-team.h
index 5819feca..a6ef387f 100644
--- a/libnm-core/nm-setting-team.h
+++ b/libnm-core/nm-setting-team.h
@@ -43,9 +43,9 @@ G_BEGIN_DECLS
  */
 typedef enum { /*< flags >*/
 	NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE              = 0, /*< skip >*/
-	NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE   = (1 << 1),
-	NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE = (1 << 2),
-	NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS       = (1 << 3)
+	NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE   = 0x2,
+	NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE = 0x4,
+	NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS       = 0x8,
 } NMTeamLinkWatcherArpPingFlags;
 
 #define NM_TEAM_LINK_WATCHER_ETHTOOL   "ethtool"
diff --git a/libnm-core/nm-setting-tun.c b/libnm-core/nm-setting-tun.c
index 348c9fc1..9801f5cf 100644
--- a/libnm-core/nm-setting-tun.c
+++ b/libnm-core/nm-setting-tun.c
@@ -24,7 +24,6 @@
 #include "nm-setting-tun.h"
 
 #include <stdlib.h>
-#include <string.h>
 
 #include "nm-utils.h"
 #include "nm-setting-connection.h"
diff --git a/libnm-core/nm-setting-vlan.c b/libnm-core/nm-setting-vlan.c
index 4eba1626..5b8a49d4 100644
--- a/libnm-core/nm-setting-vlan.c
+++ b/libnm-core/nm-setting-vlan.c
@@ -24,7 +24,6 @@
 #include "nm-setting-vlan.h"
 
 #include <stdlib.h>
-#include <string.h>
 
 #include "nm-utils.h"
 #include "nm-core-types-internal.h"
diff --git a/libnm-core/nm-setting-vpn.c b/libnm-core/nm-setting-vpn.c
index 1371140e..20a0ae14 100644
--- a/libnm-core/nm-setting-vpn.c
+++ b/libnm-core/nm-setting-vpn.c
@@ -23,8 +23,6 @@
 
 #include "nm-setting-vpn.h"
 
-#include <string.h>
-#include <errno.h>
 #include <stdlib.h>
 
 #include "nm-utils/nm-secret-utils.h"
@@ -206,7 +204,7 @@ nm_setting_vpn_get_data_item (NMSettingVpn *setting, const char *key)
 /**
  * nm_setting_vpn_get_data_keys:
  * @setting: the #NMSettingVpn
- * @out_length: (allow-none): (out): the length of the returned array
+ * @out_length: (allow-none) (out): the length of the returned array
  *
  * Retrieves every data key inside @setting, as an array.
  *
@@ -383,7 +381,7 @@ nm_setting_vpn_get_secret (NMSettingVpn *setting, const char *key)
 /**
  * nm_setting_vpn_get_secret_keys:
  * @setting: the #NMSettingVpn
- * @out_length: (allow-none): (out): the length of the returned array
+ * @out_length: (allow-none) (out): the length of the returned array
  *
  * Retrieves every secret key inside @setting, as an array.
  *
@@ -449,20 +447,17 @@ nm_setting_vpn_foreach_secret (NMSettingVpn *setting,
 	foreach_item_helper (setting, TRUE, func, user_data);
 }
 
-gboolean
-_nm_setting_vpn_aggregate (NMSettingVpn *setting,
-                           NMConnectionAggregateType type,
-                           gpointer arg)
+static gboolean
+aggregate (NMSetting *setting,
+           int type_i,
+           gpointer arg)
 {
-	NMSettingVpnPrivate *priv;
+	NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting);
+	NMConnectionAggregateType type = type_i;
 	NMSettingSecretFlags secret_flags;
 	const char *key_name;
 	GHashTableIter iter;
 
-	g_return_val_if_fail (NM_IS_SETTING_VPN (setting), FALSE);
-
-	priv = NM_SETTING_VPN_GET_PRIVATE (setting);
-
 	switch (type) {
 
 	case NM_CONNECTION_AGGREGATE_ANY_SECRETS:
@@ -687,6 +682,57 @@ update_one_secret (NMSetting *setting, const char *key, GVariant *value, GError
 	return success;
 }
 
+static void
+for_each_secret (NMSetting *setting,
+                 const char *secret_name,
+                 GVariant *val,
+                 gboolean remove_non_secrets,
+                 _NMConnectionForEachSecretFunc callback,
+                 gpointer callback_data,
+                 GVariantBuilder *setting_builder)
+{
+	GVariantBuilder vpn_secrets_builder;
+	GVariantIter vpn_secrets_iter;
+	const char *vpn_secret_name;
+	const char *secret;
+
+	if (!nm_streq (secret_name, NM_SETTING_VPN_SECRETS)) {
+		NM_SETTING_CLASS (nm_setting_vpn_parent_class)->for_each_secret (setting,
+		                                                                 secret_name,
+		                                                                 val,
+		                                                                 remove_non_secrets,
+		                                                                 callback,
+		                                                                 callback_data,
+		                                                                 setting_builder);
+		return;
+	}
+
+	if (!g_variant_is_of_type (val, G_VARIANT_TYPE ("a{ss}"))) {
+		/* invalid type. Silently ignore the secrets as we cannot find out the
+		 * secret-flags. */
+		return;
+	}
+
+	/* Iterate through each secret from the VPN dict in the overall secrets dict */
+	g_variant_builder_init (&vpn_secrets_builder, G_VARIANT_TYPE ("a{ss}"));
+	g_variant_iter_init (&vpn_secrets_iter, val);
+	while (g_variant_iter_next (&vpn_secrets_iter, "{&s&s}", &vpn_secret_name, &secret)) {
+		NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
+
+		/* we ignore the return value of get_secret_flags. The function may determine
+		 * that this is not a secret, based on having not secret-flags and no secrets.
+		 * But we have the secret at hand. We know it would be a valid secret, if we
+		 * only add it to the VPN settings. */
+		nm_setting_get_secret_flags (setting, vpn_secret_name, &secret_flags, NULL);
+
+		if (callback (secret_flags, callback_data))
+			g_variant_builder_add (&vpn_secrets_builder, "{ss}", vpn_secret_name, secret);
+	}
+
+	g_variant_builder_add (setting_builder, "{sv}",
+	                       secret_name, g_variant_builder_end (&vpn_secrets_builder));
+}
+
 static gboolean
 get_secret_flags (NMSetting *setting,
                   const char *secret_name,
@@ -717,7 +763,7 @@ get_secret_flags (NMSetting *setting,
 		return TRUE;
 	}
 
-	i64 = _nm_utils_ascii_str_to_int64 (flags_val, 10, 0, NM_SETTING_SECRET_FLAGS_ALL, -1);
+	i64 = _nm_utils_ascii_str_to_int64 (flags_val, 10, 0, NM_SETTING_SECRET_FLAG_ALL, -1);
 	if (   i64 == -1
 	    || !_nm_setting_secret_flags_valid (i64)) {
 		/* The flags keys is set to an unexpected value. That is a configuration
@@ -979,11 +1025,13 @@ nm_setting_vpn_class_init (NMSettingVpnClass *klass)
 
 	setting_class->verify            = verify;
 	setting_class->update_one_secret = update_one_secret;
+	setting_class->for_each_secret   = for_each_secret;
 	setting_class->get_secret_flags  = get_secret_flags;
 	setting_class->set_secret_flags  = set_secret_flags;
 	setting_class->need_secrets      = need_secrets;
 	setting_class->compare_property  = compare_property;
 	setting_class->clear_secrets     = clear_secrets;
+	setting_class->aggregate         = aggregate;
 
 	/**
 	 * NMSettingVpn:service-type:
diff --git a/libnm-core/nm-setting-vxlan.c b/libnm-core/nm-setting-vxlan.c
index 0c21be27..0c116f06 100644
--- a/libnm-core/nm-setting-vxlan.c
+++ b/libnm-core/nm-setting-vxlan.c
@@ -24,7 +24,6 @@
 #include "nm-setting-vxlan.h"
 
 #include <stdlib.h>
-#include <string.h>
 
 #include "nm-utils.h"
 #include "nm-setting-private.h"
diff --git a/libnm-core/nm-setting-wifi-p2p.c b/libnm-core/nm-setting-wifi-p2p.c
new file mode 100644
index 00000000..da978720
--- /dev/null
+++ b/libnm-core/nm-setting-wifi-p2p.c
@@ -0,0 +1,317 @@
+/*
+ * 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 2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-setting-wifi-p2p.h"
+
+#include <net/ethernet.h>
+
+#include "nm-utils.h"
+#include "nm-common-macros.h"
+#include "nm-utils-private.h"
+#include "nm-setting-private.h"
+
+/**
+ * SECTION:nm-setting-wifi-p2p
+ * @short_description: Describes connection properties for 802.11 Wi-Fi P2P networks
+ *
+ * The #NMSettingWifiP2P object is a #NMSetting subclass that describes properties
+ * necessary for connection to 802.11 Wi-Fi P2P networks (aka Wi-Fi Direct).
+ **/
+
+/**
+ * NMSettingWifiP2P:
+ *
+ * Wi-Fi P2P Settings
+ *
+ * Since: 1.16
+ */
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+	PROP_PEER,
+	PROP_WPS_METHOD,
+	PROP_WFD_IES,
+);
+
+typedef struct {
+	char *peer_mac_address;
+	GBytes *wfd_ies;
+
+	NMSettingWirelessSecurityWpsMethod wps_method;
+} NMSettingWifiP2PPrivate;
+
+struct _NMSettingWifiP2P {
+	NMSetting parent;
+	NMSettingWifiP2PPrivate _priv;
+};
+
+struct _NMSettingWifiP2PClass {
+	NMSettingClass parent;
+};
+
+G_DEFINE_TYPE (NMSettingWifiP2P, nm_setting_wifi_p2p, NM_TYPE_SETTING)
+
+#define NM_SETTING_WIFI_P2P_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSettingWifiP2P, NM_IS_SETTING_WIFI_P2P, NMSetting)
+
+/*****************************************************************************/
+
+/**
+ * nm_setting_wifi_p2p_get_peer:
+ * @setting: the #NMSettingWifiP2P
+ *
+ * Returns: the #NMSettingWifiP2P:peer property of the setting
+ *
+ * Since: 1.16
+ **/
+const char *
+nm_setting_wifi_p2p_get_peer (NMSettingWifiP2P *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIFI_P2P (setting), NULL);
+
+	return NM_SETTING_WIFI_P2P_GET_PRIVATE (setting)->peer_mac_address;
+}
+
+/**
+ * nm_setting_wifi_p2p_get_wps_method:
+ * @setting: the #NMSettingWifiP2P
+ *
+ * Returns: the #NMSettingWifiP2P:wps-method property of the setting
+ *
+ * Since: 1.16
+ **/
+NMSettingWirelessSecurityWpsMethod
+nm_setting_wifi_p2p_get_wps_method (NMSettingWifiP2P *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIFI_P2P (setting), NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT);
+
+	return NM_SETTING_WIFI_P2P_GET_PRIVATE (setting)->wps_method;
+}
+
+/**
+ * nm_setting_wifi_p2p_get_wfd_ies:
+ * @setting: the #NMSettingWiFiP2P
+ *
+ * Returns: (transfer none): the #NMSettingWiFiP2P:wfd-ies property of the setting
+ *
+ * Since: 1.16
+ **/
+GBytes *
+nm_setting_wifi_p2p_get_wfd_ies (NMSettingWifiP2P *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIFI_P2P (setting), NULL);
+
+	return NM_SETTING_WIFI_P2P_GET_PRIVATE (setting)->wfd_ies;
+}
+
+/*****************************************************************************/
+
+static gboolean
+verify (NMSetting *setting, NMConnection *connection, GError **error)
+{
+	NMSettingWifiP2PPrivate *priv = NM_SETTING_WIFI_P2P_GET_PRIVATE (setting);
+
+	if (!priv->peer_mac_address) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_MISSING_PROPERTY,
+		                     _("property is missing"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIFI_P2P_SETTING_NAME, NM_SETTING_WIFI_P2P_PEER);
+		return FALSE;
+	}
+
+	if (!nm_utils_hwaddr_valid (priv->peer_mac_address, ETH_ALEN)) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("property is invalid"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIFI_P2P_SETTING_NAME, NM_SETTING_WIFI_P2P_PEER);
+		return FALSE;
+	}
+
+	if (!_nm_utils_wps_method_validate (priv->wps_method,
+	                                    NM_SETTING_WIFI_P2P_SETTING_NAME,
+	                                    NM_SETTING_WIFI_P2P_WPS_METHOD,
+	                                    TRUE,
+	                                    error))
+		return FALSE;
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMSettingWifiP2P *setting = NM_SETTING_WIFI_P2P (object);
+
+	switch (prop_id) {
+	case PROP_PEER:
+		g_value_set_string (value, nm_setting_wifi_p2p_get_peer (setting));
+		break;
+	case PROP_WPS_METHOD:
+		g_value_set_uint (value, nm_setting_wifi_p2p_get_wps_method (setting));
+		break;
+	case PROP_WFD_IES:
+		g_value_set_boxed (value, nm_setting_wifi_p2p_get_wfd_ies (setting));
+		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)
+{
+	NMSettingWifiP2PPrivate *priv = NM_SETTING_WIFI_P2P_GET_PRIVATE (object);
+
+	switch (prop_id) {
+	case PROP_PEER:
+		g_free (priv->peer_mac_address);
+		priv->peer_mac_address = _nm_utils_hwaddr_canonical_or_invalid (g_value_get_string (value),
+		                                                                ETH_ALEN);
+		break;
+	case PROP_WPS_METHOD:
+		priv->wps_method = g_value_get_uint (value);
+		break;
+	case PROP_WFD_IES:
+		g_clear_pointer (&priv->wfd_ies, g_bytes_unref);
+		priv->wfd_ies = g_value_dup_boxed (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_setting_wifi_p2p_init (NMSettingWifiP2P *setting)
+{
+}
+
+/**
+ * nm_setting_wifi_p2p_new:
+ *
+ * Creates a new #NMSettingWifiP2P object with default values.
+ *
+ * Returns: (transfer full): the new empty #NMSettingWifiP2P object
+ *
+ * Since: 1.16
+ **/
+NMSetting *
+nm_setting_wifi_p2p_new (void)
+{
+	return g_object_new (NM_TYPE_SETTING_WIFI_P2P, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMSettingWifiP2PPrivate *priv = NM_SETTING_WIFI_P2P_GET_PRIVATE (object);
+
+	g_free (priv->peer_mac_address);
+	g_bytes_unref (priv->wfd_ies);
+
+	G_OBJECT_CLASS (nm_setting_wifi_p2p_parent_class)->finalize (object);
+}
+
+static void
+nm_setting_wifi_p2p_class_init (NMSettingWifiP2PClass *setting_wifi_p2p_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (setting_wifi_p2p_class);
+	NMSettingClass *setting_class = NM_SETTING_CLASS (setting_wifi_p2p_class);
+
+	object_class->get_property = get_property;
+	object_class->set_property = set_property;
+	object_class->finalize     = finalize;
+
+	setting_class->verify      = verify;
+
+	/**
+	 * NMSettingWifiP2P:peer:
+	 *
+	 * The P2P device that should be connected to. Currently this is the only
+	 * way to create or join a group.
+	 *
+	 * Since: 1.16
+	 */
+	/* ---keyfile---
+	 * property: peer
+	 * format: usual hex-digits-and-colons notation
+	 * description: MAC address in traditional hex-digits-and-colons notation
+	 *   (e.g. 00:22:68:12:79:A2), or semicolon separated list of 6 bytes (obsolete)
+	 *   (e.g. 0;34;104;18;121;162).
+	 * ---end---
+	 */
+	obj_properties[PROP_PEER] =
+	    g_param_spec_string (NM_SETTING_WIFI_P2P_PEER, "", "",
+	                         NULL,
+	                         G_PARAM_READWRITE |
+	                         G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingWifiP2P:wps-method:
+	 *
+	 * 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.
+	 *
+	 * Since: 1.16
+	 */
+	obj_properties[PROP_WPS_METHOD] =
+	    g_param_spec_uint (NM_SETTING_WIFI_P2P_WPS_METHOD, "", "",
+	                       0,
+	                       G_MAXUINT32,
+	                       NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT,
+	                       G_PARAM_READWRITE |
+	                       NM_SETTING_PARAM_FUZZY_IGNORE |
+	                       G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingWifiP2P:wfd-ies:
+	 *
+	 * The Wi-Fi Display (WFD) Information Elements (IEs) to set.
+	 *
+	 * Wi-Fi Display requires a protocol specific information element to be
+	 * set in certain Wi-Fi frames. These can be specified here for the
+	 * purpose of establishing a connection.
+	 * This setting is only useful when implementing a Wi-Fi Display client.
+	 *
+	 * Since: 1.16
+	 */
+	obj_properties[PROP_WFD_IES] =
+	    g_param_spec_boxed (NM_SETTING_WIFI_P2P_WFD_IES, "", "",
+	                        G_TYPE_BYTES,
+	                        G_PARAM_READWRITE |
+	                        NM_SETTING_PARAM_FUZZY_IGNORE |
+	                        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_WIFI_P2P);
+}
diff --git a/libnm-core/nm-setting-wifi-p2p.h b/libnm-core/nm-setting-wifi-p2p.h
new file mode 100644
index 00000000..ddb611c1
--- /dev/null
+++ b/libnm-core/nm-setting-wifi-p2p.h
@@ -0,0 +1,69 @@
+/*
+ * 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 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_SETTING_WIFI_P2P_H__
+#define __NM_SETTING_WIFI_P2P_H__
+
+#if !defined (__NETWORKMANAGER_H_INSIDE__) && !defined (NETWORKMANAGER_COMPILATION)
+#error "Only <NetworkManager.h> can be included directly."
+#endif
+
+#include "nm-setting.h"
+#include "nm-setting-wireless-security.h"
+
+G_BEGIN_DECLS
+
+#define NM_TYPE_SETTING_WIFI_P2P            (nm_setting_wifi_p2p_get_type ())
+#define NM_SETTING_WIFI_P2P(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTING_WIFI_P2P, NMSettingWifiP2P))
+#define NM_SETTING_WIFI_P2P_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTING_WIFI_P2P, NMSettingWifiP2PClass))
+#define NM_IS_SETTING_WIFI_P2P(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SETTING_WIFI_P2P))
+#define NM_IS_SETTING_WIFI_P2P_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SETTING_WIFI_P2P))
+#define NM_SETTING_WIFI_P2P_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SETTING_WIFI_P2P, NMSettingWifiP2PClass))
+
+#define NM_SETTING_WIFI_P2P_SETTING_NAME "wifi-p2p"
+
+/**
+ * NM_SETTING_WIFI_P2P_PEER:
+ *
+ * The mac address of the peer to connect to.
+ */
+#define NM_SETTING_WIFI_P2P_PEER        "peer"
+#define NM_SETTING_WIFI_P2P_WPS_METHOD  "wps-method"
+#define NM_SETTING_WIFI_P2P_WFD_IES     "wfd-ies"
+
+typedef struct _NMSettingWifiP2PClass NMSettingWifiP2PClass;
+
+NM_AVAILABLE_IN_1_16
+GType nm_setting_wifi_p2p_get_type (void);
+
+NM_AVAILABLE_IN_1_16
+NMSetting *nm_setting_wifi_p2p_new (void);
+
+NM_AVAILABLE_IN_1_16
+const char *nm_setting_wifi_p2p_get_peer (NMSettingWifiP2P *setting);
+
+NM_AVAILABLE_IN_1_16
+NMSettingWirelessSecurityWpsMethod nm_setting_wifi_p2p_get_wps_method (NMSettingWifiP2P *setting);
+
+NM_AVAILABLE_IN_1_16
+GBytes *nm_setting_wifi_p2p_get_wfd_ies (NMSettingWifiP2P *setting);
+
+G_END_DECLS
+
+#endif /* __NM_SETTING_WIFI_P2P_H__ */
diff --git a/libnm-core/nm-setting-wimax.c b/libnm-core/nm-setting-wimax.c
index aa574a56..a700f7fe 100644
--- a/libnm-core/nm-setting-wimax.c
+++ b/libnm-core/nm-setting-wimax.c
@@ -24,7 +24,6 @@
 
 #include "nm-setting-wimax.h"
 
-#include <string.h>
 #include <net/ethernet.h>
 
 #include "nm-setting-private.h"
diff --git a/libnm-core/nm-setting-wired.c b/libnm-core/nm-setting-wired.c
index ef994255..ef23294f 100644
--- a/libnm-core/nm-setting-wired.c
+++ b/libnm-core/nm-setting-wired.c
@@ -24,7 +24,6 @@
 
 #include "nm-setting-wired.h"
 
-#include <string.h>
 #include <net/ethernet.h>
 
 #include "nm-utils.h"
@@ -42,6 +41,11 @@
 
 /*****************************************************************************/
 
+G_STATIC_ASSERT (NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS == (  NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT
+                                                                  | NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE));
+
+/*****************************************************************************/
+
 NM_GOBJECT_PROPERTIES_DEFINE (NMSettingWired,
 	PROP_PORT,
 	PROP_SPEED,
diff --git a/libnm-core/nm-setting-wired.h b/libnm-core/nm-setting-wired.h
index 015f09da..8e707b1b 100644
--- a/libnm-core/nm-setting-wired.h
+++ b/libnm-core/nm-setting-wired.h
@@ -63,20 +63,19 @@ G_BEGIN_DECLS
  * Since: 1.2
  */
 typedef enum { /*< flags >*/
-	NM_SETTING_WIRED_WAKE_ON_LAN_NONE      = 0, /*< skip >*/
-	NM_SETTING_WIRED_WAKE_ON_LAN_PHY       = (1 << 1),
-	NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST   = (1 << 2),
-	NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST = (1 << 3),
-	NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST = (1 << 4),
-	NM_SETTING_WIRED_WAKE_ON_LAN_ARP       = (1 << 5),
-	NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC     = (1 << 6),
-
-	_NM_SETTING_WIRED_WAKE_ON_LAN_LAST_OPT, /*< skip >*/
-	NM_SETTING_WIRED_WAKE_ON_LAN_ALL       = (((_NM_SETTING_WIRED_WAKE_ON_LAN_LAST_OPT - 1) << 1) - 1) - (1 << 0 /*DEFAULT*/), /*< skip >*/
-
-	NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT   = (1 << 0),
-	NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE    = (1 << 15),
-	NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS = NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT | NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE, /*< skip >*/
+	NM_SETTING_WIRED_WAKE_ON_LAN_NONE            = 0, /*< skip >*/
+	NM_SETTING_WIRED_WAKE_ON_LAN_PHY             = 0x2,
+	NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST         = 0x4,
+	NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST       = 0x8,
+	NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST       = 0x10,
+	NM_SETTING_WIRED_WAKE_ON_LAN_ARP             = 0x20,
+	NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC           = 0x40,
+
+	NM_SETTING_WIRED_WAKE_ON_LAN_ALL             = 0x7E, /*< skip >*/
+
+	NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT         = 0x1,
+	NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE          = 0x8000,
+	NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS = 0x8001, /*< skip >*/
 } NMSettingWiredWakeOnLan;
 
 #define NM_SETTING_WIRED_PORT "port"
diff --git a/libnm-core/nm-setting-wireguard.c b/libnm-core/nm-setting-wireguard.c
new file mode 100644
index 00000000..8c5b25a5
--- /dev/null
+++ b/libnm-core/nm-setting-wireguard.c
@@ -0,0 +1,2499 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 - 2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-setting-wireguard.h"
+
+#include "nm-setting-private.h"
+#include "nm-utils-private.h"
+#include "nm-connection-private.h"
+#include "nm-utils/nm-secret-utils.h"
+
+/*****************************************************************************/
+
+/**
+ * SECTION:nm-setting-wireguard
+ * @short_description: Describes connection properties for wireguard related options
+ *
+ * The #NMSettingWireGuard object is a #NMSetting subclass that contains settings
+ * for configuring WireGuard.
+ **/
+
+/*****************************************************************************/
+
+static NMWireGuardPeer *_wireguard_peer_dup (const NMWireGuardPeer *self);
+
+G_DEFINE_BOXED_TYPE (NMWireGuardPeer, nm_wireguard_peer, _wireguard_peer_dup, nm_wireguard_peer_unref)
+
+/* NMWireGuardPeer can also track invalid allowed-ip settings, and only reject
+ * them later during is_valid(). Such values are marked by a leading 'X' character
+ * in the @allowed_ips. It is expected, that such values are the expception, and
+ * commonly not present. */
+#define ALLOWED_IP_INVALID_X     'X'
+#define ALLOWED_IP_INVALID_X_STR "X"
+
+/**
+ * NMWireGuardPeer:
+ *
+ * The settings of one WireGuard peer.
+ *
+ * Since: 1.16
+ */
+struct _NMWireGuardPeer {
+	NMSockAddrEndpoint *endpoint;
+	char *public_key;
+	char *preshared_key;
+	GPtrArray *allowed_ips;
+	guint refcount;
+	NMSettingSecretFlags preshared_key_flags;
+	guint16 persistent_keepalive;
+	bool public_key_valid:1;
+	bool preshared_key_valid:1;
+	bool sealed:1;
+};
+
+static gboolean
+NM_IS_WIREGUARD_PEER (const NMWireGuardPeer *self, gboolean also_sealed)
+{
+	return    self
+	       && self->refcount > 0
+	       && (   also_sealed
+	           || !self->sealed);
+}
+
+/**
+ * nm_wireguard_peer_new:
+ *
+ * Returns: (transfer full): a new, default, unsealed #NMWireGuardPeer instance.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_wireguard_peer_new (void)
+{
+	NMWireGuardPeer *self;
+
+	self = g_slice_new (NMWireGuardPeer);
+	*self = (NMWireGuardPeer) {
+		.refcount            = 1,
+		.preshared_key_flags = NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
+	};
+	return self;
+}
+
+/**
+ * nm_wireguard_peer_new_clone:
+ * @self: the #NMWireGuardPeer instance to copy.
+ * @with_secrets: if %TRUE, the preshared-key secrets are copied
+ *  as well. Otherwise, they will be removed.
+ *
+ * Returns: (transfer full): a clone of @self. This instance
+ *   is always unsealed.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_wireguard_peer_new_clone (const NMWireGuardPeer *self,
+                             gboolean with_secrets)
+{
+	NMWireGuardPeer *new;
+	guint i;
+
+	g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
+
+	new = g_slice_new (NMWireGuardPeer);
+	*new = (NMWireGuardPeer) {
+		.refcount             = 1,
+		.public_key           = g_strdup (self->public_key),
+		.public_key_valid     = self->public_key_valid,
+		.preshared_key        = with_secrets ? g_strdup (self->preshared_key) : NULL,
+		.preshared_key_valid  = self->preshared_key_valid,
+		.preshared_key_flags  = self->preshared_key_flags,
+		.endpoint             = nm_sock_addr_endpoint_ref (self->endpoint),
+		.persistent_keepalive = self->persistent_keepalive,
+	};
+	if (   self->allowed_ips
+	    && self->allowed_ips->len > 0) {
+		new->allowed_ips = g_ptr_array_new_full (self->allowed_ips->len,
+		                                         g_free);
+		for (i = 0; i < self->allowed_ips->len; i++) {
+			g_ptr_array_add (new->allowed_ips,
+			                 g_strdup (self->allowed_ips->pdata[i]));
+		}
+	}
+	return new;
+}
+
+/**
+ * nm_wireguard_peer_ref:
+ * @self: (allow-none): the #NMWireGuardPeer instance
+ *
+ * This is not thread-safe.
+ *
+ * Returns: returns the input argument @self after incrementing
+ *   the reference count.
+ *
+ * Since: 1.16
+ */
+NMWireGuardPeer *
+nm_wireguard_peer_ref (NMWireGuardPeer *self)
+{
+	if (!self)
+		return NULL;
+
+	g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
+
+	nm_assert (self->refcount < G_MAXUINT);
+
+	self->refcount++;
+	return self;
+}
+
+/**
+ * nm_wireguard_peer_unref:
+ * @self: (allow-none): the #NMWireGuardPeer instance
+ *
+ * Drop a reference to @self. If the last reference is dropped,
+ * the instance is freed and all accociate data released.
+ *
+ * This is not thread-safe.
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_unref (NMWireGuardPeer *self)
+{
+	if (!self)
+		return;
+
+	g_return_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE));
+
+	if (--self->refcount > 0)
+		return;
+
+	nm_sock_addr_endpoint_unref (self->endpoint);
+	if (self->allowed_ips)
+		g_ptr_array_unref (self->allowed_ips);
+	g_free (self->public_key);
+	nm_free_secret (self->preshared_key);
+	g_slice_free (NMWireGuardPeer, self);
+}
+
+/**
+ * _wireguard_peer_dup:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Duplicates the #NMWireGuardPeer instance. Note that if @self
+ * is already sealed, this increments the reference count and
+ * returns it. If the instance is still unsealed, it is copied.
+ *
+ * Returns: (transfer full): a duplicate of @self, or (if the
+ *   instance is sealed and thus immutable) a reference to @self.
+ *   As such, the instance will be sealed if and only if @self is
+ *   sealed.
+ */
+static NMWireGuardPeer *
+_wireguard_peer_dup (const NMWireGuardPeer *self)
+{
+	g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
+
+	if (self->sealed)
+		return nm_wireguard_peer_ref ((NMWireGuardPeer *) self);
+	return nm_wireguard_peer_new_clone (self, TRUE);
+}
+
+/**
+ * nm_wireguard_peer_seal:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Seal the #NMWireGuardPeer instance. Afterwards, it is a bug
+ * to call all functions that modify the instance (except ref/unref).
+ * A sealed instance cannot be unsealed again, but you can create
+ * an unsealed copy with nm_wireguard_peer_new_clone().
+ *
+ * Since: 1.16
+ */
+void
+nm_wireguard_peer_seal (NMWireGuardPeer *self)
+{
+	g_return_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE));
+
+	self->sealed = TRUE;
+
+	if (self->allowed_ips) {
+		if (self->allowed_ips->len == 0)
+			nm_clear_pointer (&self->allowed_ips, g_ptr_array_unref);
+	}
+}
+
+/**
+ * nm_wireguard_peer_is_sealed:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: whether @self is sealed or not.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_is_sealed (const NMWireGuardPeer *self)
+{
+	g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), FALSE);
+
+	return self->sealed;
+}
+
+/**
+ * nm_wireguard_peer_get_public_key:
+ * @self: the #NMWireGuardPeer instance
+ *
+ * Returns: (transfer none): the public key or %NULL if unset.
+ *
+ * Since: 1.16
+ */
+const char *
+nm_wireguard_peer_get_public_key (const NMWireGuardPeer *self)
+{
+	g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
+
+	return self->public_key;
+}
+
+/**
+ * nm_wireguard_peer_set_public_key:
+ * @self: the unsealed #NMWireGuardPeer instance
+ * @public_key: (allow-none) (transfer none): the new public
+ *   key or %NULL to clear the public key.
+ * @accept_invalid: if %TRUE and @public_key is not %NULL and
+ *   invalid, then do not modify the instance.
+ *
+ * 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.
+ *
+ * Returns: %TRUE if the key was valid or %NULL. Returns
+ *   %FALSE for invalid keys. Depending on @accept_invalid
+ *   will an invalid key be set or not.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_set_public_key (NMWireGuardPeer *self,
+                                  const char *public_key,
+                                  gboolean accept_invalid)
+{
+	char *public_key_normalized = NULL;
+	gboolean is_valid;
+
+	g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
+
+	if (!public_key) {
+		nm_clear_g_free (&self->public_key);
+		return TRUE;
+	}
+
+	is_valid = nm_utils_base64secret_normalize (public_key,
+	                                            NM_WIREGUARD_PUBLIC_KEY_LEN,
+	                                            &public_key_normalized);
+	nm_assert (is_valid == (public_key_normalized != NULL));
+
+	if (   !is_valid
+	    && !accept_invalid)
+		return FALSE;
+
+	self->public_key_valid = is_valid;
+	g_free (self->public_key);
+	self->public_key = public_key_normalized ?: g_strdup (public_key);
+	return is_valid;
+}
+
+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.
+ * @accept_invalid: whether to allow setting the key to an invalid
+ *   value. If %FALSE, @self is unchanged if the key is invalid
+ *   and if %FALSE is returned.
+ *
+ * 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.
+ *
+ * Returns: %TRUE if the preshared-key is valid, otherwise %FALSE.
+ *   %NULL is considered a valid value.
+ *   If the key is invalid, it depends on @accept_invalid whether the
+ *   previous value was reset.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_set_preshared_key (NMWireGuardPeer *self,
+                                     const char *preshared_key,
+                                     gboolean accept_invalid)
+{
+	char *preshared_key_normalized = NULL;
+	gboolean is_valid;
+
+	g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
+
+	if (!preshared_key) {
+		nm_clear_pointer (&self->preshared_key, nm_free_secret);
+		return TRUE;
+	}
+
+	is_valid = nm_utils_base64secret_normalize (preshared_key,
+	                                            NM_WIREGUARD_SYMMETRIC_KEY_LEN,
+	                                            &preshared_key_normalized);
+	nm_assert (is_valid == (preshared_key_normalized != NULL));
+
+	if (   !is_valid
+	    && !accept_invalid)
+		return FALSE;
+
+	self->preshared_key_valid = is_valid;
+	nm_free_secret (self->preshared_key);
+	self->preshared_key = preshared_key_normalized ?: g_strdup (preshared_key);
+	return is_valid;
+}
+
+/**
+ * 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.
+ * @allow_invalid: if %TRUE, also invalid values are set.
+ *   If %FALSE, the function does nothing for invalid @endpoint
+ *   arguments.
+ *
+ * Sets or clears the endpoint of @self.
+ *
+ * It is a bug trying to modify a sealed #NMWireGuardPeer instance.
+ *
+ * Returns: %TRUE if the endpoint is %NULL or valid. For an
+ *   invalid @endpoint argument, %FALSE is returned. Depending
+ *   on @allow_invalid, the instance will be modified.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self,
+                                const char *endpoint,
+                                gboolean allow_invalid)
+{
+	NMSockAddrEndpoint *old;
+	NMSockAddrEndpoint *new;
+	gboolean is_valid;
+
+	g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
+
+	if (!endpoint) {
+		nm_clear_pointer (&self->endpoint, nm_sock_addr_endpoint_unref);
+		return TRUE;
+	}
+
+	new = nm_sock_addr_endpoint_new (endpoint);
+
+	is_valid = (nm_sock_addr_endpoint_get_host (new) != NULL);
+
+	if (   !allow_invalid
+	    && !is_valid) {
+		nm_sock_addr_endpoint_unref (new);
+		return FALSE;
+	}
+
+	old = self->endpoint;
+	self->endpoint = new;
+	nm_sock_addr_endpoint_unref (old);
+	return is_valid;
+}
+
+/**
+ * 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;
+	gboolean is_valid = TRUE;
+
+	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);
+		is_valid = FALSE;
+	} 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 is_valid;
+}
+
+/**
+ * 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_FWMARK,
+	PROP_LISTEN_PORT,
+	PROP_MTU,
+	PROP_PEER_ROUTES,
+	PROP_PRIVATE_KEY,
+	PROP_PRIVATE_KEY_FLAGS,
+);
+
+typedef struct {
+	char *private_key;
+	GPtrArray *peers_arr;
+	GHashTable *peers_hash;
+	NMSettingSecretFlags private_key_flags;
+	guint32 fwmark;
+	guint32 mtu;
+	guint16 listen_port;
+	bool private_key_valid:1;
+	bool peer_routes:1;
+} NMSettingWireGuardPrivate;
+
+/**
+ * NMSettingWireGuard:
+ *
+ * WireGuard Ethernet Settings
+ *
+ * Since: 1.16
+ */
+struct _NMSettingWireGuard {
+	NMSetting parent;
+	NMSettingWireGuardPrivate _priv;
+};
+
+struct _NMSettingWireGuardClass {
+	NMSettingClass parent;
+};
+
+G_DEFINE_TYPE (NMSettingWireGuard, nm_setting_wireguard, NM_TYPE_SETTING)
+
+#define NM_SETTING_WIREGUARD_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSettingWireGuard, NM_IS_SETTING_WIREGUARD, NMSetting)
+
+/*****************************************************************************/
+
+#define peers_psk_get_secret_name_a(public_key, to_free) \
+	nm_construct_name_a (NM_SETTING_WIREGUARD_PEERS".%s."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, (public_key), (to_free))
+
+#define peers_psk_get_secret_name_dup(public_key) \
+	g_strdup_printf (NM_SETTING_WIREGUARD_PEERS".%s."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, (public_key))
+
+#define peers_psk_get_secret_parse_a(secret_public_key, public_key_free) \
+	({ \
+		const char *_secret_public_key = (secret_public_key); \
+		char **_public_key_free = (public_key_free); \
+		const char *_public_key = NULL; \
+		\
+		nm_assert (_public_key_free && !*_public_key_free); \
+		\
+		if (NM_STR_HAS_PREFIX (_secret_public_key, NM_SETTING_WIREGUARD_PEERS".")) { \
+			_secret_public_key += NM_STRLEN (NM_SETTING_WIREGUARD_PEERS"."); \
+			if (NM_STR_HAS_SUFFIX (_secret_public_key, "."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) { \
+				_public_key = nm_strndup_a (300, _secret_public_key, strlen (_secret_public_key) - NM_STRLEN ("."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY), _public_key_free); \
+			} \
+		} \
+		\
+		_public_key; \
+	})
+
+/*****************************************************************************/
+
+/**
+ * nm_setting_wireguard_get_private_key:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: (transfer none): the set private-key or %NULL.
+ *
+ * Since: 1.16
+ */
+const char *
+nm_setting_wireguard_get_private_key (NMSettingWireGuard *self)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), NULL);
+
+	return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->private_key;
+}
+
+/**
+ * nm_setting_wireguard_get_private_key_flags:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the secret-flags for #NMSettingWireGuard:private-key.
+ *
+ * Since: 1.16
+ */
+NMSettingSecretFlags
+nm_setting_wireguard_get_private_key_flags (NMSettingWireGuard *self)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
+
+	return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->private_key_flags;
+}
+
+/**
+ * nm_setting_wireguard_get_fwmark:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the set firewall mark.
+ *
+ * Since: 1.16
+ */
+guint32
+nm_setting_wireguard_get_fwmark (NMSettingWireGuard *self)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
+
+	return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->fwmark;
+}
+
+/**
+ * nm_setting_wireguard_get_listen_port:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the set UDP listen port.
+ *
+ * Since: 1.16
+ */
+guint16
+nm_setting_wireguard_get_listen_port (NMSettingWireGuard *self)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
+
+	return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->listen_port;
+}
+
+/**
+ * nm_setting_wireguard_get_peer_routes:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: whether automatically add peer routes.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_setting_wireguard_get_peer_routes (NMSettingWireGuard *self)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), TRUE);
+
+	return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->peer_routes;
+}
+
+/**
+ * nm_setting_wireguard_get_mtu:
+ * @self: the #NMSettingWireGuard instance
+ *
+ * Returns: the MTU of the setting.
+ *
+ * Since: 1.16
+ */
+guint32
+nm_setting_wireguard_get_mtu (NMSettingWireGuard *self)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
+
+	return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->mtu;
+}
+
+/*****************************************************************************/
+
+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_base64secret_normalize (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 ();
+		if (!nm_wireguard_peer_set_public_key (peer, cstr, TRUE)) {
+			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, TRUE);
+
+		if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, "u", &u32))
+			nm_wireguard_peer_set_preshared_key_flags (peer, u32);
+
+		if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, "u", &u32))
+			nm_wireguard_peer_set_persistent_keepalive (peer, u32);
+
+		if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, "@as", &var)) {
+			_nm_unused gs_unref_variant GVariant *var_free = var;
+			gs_free const char **allowed_ips = NULL;
+			gsize i, l;
+
+			allowed_ips = g_variant_get_strv (var, &l);
+			if (allowed_ips) {
+				for (i = 0; i < l; i++) {
+					if (_peer_append_allowed_ip (peer, allowed_ips[i], FALSE))
+						continue;
+					if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
+						continue;
+					g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
+					             _("peer #%u has invalid allowed-ips setting"),
+					             i_peer);
+					goto out;
+				}
+			}
+		}
+
+		if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+			gs_free_error GError *local = NULL;
+
+			if (!nm_wireguard_peer_is_valid (peer, TRUE, FALSE, &local)) {
+				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
+				             _("peer #%u is invalid: %s"),
+				             i_peer, local->message);
+				goto out;
+			}
+		}
+
+		/* we could easily reject duplicate peers (by public-key) or duplicate GVariant attributes.
+		 * However, don't do that. In case of duplicate values, the latter peer overwrite the earlier
+		 * and GVariant attributes are ignored by g_variant_lookup() above. */
+		if (_peers_append (NM_SETTING_WIREGUARD_GET_PRIVATE (setting),
+		                   peer,
+		                   TRUE))
+			peers_changed = TRUE;
+	}
+
+	success = TRUE;
+
+out:
+	if (peers_changed)
+		_peers_notify (setting);
+	return success;
+}
+
+/*****************************************************************************/
+
+static gboolean
+verify (NMSetting *setting, NMConnection *connection, GError **error)
+{
+	NMSettingWireGuard *s_wg = NM_SETTING_WIREGUARD (setting);
+	NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+	guint i;
+
+	if (!_nm_connection_verify_required_interface_name (connection, error))
+		return FALSE;
+
+	if (!_nm_utils_secret_flags_validate (nm_setting_wireguard_get_private_key_flags (s_wg),
+	                                      NM_SETTING_WIREGUARD_SETTING_NAME,
+	                                      NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS,
+	                                      NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
+	                                      error))
+		return FALSE;
+
+	for (i = 0; i < priv->peers_arr->len; i++) {
+		NMWireGuardPeer *peer = _peers_get (priv, i)->peer;
+
+		if (!nm_wireguard_peer_is_valid (peer, TRUE, FALSE, error)) {
+			g_prefix_error (error,
+			                "%s.%s[%u]: ",
+			                NM_SETTING_WIREGUARD_SETTING_NAME,
+			                NM_SETTING_WIREGUARD_PEERS,
+			                i);
+			return FALSE;
+		}
+	}
+
+	if (connection) {
+		NMSettingIPConfig *s_ip4;
+		NMSettingIPConfig *s_ip6;
+		const char *method;
+
+		/* WireGuard is Layer 3 only. For the moment, we only support a restricted set of
+		 * IP methods. We may relax that later, once we fix the implementations so they
+		 * actually work. */
+
+		if (   (s_ip4 = nm_connection_get_setting_ip4_config (connection))
+		    && (method = nm_setting_ip_config_get_method (s_ip4))
+		    && !NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
+		                              NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
+			g_set_error (error,
+			             NM_CONNECTION_ERROR,
+			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("method \"%s\" is not supported for WireGuard"),
+			             method);
+			g_prefix_error (error, "%s.%s: ", NM_SETTING_IP4_CONFIG_SETTING_NAME, NM_SETTING_IP_CONFIG_METHOD);
+			return FALSE;
+		}
+
+		if (   (s_ip6 = nm_connection_get_setting_ip6_config (connection))
+		    && (method = nm_setting_ip_config_get_method (s_ip6))
+		    && !NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+		                              NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
+		                              NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
+			g_set_error (error,
+			             NM_CONNECTION_ERROR,
+			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("method \"%s\" is not supported for WireGuard"),
+			             method);
+			g_prefix_error (error, "%s.%s: ", NM_SETTING_IP6_CONFIG_SETTING_NAME, NM_SETTING_IP_CONFIG_METHOD);
+			return FALSE;
+		}
+	}
+
+	/* private-key is a secret, hence we cannot verify it like a regular property. */
+	return TRUE;
+}
+
+static gboolean
+verify_secrets (NMSetting *setting, NMConnection *connection, GError **error)
+{
+	NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+	guint i;
+
+	if (   priv->private_key
+	    && !priv->private_key_valid) {
+		g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("key must be 32 bytes base64 encoded"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIREGUARD_SETTING_NAME, NM_SETTING_WIREGUARD_PRIVATE_KEY);
+		return FALSE;
+	}
+
+	for (i = 0; i < priv->peers_arr->len; i++) {
+		NMWireGuardPeer *peer = _peers_get (priv, i)->peer;
+
+		if (!nm_wireguard_peer_is_valid (peer, FALSE, TRUE, error)) {
+			g_prefix_error (error,
+			                "%s.%s[%u]: ",
+			                NM_SETTING_WIREGUARD_SETTING_NAME,
+			                NM_SETTING_WIREGUARD_PEERS,
+			                i);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static GPtrArray *
+need_secrets (NMSetting *setting)
+{
+	NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+	GPtrArray *secrets = NULL;
+	guint i;
+
+	if (   !priv->private_key
+	    || !priv->private_key_valid) {
+		secrets = g_ptr_array_new_full (1, g_free);
+		g_ptr_array_add (secrets, g_strdup (NM_SETTING_WIREGUARD_PRIVATE_KEY));
+	}
+
+	for (i = 0; i < priv->peers_arr->len; i++) {
+		NMWireGuardPeer *peer = _peers_get (priv, i)->peer;
+
+		if (NM_FLAGS_HAS (peer->preshared_key_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED))
+			continue;
+
+		if (peer->preshared_key_valid)
+			continue;
+
+		if (!peer->public_key_valid)
+			continue;
+
+		if (!secrets)
+			secrets = g_ptr_array_new_full (1, g_free);
+		g_ptr_array_add (secrets, peers_psk_get_secret_name_dup (peer->public_key));
+	}
+
+	return secrets;
+}
+
+static gboolean
+clear_secrets (const NMSettInfoSetting *sett_info,
+               guint property_idx,
+               NMSetting *setting,
+               NMSettingClearSecretsWithFlagsFn func,
+               gpointer user_data)
+{
+	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_WIREGUARD_PEERS)) {
+		NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+		gboolean peers_changed = FALSE;
+		guint i, j;
+
+		j = 0;
+		for (i = 0; i < priv->peers_arr->len; i++) {
+			NMWireGuardPeer *peer = _peers_get (priv, i)->peer;
+
+			if (!peer->preshared_key)
+				continue;
+
+			if (func) {
+				gs_free char *name_free = NULL;
+				const char *name;
+
+				/* only stack-allocate (alloca) a few times. */
+				if (j++ < 5)
+					name = peers_psk_get_secret_name_a (peer->public_key, &name_free);
+				else {
+					name_free = peers_psk_get_secret_name_dup (peer->public_key);
+					name = name_free;
+				}
+
+				if (!func (setting, name, peer->preshared_key_flags, user_data))
+					continue;
+			}
+
+			{
+				nm_auto_unref_wgpeer NMWireGuardPeer *peer2 = NULL;
+
+				peer2 = nm_wireguard_peer_new_clone (peer, FALSE);
+
+				if (_peers_set (priv, peer2, i, FALSE))
+					peers_changed = TRUE;
+			}
+		}
+
+		if (peers_changed)
+			_peers_notify (setting);
+		return peers_changed;
+	}
+
+	return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->clear_secrets (sett_info,
+	                                                                            property_idx,
+	                                                                            setting,
+	                                                                            func,
+	                                                                            user_data);
+}
+
+static int
+update_one_secret (NMSetting *setting,
+                   const char *key,
+                   GVariant *value,
+                   GError **error)
+{
+	NMSettingWireGuard *self = NM_SETTING_WIREGUARD (setting);
+	NMSettingWireGuardPrivate *priv;
+	gboolean has_changes = FALSE;
+	gboolean has_error = FALSE;
+	GVariantIter iter_peers;
+	GVariant *peer_var;
+	guint i_peer;
+
+	if (!nm_streq (key, NM_SETTING_WIREGUARD_PEERS)) {
+		return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->update_one_secret (setting,
+		                                                                                key,
+		                                                                                value,
+		                                                                                error);
+	}
+
+	if (!g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}"))) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+		                     _("invalid peer secrets"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIREGUARD_SETTING_NAME, NM_SETTING_WIREGUARD_PEERS);
+		return NM_SETTING_UPDATE_SECRET_ERROR;
+	}
+
+	priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
+
+	g_variant_iter_init (&iter_peers, value);
+
+	i_peer = 0;
+	while (g_variant_iter_next (&iter_peers, "@a{sv}", &peer_var)) {
+		_nm_unused gs_unref_variant GVariant *peer_var_unref = peer_var;
+		PeerData *pd;
+		NMWireGuardPeer *peer;
+		const char *cstr;
+
+		i_peer++;
+
+		if (!g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, "&s", &cstr)) {
+			if (!has_error) {
+				g_set_error (error,
+				             NM_CONNECTION_ERROR,
+				             NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+				             _("peer #%u lacks public-key"),
+				             i_peer - 1);
+				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIREGUARD_SETTING_NAME, NM_SETTING_WIREGUARD_PEERS);
+				has_error = TRUE;
+			}
+			continue;
+		}
+
+		pd = _peers_get_by_public_key (priv, cstr, TRUE);
+		if (!pd) {
+			if (!has_error) {
+				g_set_error (error,
+				             NM_CONNECTION_ERROR,
+				             NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+				             _("non-existing peer '%s'"),
+				             cstr);
+				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIREGUARD_SETTING_NAME, NM_SETTING_WIREGUARD_PEERS);
+				has_error = TRUE;
+			}
+			continue;
+		}
+
+		if (!g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, "&s", &cstr)) {
+			/* no preshared-key. Ignore the rest.
+			 *
+			 * In particular, we don't reject all unknown fields. */
+			continue;
+		}
+
+		if (nm_streq0  (cstr, nm_wireguard_peer_get_preshared_key (pd->peer)))
+			continue;
+
+		peer = nm_wireguard_peer_new_clone (pd->peer, FALSE);
+		nm_wireguard_peer_set_preshared_key (peer, cstr, TRUE);
+
+		if (!_peers_set (priv, peer, pd->idx, FALSE))
+			nm_assert_not_reached ();
+		has_changes = TRUE;
+	}
+
+	if (has_error)
+		return NM_SETTING_UPDATE_SECRET_ERROR;
+	if (has_changes)
+		return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED;
+	return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED;
+}
+
+static NMTernary
+compare_property (const NMSettInfoSetting *sett_info,
+                  guint property_idx,
+                  NMSetting *setting,
+                  NMSetting *other,
+                  NMSettingCompareFlags flags)
+{
+	NMSettingWireGuardPrivate *a_priv;
+	NMSettingWireGuardPrivate *b_priv;
+	guint i;
+
+	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_WIREGUARD_PEERS)) {
+
+		if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE))
+			return NM_TERNARY_DEFAULT;
+
+		if (!other)
+			return TRUE;
+
+		a_priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+		b_priv = NM_SETTING_WIREGUARD_GET_PRIVATE (other);
+
+		if (a_priv->peers_arr->len != b_priv->peers_arr->len)
+			return FALSE;
+		for (i = 0; i < a_priv->peers_arr->len; i++) {
+			NMWireGuardPeer *a_peer = _peers_get (a_priv, i)->peer;
+			NMWireGuardPeer *b_peer = _peers_get (b_priv, i)->peer;
+
+			if (nm_wireguard_peer_cmp (a_peer,
+			                           b_peer,
+			                           flags) != 0)
+				return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->compare_property (sett_info,
+	                                                                               property_idx,
+	                                                                               setting,
+	                                                                               other,
+	                                                                               flags);
+}
+
+static void
+duplicate_copy_properties (const NMSettInfoSetting *sett_info,
+                           NMSetting *src,
+                           NMSetting *dst)
+{
+	NMSettingWireGuardPrivate *priv_src = NM_SETTING_WIREGUARD_GET_PRIVATE (src);
+	NMSettingWireGuardPrivate *priv_dst = NM_SETTING_WIREGUARD_GET_PRIVATE (dst);
+	guint i;
+	gboolean peers_changed = FALSE;
+
+	NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->duplicate_copy_properties (sett_info,
+	                                                                                 src,
+	                                                                                 dst);
+
+	/* We don't bother comparing the existing peers with what we are about to set.
+	 * Always reset all. */
+	if (_peers_clear (priv_dst) > 0)
+		peers_changed = TRUE;
+	for (i = 0; i < priv_src->peers_arr->len; i++) {
+		if (_peers_append (priv_dst,
+		                   _peers_get (priv_src, i)->peer,
+		                   FALSE))
+			peers_changed = TRUE;
+	}
+	if (peers_changed)
+		_peers_notify (dst);
+}
+
+static void
+enumerate_values (const NMSettInfoProperty *property_info,
+                  NMSetting *setting,
+                  NMSettingValueIterFn func,
+                  gpointer user_data)
+{
+	if (nm_streq (property_info->name, NM_SETTING_WIREGUARD_PEERS)) {
+		NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+		nm_auto_unset_gvalue GValue value = G_VALUE_INIT;
+		GPtrArray *ptr = NULL;
+		guint i;
+
+		if (priv->peers_arr && priv->peers_arr->len > 0) {
+			ptr = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_wireguard_peer_unref);
+			for (i = 0; i < priv->peers_arr->len; i++)
+				g_ptr_array_add (ptr, nm_wireguard_peer_ref (_peers_get (priv, i)->peer));
+		}
+		g_value_init (&value, G_TYPE_PTR_ARRAY);
+		g_value_take_boxed (&value, ptr);
+		func (setting,
+		      property_info->name,
+		      &value,
+		      0,
+		      user_data);
+		return;
+	}
+
+	NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->enumerate_values (property_info,
+	                                                                        setting,
+	                                                                        func,
+	                                                                        user_data);
+}
+
+static gboolean
+aggregate (NMSetting *setting,
+           int type_i,
+           gpointer arg)
+{
+	NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+	NMConnectionAggregateType type = type_i;
+	NMSettingSecretFlags secret_flags;
+	guint i;
+
+	nm_assert (NM_IN_SET (type, NM_CONNECTION_AGGREGATE_ANY_SECRETS,
+	                            NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS));
+
+	switch (type) {
+
+	case NM_CONNECTION_AGGREGATE_ANY_SECRETS:
+		if (priv->private_key)
+			goto out_done;
+		for (i = 0; i < priv->peers_arr->len; i++) {
+			if (nm_wireguard_peer_get_preshared_key (_peers_get (priv, i)->peer))
+				goto out_done;
+		}
+		break;
+
+	case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS:
+#if NM_MORE_ASSERTS
+		if (!nm_setting_get_secret_flags (setting, NM_SETTING_WIREGUARD_PRIVATE_KEY, &secret_flags, NULL))
+			nm_assert_not_reached ();
+		nm_assert (secret_flags == priv->private_key_flags);
+#endif
+		if (priv->private_key_flags == NM_SETTING_SECRET_FLAG_NONE)
+			goto out_done;
+		for (i = 0; i < priv->peers_arr->len; i++) {
+			secret_flags = nm_wireguard_peer_get_preshared_key_flags (_peers_get (priv, i)->peer);
+			if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
+				goto out_done;
+		}
+		break;
+	}
+
+	return FALSE;
+
+out_done:
+	*((gboolean *) arg) = TRUE;
+	return TRUE;
+}
+
+static gboolean
+get_secret_flags (NMSetting *setting,
+                  const char *secret_name,
+                  NMSettingSecretFlags *out_flags,
+                  GError **error)
+{
+	if (NM_STR_HAS_PREFIX (secret_name, NM_SETTING_WIREGUARD_PEERS".")) {
+		NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+		gs_free char *public_key_free = NULL;
+		const char *public_key;
+		PeerData *pd;
+
+		public_key = peers_psk_get_secret_parse_a (secret_name, &public_key_free);
+		if (   public_key
+		    && (pd = _peers_get_by_public_key (priv, public_key, FALSE))) {
+			NM_SET_OUT (out_flags, nm_wireguard_peer_get_preshared_key_flags (pd->peer));
+			return TRUE;
+		}
+	}
+
+	return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->get_secret_flags (setting,
+	                                                                               secret_name,
+	                                                                               out_flags,
+	                                                                               error);
+}
+
+static gboolean
+set_secret_flags (NMSetting *setting,
+                  const char *secret_name,
+                  NMSettingSecretFlags flags,
+                  GError **error)
+{
+	if (NM_STR_HAS_PREFIX (secret_name, NM_SETTING_WIREGUARD_PEERS".")) {
+		NMSettingWireGuard *self = NM_SETTING_WIREGUARD (setting);
+		NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
+		gs_free char *public_key_free = NULL;
+		const char *public_key;
+		PeerData *pd;
+
+		public_key = peers_psk_get_secret_parse_a (secret_name, &public_key_free);
+		if (   public_key
+		    && (pd = _peers_get_by_public_key (priv, public_key, FALSE))) {
+
+			if (nm_wireguard_peer_get_preshared_key_flags (pd->peer) != flags) {
+				nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
+
+				peer = nm_wireguard_peer_new_clone (pd->peer, TRUE);
+				peer->preshared_key_flags = flags;
+				if (_peers_set (priv, peer, pd->idx, FALSE))
+					_peers_notify (self);
+			}
+
+			return TRUE;
+		}
+	}
+
+	return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->set_secret_flags (setting,
+	                                                                               secret_name,
+	                                                                               flags,
+	                                                                               error);
+}
+
+static void
+for_each_secret (NMSetting *setting,
+                 const char *data_key,
+                 GVariant *data_val,
+                 gboolean remove_non_secrets,
+                 _NMConnectionForEachSecretFunc callback,
+                 gpointer callback_data,
+                 GVariantBuilder *setting_builder)
+{
+	NMSettingWireGuard *s_wg;
+	NMSettingWireGuardPrivate *priv;
+	GVariantBuilder peers_builder;
+	GVariantIter *peer_iter;
+	GVariantIter data_iter;
+	const char *key;
+
+	if (!nm_streq (data_key, NM_SETTING_WIREGUARD_PEERS)) {
+		NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->for_each_secret (setting,
+		                                                                       data_key,
+		                                                                       data_val,
+		                                                                       remove_non_secrets,
+		                                                                       callback,
+		                                                                       callback_data,
+		                                                                       setting_builder);
+		return;
+	}
+
+	if (!g_variant_is_of_type (data_val, G_VARIANT_TYPE ("aa{sv}"))) {
+		/* invalid type. Silently ignore content as we cannot find secret-keys
+		 * here. */
+		return;
+	}
+
+	s_wg = NM_SETTING_WIREGUARD (setting);
+	priv = NM_SETTING_WIREGUARD_GET_PRIVATE (s_wg);
+
+	g_variant_builder_init (&peers_builder, G_VARIANT_TYPE ("aa{sv}"));
+	g_variant_iter_init (&data_iter, data_val);
+	while (g_variant_iter_next (&data_iter, "a{sv}", &peer_iter)) {
+		_nm_unused nm_auto_free_variant_iter GVariantIter *peer_iter_free = peer_iter;
+		gs_unref_variant GVariant *preshared_key = NULL;
+		PeerData *pd = NULL;
+		NMSettingSecretFlags secret_flags;
+		GVariant *val;
+		GVariantBuilder peer_builder;
+
+		g_variant_builder_init (&peer_builder, G_VARIANT_TYPE ("a{sv}"));
+
+		while (g_variant_iter_next (peer_iter, "{&sv}", &key, &val)) {
+			_nm_unused gs_unref_variant GVariant *val_free = val;
+
+			if (nm_streq (key, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) {
+				if (   !preshared_key
+				    && g_variant_is_of_type (val, G_VARIANT_TYPE_STRING))
+					preshared_key = g_variant_ref (val);
+				continue;
+			}
+
+			if (nm_streq (key, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY)) {
+				if (   !pd
+				    && g_variant_is_of_type (val, G_VARIANT_TYPE_STRING))
+					pd = _peers_get_by_public_key (priv, g_variant_get_string (val, NULL), TRUE);
+			} else if (remove_non_secrets)
+				continue;
+
+			g_variant_builder_add (&peer_builder, "{sv}", key, val);
+		}
+
+		if (pd && preshared_key) {
+			/* without specifying a public-key of an existing peer, the secret is
+			 * ignored. */
+			secret_flags = nm_wireguard_peer_get_preshared_key_flags (pd->peer);
+			if (callback (secret_flags, callback_data))
+				g_variant_builder_add (&peer_builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, preshared_key);
+		}
+
+		g_variant_builder_add (&peers_builder, "a{sv}", &peer_builder);
+	}
+
+	g_variant_builder_add (setting_builder,
+	                       "{sv}",
+	                       NM_SETTING_WIREGUARD_PEERS,
+	                       g_variant_builder_end (&peers_builder));
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMSettingWireGuard *setting = NM_SETTING_WIREGUARD (object);
+	NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+
+	switch (prop_id) {
+	case PROP_FWMARK:
+		g_value_set_uint (value, priv->fwmark);
+		break;
+	case PROP_LISTEN_PORT:
+		g_value_set_uint (value, priv->listen_port);
+		break;
+	case PROP_MTU:
+		g_value_set_uint (value, priv->mtu);
+		break;
+	case PROP_PEER_ROUTES:
+		g_value_set_boolean (value, priv->peer_routes);
+		break;
+	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;
+	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_FWMARK:
+		priv->fwmark = g_value_get_uint (value);
+		break;
+	case PROP_LISTEN_PORT:
+		priv->listen_port = g_value_get_uint (value);
+		break;
+	case PROP_MTU:
+		priv->mtu = g_value_get_uint (value);
+		break;
+	case PROP_PEER_ROUTES:
+		priv->peer_routes = g_value_get_boolean (value);
+		break;
+	case PROP_PRIVATE_KEY:
+		nm_clear_pointer (&priv->private_key, nm_free_secret);
+		str = g_value_get_string (value);
+		if (str) {
+			if (nm_utils_base64secret_normalize (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;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_setting_wireguard_init (NMSettingWireGuard *setting)
+{
+	NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
+
+	priv->peers_arr = g_ptr_array_new ();
+	priv->peers_hash = g_hash_table_new (nm_pstr_hash, nm_pstr_equal);
+	priv->peer_routes = TRUE;
+}
+
+/**
+ * nm_setting_wireguard_new:
+ *
+ * Creates a new #NMSettingWireGuard object with default values.
+ *
+ * Returns: (transfer full): the new empty #NMSettingWireGuard object
+ *
+ * Since: 1.16
+ **/
+NMSetting *
+nm_setting_wireguard_new (void)
+{
+	return g_object_new (NM_TYPE_SETTING_WIREGUARD, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (object);
+
+	nm_free_secret (priv->private_key);
+
+	_peers_clear (priv);
+	g_ptr_array_unref (priv->peers_arr);
+	g_hash_table_unref (priv->peers_hash);
+
+	G_OBJECT_CLASS (nm_setting_wireguard_parent_class)->finalize (object);
+}
+
+static void
+nm_setting_wireguard_class_init (NMSettingWireGuardClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMSettingClass *setting_class = NM_SETTING_CLASS (klass);
+	GArray *properties_override = _nm_sett_info_property_override_create_array ();
+
+	object_class->get_property = get_property;
+	object_class->set_property = set_property;
+	object_class->finalize     = finalize;
+
+	setting_class->verify                    = verify;
+	setting_class->verify_secrets            = verify_secrets;
+	setting_class->need_secrets              = need_secrets;
+	setting_class->clear_secrets             = clear_secrets;
+	setting_class->update_one_secret         = update_one_secret;
+	setting_class->compare_property          = compare_property;
+	setting_class->duplicate_copy_properties = duplicate_copy_properties;
+	setting_class->enumerate_values          = enumerate_values;
+	setting_class->aggregate                 = aggregate;
+	setting_class->get_secret_flags          = get_secret_flags;
+	setting_class->set_secret_flags          = set_secret_flags;
+	setting_class->for_each_secret           = for_each_secret;
+
+	/**
+	 * NMSettingWireGuard:private-key:
+	 *
+	 * The 256 bit private-key in base64 encoding.
+	 *
+	 * Since: 1.16
+	 **/
+	obj_properties[PROP_PRIVATE_KEY] =
+	    g_param_spec_string (NM_SETTING_WIREGUARD_PRIVATE_KEY, "", "",
+	                         NULL,
+	                           G_PARAM_READWRITE
+	                         | NM_SETTING_PARAM_SECRET
+	                         | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingWireGuard:private-key-flags:
+	 *
+	 * Flags indicating how to handle the #NMSettingWirelessSecurity:private-key
+	 * property.
+	 *
+	 * Since: 1.16
+	 **/
+	obj_properties[PROP_PRIVATE_KEY_FLAGS] =
+	    g_param_spec_flags (NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS, "", "",
+	                        NM_TYPE_SETTING_SECRET_FLAGS,
+	                        NM_SETTING_SECRET_FLAG_NONE,
+	                          G_PARAM_READWRITE
+	                        | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingWireGuard:fwmark:
+	 *
+	 * The use of fwmark is optional and is by default off. Setting it to 0
+	 * disables it. Otherwise it is a 32-bit fwmark for outgoing packets.
+	 *
+	 * Since: 1.16
+	 **/
+	obj_properties[PROP_FWMARK] =
+	    g_param_spec_uint (NM_SETTING_WIREGUARD_FWMARK, "", "",
+	                       0, G_MAXUINT32, 0,
+	                         G_PARAM_READWRITE
+	                       | NM_SETTING_PARAM_INFERRABLE
+	                       | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingWireGuard:listen-port:
+	 *
+	 * The listen-port. If listen-port is not specified, the port will be chosen
+	 * randomly when the interface comes up.
+	 *
+	 * Since: 1.16
+	 **/
+	obj_properties[PROP_LISTEN_PORT] =
+	    g_param_spec_uint (NM_SETTING_WIREGUARD_LISTEN_PORT, "", "",
+	                       0, 65535, 0,
+	                         G_PARAM_READWRITE
+	                       | NM_SETTING_PARAM_INFERRABLE
+	                       | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingWireGuard:peer-routes:
+	 *
+	 * Whether to automatically add routes for the AllowedIPs ranges
+	 * of the peers. If %TRUE (the default), NetworkManager will automatically
+	 * add routes in the routing tables according to ipv4.route-table and
+	 * ipv6.route-table.
+	 * If %FALSE, no such routes are added automatically. In this case, the
+	 * user may want to configure static routes in ipv4.routes and ipv6.routes,
+	 * respectively.
+	 *
+	 * Since: 1.16
+	 **/
+	obj_properties[PROP_PEER_ROUTES] =
+	    g_param_spec_boolean (NM_SETTING_WIREGUARD_PEER_ROUTES, "", "",
+	                          TRUE,
+	                            G_PARAM_READWRITE
+	                          | NM_SETTING_PARAM_INFERRABLE
+	                          | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingWireGuard:mtu:
+	 *
+	 * If non-zero, only transmit packets of the specified size or smaller,
+	 * breaking larger packets up into multiple fragments.
+	 *
+	 * If zero a default MTU is used. Note that contrary to wg-quick's MTU
+	 * setting, this does not take into account the current routes at the
+	 * time of activation.
+	 *
+	 * Since: 1.16
+	 **/
+	obj_properties[PROP_MTU] =
+	    g_param_spec_uint (NM_SETTING_WIREGUARD_MTU, "", "",
+	                       0, G_MAXUINT32, 0,
+	                         G_PARAM_READWRITE
+	                       | NM_SETTING_PARAM_INFERRABLE
+	                       | G_PARAM_STATIC_STRINGS);
+
+	/* ---dbus---
+	 * property: peers
+	 * format: array of 'a{sv}'
+	 * description: Array of dictionaries for the WireGuard peers.
+	 * ---end---
+	 */
+	_properties_override_add_dbus_only (properties_override,
+	                                    NM_SETTING_WIREGUARD_PEERS,
+	                                    G_VARIANT_TYPE ("aa{sv}"),
+	                                    _peers_dbus_only_synth,
+	                                    _peers_dbus_only_set);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+	_nm_setting_class_commit_full (setting_class, NM_META_SETTING_TYPE_WIREGUARD, NULL, properties_override);
+}
diff --git a/libnm-core/nm-setting-wireguard.h b/libnm-core/nm-setting-wireguard.h
new file mode 100644
index 00000000..017eb1f6
--- /dev/null
+++ b/libnm-core/nm-setting-wireguard.h
@@ -0,0 +1,213 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 - 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_SETTING_WIREGUARD_H__
+#define __NM_SETTING_WIREGUARD_H__
+
+#if !defined (__NETWORKMANAGER_H_INSIDE__) && !defined (NETWORKMANAGER_COMPILATION)
+#error "Only <NetworkManager.h> can be included directly."
+#endif
+
+#include "nm-setting.h"
+#include "nm-utils.h"
+
+G_BEGIN_DECLS
+
+/*****************************************************************************/
+
+#define NM_WIREGUARD_PUBLIC_KEY_LEN     32
+#define NM_WIREGUARD_SYMMETRIC_KEY_LEN  32
+
+/*****************************************************************************/
+
+typedef struct _NMWireGuardPeer NMWireGuardPeer;
+
+NM_AVAILABLE_IN_1_16
+GType nm_wireguard_peer_get_type (void);
+
+NM_AVAILABLE_IN_1_16
+NMWireGuardPeer *nm_wireguard_peer_new (void);
+
+NM_AVAILABLE_IN_1_16
+NMWireGuardPeer *nm_wireguard_peer_new_clone (const NMWireGuardPeer *self,
+                                              gboolean with_secrets);
+
+NM_AVAILABLE_IN_1_16
+NMWireGuardPeer *nm_wireguard_peer_ref (NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+void nm_wireguard_peer_unref (NMWireGuardPeer *self);
+
+NM_AVAILABLE_IN_1_16
+void nm_wireguard_peer_seal (NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+gboolean nm_wireguard_peer_is_sealed (const NMWireGuardPeer *self);
+
+NM_AVAILABLE_IN_1_16
+const char *nm_wireguard_peer_get_public_key (const NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+gboolean nm_wireguard_peer_set_public_key (NMWireGuardPeer *self,
+                                           const char *public_key,
+                                           gboolean accept_invalid);
+
+NM_AVAILABLE_IN_1_16
+const char *nm_wireguard_peer_get_preshared_key (const NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+gboolean nm_wireguard_peer_set_preshared_key (NMWireGuardPeer *self,
+                                              const char *preshared_key,
+                                              gboolean accept_invalid);
+
+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
+gboolean nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self,
+                                         const char *endpoint,
+                                         gboolean allow_invalid);
+
+NM_AVAILABLE_IN_1_16
+guint nm_wireguard_peer_get_allowed_ips_len (const NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+const char *nm_wireguard_peer_get_allowed_ip (const NMWireGuardPeer *self,
+                                              guint idx,
+                                              gboolean *out_is_valid);
+NM_AVAILABLE_IN_1_16
+void nm_wireguard_peer_clear_allowed_ips (NMWireGuardPeer *self);
+NM_AVAILABLE_IN_1_16
+gboolean nm_wireguard_peer_append_allowed_ip (NMWireGuardPeer *self,
+                                              const char *allowed_ip,
+                                              gboolean accept_invalid);
+NM_AVAILABLE_IN_1_16
+gboolean nm_wireguard_peer_remove_allowed_ip (NMWireGuardPeer *self,
+                                              guint idx);
+
+NM_AVAILABLE_IN_1_16
+gboolean nm_wireguard_peer_is_valid (const NMWireGuardPeer *self,
+                                     gboolean check_non_secrets,
+                                     gboolean check_secrets,
+                                     GError **error);
+
+NM_AVAILABLE_IN_1_16
+int nm_wireguard_peer_cmp (const NMWireGuardPeer *a,
+                           const NMWireGuardPeer *b,
+                           NMSettingCompareFlags compare_flags);
+
+/*****************************************************************************/
+
+#define NM_TYPE_SETTING_WIREGUARD            (nm_setting_wireguard_get_type ())
+#define NM_SETTING_WIREGUARD(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTING_WIREGUARD, NMSettingWireGuard))
+#define NM_SETTING_WIREGUARD_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTING_WIREGUARD, NMSettingWireGuardClass))
+#define NM_IS_SETTING_WIREGUARD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SETTING_WIREGUARD))
+#define NM_IS_SETTING_WIREGUARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SETTING_WIREGUARD))
+#define NM_SETTING_WIREGUARD_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SETTING_WIREGUARD, NMSettingWireGuardClass))
+
+#define NM_SETTING_WIREGUARD_SETTING_NAME "wireguard"
+
+#define NM_SETTING_WIREGUARD_FWMARK            "fwmark"
+#define NM_SETTING_WIREGUARD_LISTEN_PORT       "listen-port"
+#define NM_SETTING_WIREGUARD_PRIVATE_KEY       "private-key"
+#define NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS "private-key-flags"
+
+#define NM_SETTING_WIREGUARD_PEERS             "peers"
+
+#define NM_SETTING_WIREGUARD_MTU               "mtu"
+#define NM_SETTING_WIREGUARD_PEER_ROUTES       "peer-routes"
+
+#define NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS          "allowed-ips"
+#define NM_WIREGUARD_PEER_ATTR_ENDPOINT             "endpoint"
+#define NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE "persistent-keepalive"
+#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_PUBLIC_KEY           "public-key"
+
+/*****************************************************************************/
+
+typedef struct _NMSettingWireGuardClass NMSettingWireGuardClass;
+
+NM_AVAILABLE_IN_1_16
+GType nm_setting_wireguard_get_type (void);
+
+NM_AVAILABLE_IN_1_16
+NMSetting *nm_setting_wireguard_new (void);
+
+/*****************************************************************************/
+
+NM_AVAILABLE_IN_1_16
+const char *nm_setting_wireguard_get_private_key (NMSettingWireGuard *self);
+
+NM_AVAILABLE_IN_1_16
+NMSettingSecretFlags nm_setting_wireguard_get_private_key_flags (NMSettingWireGuard *self);
+
+NM_AVAILABLE_IN_1_16
+guint16 nm_setting_wireguard_get_listen_port (NMSettingWireGuard *self);
+
+NM_AVAILABLE_IN_1_16
+guint32 nm_setting_wireguard_get_fwmark (NMSettingWireGuard *self);
+
+/*****************************************************************************/
+
+NM_AVAILABLE_IN_1_16
+guint nm_setting_wireguard_get_peers_len (NMSettingWireGuard *self);
+
+NM_AVAILABLE_IN_1_16
+NMWireGuardPeer *nm_setting_wireguard_get_peer (NMSettingWireGuard *self,
+                                                guint idx);
+
+NM_AVAILABLE_IN_1_16
+NMWireGuardPeer *nm_setting_wireguard_get_peer_by_public_key (NMSettingWireGuard *self,
+                                                              const char *public_key,
+                                                              guint *out_idx);
+
+NM_AVAILABLE_IN_1_16
+void nm_setting_wireguard_set_peer (NMSettingWireGuard *self,
+                                    NMWireGuardPeer *peer,
+                                    guint idx);
+
+NM_AVAILABLE_IN_1_16
+void nm_setting_wireguard_append_peer (NMSettingWireGuard *self,
+                                       NMWireGuardPeer *peer);
+
+NM_AVAILABLE_IN_1_16
+gboolean nm_setting_wireguard_remove_peer (NMSettingWireGuard *self,
+                                           guint idx);
+
+NM_AVAILABLE_IN_1_16
+guint nm_setting_wireguard_clear_peers (NMSettingWireGuard *self);
+
+NM_AVAILABLE_IN_1_16
+gboolean nm_setting_wireguard_get_peer_routes (NMSettingWireGuard *self);
+
+NM_AVAILABLE_IN_1_16
+guint32 nm_setting_wireguard_get_mtu (NMSettingWireGuard *self);
+
+/*****************************************************************************/
+
+G_END_DECLS
+
+#endif /* __NM_SETTING_WIREGUARD_H__ */
diff --git a/libnm-core/nm-setting-wireless-security.c b/libnm-core/nm-setting-wireless-security.c
index fe339d9e..f689751e 100644
--- a/libnm-core/nm-setting-wireless-security.c
+++ b/libnm-core/nm-setting-wireless-security.c
@@ -24,8 +24,6 @@
 
 #include "nm-setting-wireless-security.h"
 
-#include <string.h>
-
 #include "nm-setting-8021x.h"
 #include "nm-utils.h"
 #include "nm-utils-private.h"
@@ -863,6 +861,15 @@ need_secrets (NMSetting *setting)
 		goto no_secrets;
 	}
 
+	/* SAE, used in MESH and WPA3-Personal */
+	if (strcmp (priv->key_mgmt, "sae") == 0) {
+		if (!priv->psk || !*priv->psk) {
+			g_ptr_array_add (secrets, NM_SETTING_WIRELESS_SECURITY_PSK);
+			return secrets;
+		}
+		goto no_secrets;
+	}
+
 	/* LEAP */
 	if (   priv->auth_alg
 	    && !strcmp (priv->auth_alg, "leap")
@@ -894,7 +901,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
 	NMSettingWirelessSecurity *self = NM_SETTING_WIRELESS_SECURITY (setting);
 	NMSettingWirelessSecurityPrivate *priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE (self);
-	const char *valid_key_mgmt[] = { "none", "ieee8021x", "wpa-none", "wpa-psk", "wpa-eap", NULL };
+	const char *valid_key_mgmt[] = { "none", "ieee8021x", "wpa-none", "wpa-psk", "wpa-eap", "sae", NULL };
 	const char *valid_auth_algs[] = { "open", "shared", "leap", NULL };
 	const char *valid_protos[] = { "wpa", "rsn", NULL };
 	const char *valid_pairwise[] = { "tkip", "ccmp", NULL };
@@ -1072,36 +1079,22 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	if (   NM_IN_SET (priv->pmf,
 	                  NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL,
 	                  NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)
-	    && !NM_IN_STRSET (priv->key_mgmt, "wpa-eap", "wpa-psk")) {
+	    && !NM_IN_STRSET (priv->key_mgmt, "wpa-eap", "wpa-psk", "sae")) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-		             _("'%s' can only be used with '%s=%s' or '%s=%s'"),
-		             priv->pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL ? "optional" : "required",
-		             NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap",
-		             NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk");
+		             _("'%s' can only be used with 'wpa-eap', 'wpa-psk' or 'sae' key management "),
+		             priv->pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL ? "optional" : "required");
 		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_PMF);
 		return FALSE;
 	}
 
-	/* WPS */
-	if (priv->wps_method > NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN) {
-		g_set_error_literal (error,
-		                     NM_CONNECTION_ERROR,
-		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-		                     _("property is invalid"));
-		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_WPS_METHOD);
+	if (!_nm_utils_wps_method_validate (priv->wps_method,
+	                                    NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+	                                    NM_SETTING_WIRELESS_SECURITY_WPS_METHOD,
+	                                    FALSE,
+	                                    error))
 		return FALSE;
-	}
-
-	if (priv->wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DISABLED && priv->wps_method != NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DISABLED) {
-		g_set_error_literal (error,
-		                     NM_CONNECTION_ERROR,
-		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-		                     _("can't be simultaneously disabled and enabled"));
-		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_WPS_METHOD);
-		return FALSE;
-	}
 
 	return TRUE;
 }
@@ -1151,7 +1144,9 @@ verify_secrets (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 
 	/* WPA-PSK */
-	if (priv->psk && !nm_utils_wpa_psk_valid (priv->psk)) {
+	if (   priv->psk
+	    && strcmp (priv->key_mgmt, "sae") != 0
+	    && !nm_utils_wpa_psk_valid (priv->psk)) {
 		g_set_error_literal (error,
 		                     NM_CONNECTION_ERROR,
 		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -1459,8 +1454,8 @@ nm_setting_wireless_security_class_init (NMSettingWirelessSecurityClass *klass)
 	 *
 	 * Key management used for the connection.  One of "none" (WEP), "ieee8021x"
 	 * (Dynamic WEP), "wpa-none" (Ad-Hoc WPA-PSK), "wpa-psk" (infrastructure
-	 * WPA-PSK), or "wpa-eap" (WPA-Enterprise).  This property must be set for
-	 * any Wi-Fi connection that uses security.
+	 * WPA-PSK), "sae" (SAE) or "wpa-eap" (WPA-Enterprise).
+	 * This property must be set for any Wi-Fi connection that uses security.
 	 **/
 	/* ---ifcfg-rh---
 	 * property: key-mgmt
@@ -1734,12 +1729,11 @@ nm_setting_wireless_security_class_init (NMSettingWirelessSecurityClass *klass)
 	/**
 	 * NMSettingWirelessSecurity:psk:
 	 *
-	 * Pre-Shared-Key for WPA networks.  If the key is 64-characters long, it
-	 * must contain only hexadecimal characters and is interpreted as a
-	 * hexadecimal WPA key.  Otherwise, the key must be between 8 and 63 ASCII
-	 * characters (as specified in the 802.11i standard) and is interpreted as a
-	 * WPA passphrase, and is hashed to derive the actual WPA-PSK used when
-	 * connecting to the Wi-Fi network.
+	 * Pre-Shared-Key for WPA networks. For WPA-PSK, it's either an ASCII
+	 * passphrase of 8 to 63 characters that is (as specified in the 802.11i
+	 * standard) hashed to derive the actual key, or the key in form of 64
+	 * hexadecimal character. The WPA3-Personal networks use a passphrase
+	 * of any length for SAE authentication.
 	 **/
 	/* ---ifcfg-rh---
 	 * property: psk
diff --git a/libnm-core/nm-setting-wireless-security.h b/libnm-core/nm-setting-wireless-security.h
index 2f4b4084..c560f348 100644
--- a/libnm-core/nm-setting-wireless-security.h
+++ b/libnm-core/nm-setting-wireless-security.h
@@ -100,7 +100,7 @@ typedef enum {
  * Configure the use of WPS by a connection while it activates.
  *
  * Note: prior to 1.16, this was a GEnum type instead of a GFlags type
- * altough, with the same numeric values.
+ * although, with the same numeric values.
  *
  * Since: 1.10
  **/
diff --git a/libnm-core/nm-setting-wireless.c b/libnm-core/nm-setting-wireless.c
index 5d3f8fe8..b4cb1056 100644
--- a/libnm-core/nm-setting-wireless.c
+++ b/libnm-core/nm-setting-wireless.c
@@ -24,7 +24,6 @@
 
 #include "nm-setting-wireless.h"
 
-#include <string.h>
 #include <net/ethernet.h>
 
 #include "nm-utils.h"
diff --git a/libnm-core/nm-setting-wireless.h b/libnm-core/nm-setting-wireless.h
index 4611f94b..a2ae38a4 100644
--- a/libnm-core/nm-setting-wireless.h
+++ b/libnm-core/nm-setting-wireless.h
@@ -66,21 +66,21 @@ G_BEGIN_DECLS
  */
 typedef enum { /*< flags >*/
 	NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE                 = 0, /*< skip >*/
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY                  = (1 << 1),
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT           = (1 << 2),
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC                = (1 << 3),
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE    = (1 << 4),
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST = (1 << 5),
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE       = (1 << 6),
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE       = (1 << 7),
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP                  = (1 << 8),
-	_NM_SETTING_WIRELESS_WAKE_ON_WLAN_NUM, /*< skip >*/
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_LAST                 = _NM_SETTING_WIRELESS_WAKE_ON_WLAN_NUM - 1, /*< skip >*/
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_ALL                  = ((NM_SETTING_WIRELESS_WAKE_ON_WLAN_LAST << 1) - 1) - (1 << 0 /*DEFAULT*/), /*< skip >*/
-
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT              = (1 << 0),
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE               = (1 << 15),
-	NM_SETTING_WIRELESS_WAKE_ON_WLAN_EXCLUSIVE_FLAGS = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT | NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE, /*< skip >*/
+	NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY                  = 0x2,
+	NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT           = 0x4,
+	NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC                = 0x8,
+	NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE    = 0x10,
+	NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST = 0x20,
+	NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE       = 0x40,
+	NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE       = 0x80,
+	NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP                  = 0x100,
+
+	NM_SETTING_WIRELESS_WAKE_ON_WLAN_ALL                  = 0x1FE,
+
+	NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT              = 0x1,
+	NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE               = 0x8000,
+
+	NM_SETTING_WIRELESS_WAKE_ON_WLAN_EXCLUSIVE_FLAGS      = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT | NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE, /*< skip >*/
 } NMSettingWirelessWakeOnWLan;
 
 #define NM_SETTING_WIRELESS_SSID        "ssid"
diff --git a/libnm-core/nm-setting.c b/libnm-core/nm-setting.c
index 976f7f00..2e9081df 100644
--- a/libnm-core/nm-setting.c
+++ b/libnm-core/nm-setting.c
@@ -24,23 +24,12 @@
 
 #include "nm-setting.h"
 
-#include <string.h>
-
 #include "nm-setting-private.h"
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "nm-utils-private.h"
 #include "nm-property-compare.h"
 
-#include "nm-setting-connection.h"
-#include "nm-setting-bond.h"
-#include "nm-setting-bridge.h"
-#include "nm-setting-bridge-port.h"
-#include "nm-setting-pppoe.h"
-#include "nm-setting-team.h"
-#include "nm-setting-team-port.h"
-#include "nm-setting-vpn.h"
-
 /**
  * SECTION:nm-setting
  * @short_description: Describes related configuration information
@@ -67,7 +56,7 @@ typedef struct {
 	NMSettingPriority priority;
 } SettingInfo;
 
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+NM_GOBJECT_PROPERTIES_DEFINE (NMSetting,
 	PROP_NAME,
 );
 
@@ -553,6 +542,35 @@ _nm_setting_class_get_sett_info (NMSettingClass *setting_class)
 
 /*****************************************************************************/
 
+void
+_nm_setting_emit_property_changed (NMSetting *setting)
+{
+	/* Some settings have "properties" that are not implemented as GObject properties.
+	 *
+	 * For example:
+	 *
+	 *   - gendata-base settings like NMSettingEthtool. Here properties are just
+	 *     GVariant values in the gendata hash.
+	 *
+	 *   - NMSettingWireGuard's peers are not backed by a GObject property. Instead
+	 *     there is C-API to access/modify peers.
+	 *
+	 * We still want to emit property-changed notifications for such properties,
+	 * in particular because NMConnection registers to such signals to re-emit
+	 * it as NM_CONNECTION_CHANGED signal. In fact, there are unlikely any other
+	 * uses of such a property-changed signal, because generally it doesn't make
+	 * too much sense.
+	 *
+	 * So, instead of adding yet another (artificial) signal "setting-changed",
+	 * hijack the "notify" signal and just notify about changes of the "name".
+	 * Of course, the "name" doesn't really ever change, because it's tied to
+	 * the GObject's type.
+	 */
+	_notify (setting, PROP_NAME);
+}
+
+/*****************************************************************************/
+
 gboolean
 _nm_setting_use_legacy_property (NMSetting *setting,
                                  GVariant *connection_dict,
@@ -1082,6 +1100,8 @@ duplicate_copy_properties (const NMSettInfoSetting *sett_info,
 	if (sett_info->detail.gendata_info) {
 		GenData *gendata = _gendata_hash (src, FALSE);
 
+		nm_assert (!_gendata_hash (dst, FALSE));
+
 		if (   gendata
 		    && g_hash_table_size (gendata->hash) > 0) {
 			GHashTableIter iter;
@@ -1303,7 +1323,7 @@ _nm_setting_should_compare_secret_property (NMSetting *setting,
 	 *   - @other also has the secret flat to be ignored.
 	 *
 	 * This makes the check symmetric (aside the fact that @setting must
-	 * have the secret while @other may not -- which is asymetric). */
+	 * have the secret while @other may not -- which is asymmetric). */
 	if (   NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS)
 	    && NM_FLAGS_HAS (a_secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)
 	    && (   !other
@@ -1602,7 +1622,7 @@ nm_setting_diff (NMSetting *a,
 				 *
 				 * Note that compare_property() called with two settings will ignore secrets
 				 * based on the flags, but it will do so if *both* settings have the flag we
-				 * look for. So that is symetric behavior and good.
+				 * look for. So that is symmetric behavior and good.
 				 *
 				 * But for the purpose of diff(), we do a asymmetric comparison because and
 				 * we want to skip testing the property if setting @a alone indicates to do
@@ -1698,6 +1718,27 @@ nm_setting_diff (NMSetting *a,
 	}
 }
 
+static void
+enumerate_values (const NMSettInfoProperty *property_info,
+                  NMSetting *setting,
+                  NMSettingValueIterFn func,
+                  gpointer user_data)
+{
+	GValue value = G_VALUE_INIT;
+
+	if (!property_info->param_spec)
+		return;
+
+	g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec));
+	g_object_get_property (G_OBJECT (setting), property_info->param_spec->name, &value);
+	func (setting,
+	      property_info->param_spec->name,
+	      &value,
+	      property_info->param_spec->flags,
+	      user_data);
+	g_value_unset (&value);
+}
+
 /**
  * nm_setting_enumerate_values:
  * @setting: the #NMSetting
@@ -1753,50 +1794,24 @@ nm_setting_enumerate_values (NMSetting *setting,
 	}
 
 	for (i = 0; i < sett_info->property_infos_len; i++) {
-		GParamSpec *prop_spec = _nm_sett_info_property_info_get_sorted (sett_info, i)->param_spec;
-		GValue value = G_VALUE_INIT;
-
-		if (!prop_spec)
-			continue;
-
-		g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (prop_spec));
-		g_object_get_property (G_OBJECT (setting), prop_spec->name, &value);
-		func (setting, prop_spec->name, &value, prop_spec->flags, user_data);
-		g_value_unset (&value);
+		NM_SETTING_GET_CLASS (setting)->enumerate_values (_nm_sett_info_property_info_get_sorted (sett_info, i),
+		                                                  setting,
+		                                                  func,
+		                                                  user_data);
 	}
 }
 
-/**
- * _nm_setting_aggregate:
- * @setting: the #NMSetting to aggregate.
- * @type: the #NMConnectionAggregateType aggregate type.
- * @arg: the in/out arguments for aggregation. They depend on @type.
- *
- * This is the implementation detail of _nm_connection_aggregate(). It
- * makes no sense to call this function directly outside of _nm_connection_aggregate().
- *
- * Returns: %TRUE if afterwards the aggregation is complete. That means,
- *   the only caller _nm_connection_aggregate() will not visit other settings
- *   after a setting returns %TRUE (indicating that there is nothing further
- *   to aggregate). Note that is very different from the boolean return
- *   argument of _nm_connection_aggregate(), which serves a different purpose.
- */
-gboolean
-_nm_setting_aggregate (NMSetting *setting,
-                       NMConnectionAggregateType type,
-                       gpointer arg)
+static gboolean
+aggregate (NMSetting *setting,
+           int type_i,
+           gpointer arg)
 {
+	NMConnectionAggregateType type = type_i;
 	const NMSettInfoSetting *sett_info;
 	guint i;
 
-	g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
-	g_return_val_if_fail (arg, FALSE);
-	g_return_val_if_fail (NM_IN_SET (type, NM_CONNECTION_AGGREGATE_ANY_SECRETS,
-	                                       NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS),
-	                      FALSE);
-
-	if (NM_IS_SETTING_VPN (setting))
-		return _nm_setting_vpn_aggregate (NM_SETTING_VPN (setting), type, arg);
+	nm_assert (NM_IN_SET (type, NM_CONNECTION_AGGREGATE_ANY_SECRETS,
+	                            NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS));
 
 	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
 	for (i = 0; i < sett_info->property_infos_len; i++) {
@@ -1840,6 +1855,35 @@ _nm_setting_aggregate (NMSetting *setting,
 	return FALSE;
 }
 
+/**
+ * _nm_setting_aggregate:
+ * @setting: the #NMSetting to aggregate.
+ * @type: the #NMConnectionAggregateType aggregate type.
+ * @arg: the in/out arguments for aggregation. They depend on @type.
+ *
+ * This is the implementation detail of _nm_connection_aggregate(). It
+ * makes no sense to call this function directly outside of _nm_connection_aggregate().
+ *
+ * Returns: %TRUE if afterwards the aggregation is complete. That means,
+ *   the only caller _nm_connection_aggregate() will not visit other settings
+ *   after a setting returns %TRUE (indicating that there is nothing further
+ *   to aggregate). Note that is very different from the boolean return
+ *   argument of _nm_connection_aggregate(), which serves a different purpose.
+ */
+gboolean
+_nm_setting_aggregate (NMSetting *setting,
+                       NMConnectionAggregateType type,
+                       gpointer arg)
+{
+	g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
+	g_return_val_if_fail (arg, FALSE);
+	g_return_val_if_fail (NM_IN_SET (type, NM_CONNECTION_AGGREGATE_ANY_SECRETS,
+	                                       NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS),
+	                      FALSE);
+
+	return NM_SETTING_GET_CLASS (setting)->aggregate (setting, type, arg);
+}
+
 static gboolean
 clear_secrets (const NMSettInfoSetting *sett_info,
                guint property_idx,
@@ -2023,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);
 
@@ -2040,6 +2084,26 @@ _nm_setting_update_secrets (NMSetting *setting, GVariant *secrets, GError **erro
 }
 
 static void
+for_each_secret (NMSetting *setting,
+                 const char *secret_name,
+                 GVariant *val,
+                 gboolean remove_non_secrets,
+                 _NMConnectionForEachSecretFunc callback,
+                 gpointer callback_data,
+                 GVariantBuilder *setting_builder)
+{
+	NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
+
+	if (!nm_setting_get_secret_flags (setting, secret_name, &secret_flags, NULL)) {
+		if (!remove_non_secrets)
+			g_variant_builder_add (setting_builder, "{sv}", secret_name, val);
+		return;
+	}
+	if (callback (secret_flags, callback_data))
+		g_variant_builder_add (setting_builder, "{sv}", secret_name, val);
+}
+
+static void
 _set_error_secret_property_not_found (GError **error,
                                       NMSetting *setting,
                                       const char *secret_name)
@@ -2293,7 +2357,7 @@ _nm_setting_gendata_notify (NMSetting *setting,
 
 	gendata = _gendata_hash (setting, FALSE);
 	if (!gendata)
-		return;
+		goto out;
 
 	nm_clear_g_free (&gendata->values);
 
@@ -2303,7 +2367,7 @@ _nm_setting_gendata_notify (NMSetting *setting,
 		nm_clear_g_free (&gendata->names);
 	}
 
-	/* Note, that currently there is now way to notify the subclass when gendata changed.
+	/* Note, currently there is no way to notify the subclass when gendata changed.
 	 * gendata is only changed in two situations:
 	 *   1) from within NMSetting itself, for example when creating a NMSetting instance
 	 *      from keyfile or a D-Bus GVariant.
@@ -2316,6 +2380,9 @@ _nm_setting_gendata_notify (NMSetting *setting,
 	 *
 	 * If we ever need it, then we would need to call a virtual function to notify the subclass
 	 * that gendata changed. */
+
+out:
+	_nm_setting_emit_property_changed (setting);
 }
 
 GVariant *
@@ -2382,7 +2449,7 @@ out_zero:
 /**
  * nm_setting_gendata_get_all_names:
  * @setting: the #NMSetting
- * @out_len: (allow-none): (out):
+ * @out_len: (allow-none) (out):
  *
  * Gives the number of generic data elements and optionally returns all their
  * key names and values. This API is low level access and unless you know what you
@@ -2438,7 +2505,7 @@ nm_setting_gendata_get_all_values (NMSetting *setting)
 
 void
 _nm_setting_gendata_to_gvalue (NMSetting *setting,
-                                GValue *value)
+                               GValue *value)
 {
 	GenData *gendata;
 	GHashTable *new;
@@ -2490,7 +2557,7 @@ _nm_setting_gendata_reset_from_hash (NMSetting *setting,
 	}
 
 	/* let's not bother to find out whether the new hash has any different
-	 * content the the current gendata. Just replace it. */
+	 * content the current gendata. Just replace it. */
 	g_hash_table_remove_all (gendata->hash);
 	if (num > 0) {
 		g_hash_table_iter_init (&iter, new);
@@ -2571,7 +2638,10 @@ nm_setting_class_init (NMSettingClass *setting_class)
 	setting_class->set_secret_flags          = set_secret_flags;
 	setting_class->compare_property          = compare_property;
 	setting_class->clear_secrets             = clear_secrets;
+	setting_class->for_each_secret           = for_each_secret;
 	setting_class->duplicate_copy_properties = duplicate_copy_properties;
+	setting_class->enumerate_values          = enumerate_values;
+	setting_class->aggregate                 = aggregate;
 
 	/**
 	 * NMSetting:name:
diff --git a/libnm-core/nm-setting.h b/libnm-core/nm-setting.h
index 0f0ac8f3..fdf4a4c5 100644
--- a/libnm-core/nm-setting.h
+++ b/libnm-core/nm-setting.h
@@ -170,6 +170,26 @@ typedef gboolean (*NMSettingClearSecretsWithFlagsFn) (NMSetting *setting,
 
 struct _NMMetaSettingInfo;
 struct _NMSettInfoSetting;
+struct _NMSettInfoProperty;
+
+/**
+ * NMSettingValueIterFn:
+ * @setting: The setting for which properties are being iterated, given to
+ * nm_setting_enumerate_values()
+ * @key: The value/property name
+ * @value: The property's value
+ * @flags: The property's flags, like %NM_SETTING_PARAM_SECRET
+ * @user_data: User data passed to nm_setting_enumerate_values()
+ */
+typedef void (*NMSettingValueIterFn) (NMSetting *setting,
+                                      const char *key,
+                                      const GValue *value,
+                                      GParamFlags flags,
+                                      gpointer user_data);
+
+/*< private >*/
+typedef gboolean (*_NMConnectionForEachSecretFunc) (NMSettingSecretFlags flags,
+                                                    gpointer user_data);
 
 typedef struct {
 	GObjectClass parent;
@@ -226,26 +246,31 @@ typedef struct {
 	                                   NMSetting *dst);
 
 	/*< private >*/
-	const struct _NMMetaSettingInfo *setting_info;
+	void (*enumerate_values) (const struct _NMSettInfoProperty *property_info,
+	                          NMSetting *setting,
+	                          NMSettingValueIterFn func,
+	                          gpointer user_data);
 
 	/*< private >*/
-	gpointer padding[5];
-} NMSettingClass;
+	gboolean (*aggregate) (NMSetting *setting,
+	                       int type_i,
+	                       gpointer arg);
 
-/**
- * NMSettingValueIterFn:
- * @setting: The setting for which properties are being iterated, given to
- * nm_setting_enumerate_values()
- * @key: The value/property name
- * @value: The property's value
- * @flags: The property's flags, like %NM_SETTING_PARAM_SECRET
- * @user_data: User data passed to nm_setting_enumerate_values()
- */
-typedef void (*NMSettingValueIterFn) (NMSetting *setting,
-                                      const char *key,
-                                      const GValue *value,
-                                      GParamFlags flags,
-                                      gpointer user_data);
+	/*< private >*/
+	void (*for_each_secret) (NMSetting *setting,
+	                         const char *secret_name,
+	                         GVariant *val,
+	                         gboolean remove_non_secrets,
+	                         _NMConnectionForEachSecretFunc callback,
+	                         gpointer callback_data,
+	                         GVariantBuilder *setting_builder);
+
+	/*< private >*/
+	gpointer padding[2];
+
+	/*< private >*/
+	const struct _NMMetaSettingInfo *setting_info;
+} NMSettingClass;
 
 GType nm_setting_get_type (void);
 
diff --git a/libnm-core/nm-utils-private.h b/libnm-core/nm-utils-private.h
index 58822cde..46af26ed 100644
--- a/libnm-core/nm-utils-private.h
+++ b/libnm-core/nm-utils-private.h
@@ -41,6 +41,18 @@ struct _NMVariantAttributeSpec {
 gboolean    _nm_utils_string_slist_validate (GSList *list,
                                              const char **valid_values);
 
+gboolean _nm_utils_secret_flags_validate (NMSettingSecretFlags secret_flags,
+                                          const char *setting_name,
+                                          const char *property_name,
+                                          NMSettingSecretFlags disallowed_flags,
+                                          GError **error);
+
+gboolean _nm_utils_wps_method_validate (NMSettingWirelessSecurityWpsMethod wps_method,
+                                        const char *setting_name,
+                                        const char *property_name,
+                                        gboolean wps_required,
+                                        GError **error);
+
 /* D-Bus transform funcs */
 
 GVariant   *_nm_utils_hwaddr_cloned_get (NMSetting     *setting,
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c
index 47099712..d276cfe6 100644
--- a/libnm-core/nm-utils.c
+++ b/libnm-core/nm-utils.c
@@ -23,8 +23,6 @@
 
 #include "nm-utils.h"
 
-#include <string.h>
-#include <errno.h>
 #include <stdlib.h>
 #include <netinet/ether.h>
 #include <arpa/inet.h>
@@ -40,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"
@@ -61,6 +61,277 @@
  * access points and devices, among other things.
  */
 
+/*****************************************************************************/
+
+struct _NMSockAddrEndpoint {
+	const char *host;
+	guint16 port;
+	guint refcount;
+	char endpoint[];
+};
+
+static gboolean
+NM_IS_SOCK_ADDR_ENDPOINT (const NMSockAddrEndpoint *self)
+{
+	return self && self->refcount > 0;
+}
+
+static const char *
+_parse_endpoint (char *str,
+                 guint16 *out_port)
+{
+	char *s;
+	const char *s_port;
+	gint16 port;
+
+	/* Like
+	 * - https://git.zx2c4.com/WireGuard/tree/src/tools/config.c?id=5e99a6d43fe2351adf36c786f5ea2086a8fe7ab8#n192
+	 * - https://github.com/systemd/systemd/blob/911649fdd43f3a9158b847947724a772a5a45c34/src/network/netdev/wireguard.c#L614
+	 */
+
+	g_strstrip (str);
+
+	if (!str[0])
+		return NULL;
+
+	if (str[0] == '[') {
+		str++;
+		s = strchr (str, ']');
+		if (!s)
+			return NULL;
+		if (s == str)
+			return NULL;
+		if (s[1] != ':')
+			return NULL;
+		if (!s[2])
+			return NULL;
+		*s = '\0';
+		s_port = &s[2];
+	} else {
+		s = strrchr (str, ':');
+		if (!s)
+			return NULL;
+		if (s == str)
+			return NULL;
+		if (!s[1])
+			return NULL;
+		*s = '\0';
+		s_port = &s[1];
+	}
+
+	if (!NM_STRCHAR_ALL (s_port, ch, (ch >= '0' && ch <= '9')))
+		return NULL;
+
+	port = _nm_utils_ascii_str_to_int64 (s_port, 10, 1, G_MAXUINT16, 0);
+	if (port == 0)
+		return NULL;
+
+	*out_port = port;
+	return str;
+}
+
+/**
+ * nm_sock_addr_endpoint_new:
+ * @endpoint: the endpoint string.
+ *
+ * This function cannot fail, even if the @endpoint is invalid.
+ * The reason is to allow NMSockAddrEndpoint also to be used
+ * for tracking invalid endpoints. Use nm_sock_addr_endpoint_get_host()
+ * to determine whether the endpoint is valid.
+ *
+ * Returns: (transfer full): the new #NMSockAddrEndpoint endpoint.
+ */
+NMSockAddrEndpoint *
+nm_sock_addr_endpoint_new (const char *endpoint)
+{
+	NMSockAddrEndpoint *ep;
+	gsize l_endpoint;
+	gsize l_host = 0;
+	gsize i;
+	gs_free char *host_clone = NULL;
+	const char *host;
+	guint16 port;
+
+	g_return_val_if_fail (endpoint, NULL);
+
+	l_endpoint = strlen (endpoint) + 1;
+
+	host = _parse_endpoint (nm_strndup_a (200, endpoint, l_endpoint - 1, &host_clone),
+	                        &port);
+
+	if (host)
+		l_host = strlen (host) + 1;
+
+	ep = g_malloc (sizeof (NMSockAddrEndpoint) + l_endpoint + l_host);
+	ep->refcount = 1;
+	memcpy (ep->endpoint, endpoint, l_endpoint);
+	if (host) {
+		i = l_endpoint;
+		memcpy (&ep->endpoint[i], host, l_host);
+		ep->host = &ep->endpoint[i];
+		ep->port = port;
+	} else {
+		ep->host = NULL;
+		ep->port = 0;
+	}
+	return ep;
+}
+
+/**
+ * nm_sock_addr_endpoint_ref:
+ * @self: (allow-none): the #NMSockAddrEndpoint
+ */
+NMSockAddrEndpoint *
+nm_sock_addr_endpoint_ref (NMSockAddrEndpoint *self)
+{
+	if (!self)
+		return NULL;
+
+	g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), NULL);
+
+	nm_assert (self->refcount < G_MAXUINT);
+
+	self->refcount++;
+	return self;
+}
+
+/**
+ * nm_sock_addr_endpoint_unref:
+ * @self: (allow-none): the #NMSockAddrEndpoint
+ */
+void
+nm_sock_addr_endpoint_unref (NMSockAddrEndpoint *self)
+{
+	if (!self)
+		return;
+
+	g_return_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self));
+
+	if (--self->refcount == 0)
+		g_free (self);
+}
+
+/**
+ * nm_sock_addr_endpoint_get_endpoint:
+ * @self: the #NMSockAddrEndpoint
+ *
+ * Gives the endpoint string. Since #NMSockAddrEndpoint's only
+ * information is the endpoint string, this can be used for comparing
+ * to instances for equality and order them lexically.
+ *
+ * Returns: (transfer none): the endpoint.
+ */
+const char *
+nm_sock_addr_endpoint_get_endpoint (NMSockAddrEndpoint *self)
+{
+	g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), NULL);
+
+	return self->endpoint;
+}
+
+/**
+ * nm_sock_addr_endpoint_get_host:
+ * @self: the #NMSockAddrEndpoint
+ *
+ * Returns: (transfer none): the parsed host part of the endpoint.
+ *   If the endpoint is invalid, %NULL will be returned.
+ */
+const char *
+nm_sock_addr_endpoint_get_host (NMSockAddrEndpoint *self)
+{
+	g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), NULL);
+
+	return self->host;
+}
+
+/**
+ * nm_sock_addr_endpoint_get_port:
+ * @self: the #NMSockAddrEndpoint
+ *
+ * Returns: the parsed port part of the endpoint (the service).
+ *   If the endpoint is invalid, -1 will be returned.
+ */
+gint32
+nm_sock_addr_endpoint_get_port (NMSockAddrEndpoint *self)
+{
+	g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), -1);
+
+	return self->host ? (int) self->port : -1;
+}
+
+gboolean
+nm_sock_addr_endpoint_get_fixed_sockaddr (NMSockAddrEndpoint *self,
+                                          gpointer sockaddr)
+{
+	int addr_family;
+	NMIPAddr addrbin;
+	const char *s;
+	guint scope_id = 0;
+
+	g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), FALSE);
+	g_return_val_if_fail (sockaddr, FALSE);
+
+	if (!self->host)
+		return FALSE;
+
+	if (nm_utils_parse_inaddr_bin (AF_UNSPEC, self->host, &addr_family, &addrbin))
+		goto good;
+
+	/* See if there is an IPv6 scope-id...
+	 *
+	 * Note that it does not make sense to persist connection profiles to disk,
+	 * that refenrence a scope-id (because the interface's ifindex changes on
+	 * reboot). However, we also support runtime only changes like `nmcli device modify`
+	 * where nothing is persisted to disk. At least in that case, passing a scope-id
+	 * might be reasonable. So, parse that too. */
+	s = strchr (self->host, '%');
+	if (!s)
+		return FALSE;
+
+	if (   s[1] == '\0'
+	    || !NM_STRCHAR_ALL (&s[1], ch, (ch >= '0' && ch <= '9')))
+		return FALSE;
+
+	scope_id = _nm_utils_ascii_str_to_int64 (&s[1], 10, 0, G_MAXINT32, G_MAXUINT);
+	if (scope_id == G_MAXUINT && errno)
+		return FALSE;
+
+	{
+		gs_free char *tmp_str = NULL;
+		const char *host_part;
+
+		host_part = nm_strndup_a (200, self->host, s - self->host, &tmp_str);
+		if (nm_utils_parse_inaddr_bin (AF_INET6, host_part, &addr_family, &addrbin))
+			goto good;
+	}
+
+	return FALSE;
+
+good:
+	switch (addr_family) {
+	case AF_INET:
+		*((struct sockaddr_in *) sockaddr) = (struct sockaddr_in) {
+			.sin_family = AF_INET,
+			.sin_addr   = addrbin.addr4_struct,
+			.sin_port   = htons (self->port),
+		};
+		return TRUE;
+	case AF_INET6:
+		*((struct sockaddr_in6 *) sockaddr) = (struct sockaddr_in6) {
+			.sin6_family   = AF_INET6,
+			.sin6_addr     = addrbin.addr6,
+			.sin6_port     = htons (self->port),
+			.sin6_scope_id = scope_id,
+			.sin6_flowinfo = 0,
+		};
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/*****************************************************************************/
+
 struct IsoLangToEncodings
 {
 	const char *lang;
@@ -2892,7 +3163,7 @@ nm_utils_uuid_generate_from_string_bin (NMUuid *uuid, const char *s, gssize slen
 		break;
 	case NM_UTILS_UUID_TYPE_VERSION3:
 	case NM_UTILS_UUID_TYPE_VERSION5: {
-		NMUuid ns_uuid = { 0 };
+		NMUuid ns_uuid = { };
 
 		if (type_args) {
 			/* type_args can be a name space UUID. Interpret it as (char *) */
@@ -3107,7 +3378,7 @@ _nm_utils_check_file (const char *filename,
 		g_set_error (error,
 		             NM_VPN_PLUGIN_ERROR,
 		             NM_VPN_PLUGIN_ERROR_FAILED,
-		             _("failed stat file %s: %s"), filename, strerror (errsv));
+		             _("failed stat file %s: %s"), filename, nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -3226,7 +3497,7 @@ _nm_utils_check_module_file (const char *name,
  * @predicate: (scope call): if given, pass the file name to this function
  *   for additional checks. This check is performed after the check for
  *   @file_test_flags. You cannot omit both @file_test_flags and @predicate.
- * @user_data: (closure): (allow-none): user data for @predicate function.
+ * @user_data: (closure) (allow-none): user data for @predicate function.
  * @error: (allow-none): on failure, set a "not found" error %G_IO_ERROR %G_IO_ERROR_NOT_FOUND.
  *
  * Searches for a @progname file in a list of search @paths.
@@ -3603,147 +3874,6 @@ nm_utils_hwaddr_len (int type)
 	g_return_val_if_reached (0);
 }
 
-guint8 *
-_nm_utils_hexstr2bin_full (const char *hexstr,
-                           gboolean allow_0x_prefix,
-                           gboolean delimiter_required,
-                           const char *delimiter_candidates,
-                           gsize required_len,
-                           guint8 *buffer,
-                           gsize buffer_len,
-                           gsize *out_len)
-{
-	const char *in = hexstr;
-	guint8 *out = buffer;
-	gboolean delimiter_has = TRUE;
-	guint8 delimiter = '\0';
-	gsize len;
-
-	nm_assert (hexstr);
-	nm_assert (buffer);
-	nm_assert (required_len > 0 || out_len);
-
-	if (   allow_0x_prefix
-	    && in[0] == '0'
-	    && in[1] == 'x')
-		in += 2;
-
-	while (TRUE) {
-		const guint8 d1 = in[0];
-		guint8 d2;
-		int i1, i2;
-
-		i1 = nm_utils_hexchar_to_int (d1);
-		if (i1 < 0)
-			goto fail;
-
-		/* If there's no leading zero (ie "aa:b:cc") then fake it */
-		d2 = in[1];
-		if (   d2
-		    && (i2 = nm_utils_hexchar_to_int (d2)) >= 0) {
-			*out++ = (i1 << 4) + i2;
-			d2 = in[2];
-			if (!d2)
-				break;
-			in += 2;
-		} else {
-			/* Fake leading zero */
-			*out++ = i1;
-			if (!d2) {
-				if (!delimiter_has) {
-					/* when using no delimiter, there must be pairs of hex chars */
-					goto fail;
-				}
-				break;
-			}
-			in += 1;
-		}
-
-		if (--buffer_len == 0)
-			goto fail;
-
-		if (delimiter_has) {
-			if (d2 != delimiter) {
-				if (delimiter)
-					goto fail;
-				if (delimiter_candidates) {
-					while (delimiter_candidates[0]) {
-						if (delimiter_candidates++[0] == d2)
-							delimiter = d2;
-					}
-				}
-				if (!delimiter) {
-					if (delimiter_required)
-						goto fail;
-					delimiter_has = FALSE;
-					continue;
-				}
-			}
-			in++;
-		}
-	}
-
-	len = out - buffer;
-	if (   required_len == 0
-	    || len == required_len) {
-		NM_SET_OUT (out_len, len);
-		return buffer;
-	}
-
-fail:
-	NM_SET_OUT (out_len, 0);
-	return NULL;
-}
-
-guint8 *
-_nm_utils_hexstr2bin_alloc (const char *hexstr,
-                            gboolean allow_0x_prefix,
-                            gboolean delimiter_required,
-                            const char *delimiter_candidates,
-                            gsize required_len,
-                            gsize *out_len)
-{
-	guint8 *buffer;
-	gsize buffer_len, len;
-
-	g_return_val_if_fail (hexstr, NULL);
-
-	nm_assert (required_len > 0 || out_len);
-
-	if (   allow_0x_prefix
-	    && hexstr[0] == '0'
-	    && hexstr[1] == 'x')
-		hexstr += 2;
-
-	if (!hexstr[0])
-		goto fail;
-
-	if (required_len > 0)
-		buffer_len = required_len;
-	else
-		buffer_len = strlen (hexstr) / 2 + 3;
-
-	buffer = g_malloc (buffer_len);
-
-	if (_nm_utils_hexstr2bin_full (hexstr,
-	                               FALSE,
-	                               delimiter_required,
-	                               delimiter_candidates,
-	                               required_len,
-	                               buffer,
-	                               buffer_len,
-	                               &len)) {
-		NM_SET_OUT (out_len, len);
-		return buffer;
-	}
-
-	g_free (buffer);
-
-fail:
-	NM_SET_OUT (out_len, 0);
-	return NULL;
-}
-
 /**
  * nm_utils_hexstr2bin:
  * @hex: a string of hexadecimal characters with optional ':' separators
@@ -3761,14 +3891,14 @@ nm_utils_hexstr2bin (const char *hex)
 	guint8 *buffer;
 	gsize len;
 
-	buffer = _nm_utils_hexstr2bin_alloc (hex, TRUE, FALSE, ":", 0, &len);
+	buffer = nm_utils_hexstr2bin_alloc (hex, TRUE, FALSE, ":", 0, &len);
 	if (!buffer)
 		return NULL;
 	buffer = g_realloc (buffer, len);
 	return g_bytes_new_take (buffer, len);
 }
 
-#define hwaddr_aton(asc, buffer, buffer_len, out_len) _nm_utils_hexstr2bin_full ((asc), FALSE, TRUE, ":-", 0, (buffer), (buffer_len), (out_len))
+#define hwaddr_aton(asc, buffer, buffer_len, out_len) nm_utils_hexstr2bin_full ((asc), FALSE, TRUE, ":-", 0, (buffer), (buffer_len), (out_len))
 
 /**
  * nm_utils_hwaddr_atoba:
@@ -3862,64 +3992,7 @@ nm_utils_hwaddr_aton (const char *asc, gpointer buffer, gsize length)
 	return buffer;
 }
 
-/**
- * _nm_utils_bin2hexstr_full:
- * @addr: pointer of @length bytes.
- * @length: number of bytes in @addr
- * @delimiter: either '\0', otherwise the output string will have the
- *   given delimiter character between each two hex numbers.
- * @upper_case: if TRUE, use upper case ASCII characters for hex.
- * @out: if %NULL, the function will allocate a new buffer of
- *   either (@length*2+1) or (@length*3) bytes, depending on whether
- *   a @delimiter is specified. In that case, the allocated buffer will
- *   be returned and must be freed by the caller.
- *   If not %NULL, the buffer must already be preallocated and contain
- *   at least (@length*2+1) or (@length*3) bytes, depending on the delimiter.
- *
- * Returns: the binary value converted to a hex string. If @out is given,
- *   this always returns @out. If @out is %NULL, a newly allocated string
- *   is returned.
- */
-char *
-_nm_utils_bin2hexstr_full (gconstpointer addr,
-                           gsize length,
-                           char delimiter,
-                           gboolean upper_case,
-                           char *out)
-{
-	const guint8 *in = addr;
-	const char *LOOKUP = upper_case ? "0123456789ABCDEF" : "0123456789abcdef";
-	char *out0;
 
-	nm_assert (addr);
-	nm_assert (length > 0);
-
-	if (out)
-		out0 = out;
-	else {
-		out0 = out = g_new (char, delimiter == '\0'
-		                          ? length * 2 + 1
-		                          : length * 3);
-	}
-
-	/* @out must contain at least @length*3 bytes if @delimiter is set,
-	 * otherwise, @length*2+1. */
-
-	for (;;) {
-		const guint8 v = *in++;
-
-		*out++ = LOOKUP[v >> 4];
-		*out++ = LOOKUP[v & 0x0F];
-		length--;
-		if (!length)
-			break;
-		if (delimiter)
-			*out++ = delimiter;
-	}
-
-	*out = 0;
-	return out0;
-}
 
 /**
  * nm_utils_bin2hexstr:
@@ -3945,7 +4018,7 @@ nm_utils_bin2hexstr (gconstpointer src, gsize len, int final_len)
 
 	result = g_malloc (buflen);
 
-	_nm_utils_bin2hexstr_full (src, len, '\0', FALSE, result);
+	nm_utils_bin2hexstr_full (src, len, '\0', FALSE, result);
 
 	/* Cut converted key off at the correct length for this cipher type */
 	if (final_len >= 0 && (gsize) final_len < buflen)
@@ -3969,7 +4042,7 @@ nm_utils_hwaddr_ntoa (gconstpointer addr, gsize length)
 	g_return_val_if_fail (addr, g_strdup (""));
 	g_return_val_if_fail (length > 0, g_strdup (""));
 
-	return _nm_utils_bin2hexstr_full (addr, length, ':', TRUE, NULL);
+	return nm_utils_bin2hexstr_full (addr, length, ':', TRUE, NULL);
 }
 
 const char *
@@ -3981,7 +4054,7 @@ nm_utils_hwaddr_ntoa_buf (gconstpointer addr, gsize addr_len, gboolean upper_cas
 	if (buf_len < addr_len * 3)
 		g_return_val_if_reached (NULL);
 
-	return _nm_utils_bin2hexstr_full (addr, addr_len, ':', upper_case, buf);
+	return nm_utils_bin2hexstr_full (addr, addr_len, ':', upper_case, buf);
 }
 
 /**
@@ -4311,6 +4384,102 @@ _nm_utils_hwaddr_from_dbus (GVariant *dbus_value,
 
 /*****************************************************************************/
 
+/* Validate secret-flags. Most settings don't validate them, which is a bug.
+ * But we possibly cannot enforce a strict validation now.
+ *
+ * For new settings, they shall validate the secret-flags strictly. */
+gboolean
+_nm_utils_secret_flags_validate (NMSettingSecretFlags secret_flags,
+                                 const char *setting_name,
+                                 const char *property_name,
+                                 NMSettingSecretFlags disallowed_flags,
+                                 GError **error)
+{
+	if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
+		return TRUE;
+
+	if (NM_FLAGS_ANY (secret_flags, ~NM_SETTING_SECRET_FLAG_ALL)) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("unknown secret flags"));
+		if (setting_name)
+			g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+		return FALSE;
+	}
+
+	if (!nm_utils_is_power_of_two (secret_flags)) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("conflicting secret flags"));
+		if (setting_name)
+			g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+		return FALSE;
+	}
+
+	if (NM_FLAGS_ANY (secret_flags, disallowed_flags)) {
+		if (NM_FLAGS_HAS (secret_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("secret flags must not be \"not-required\""));
+			if (setting_name)
+				g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+			return FALSE;
+		}
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("unsupported secret flags"));
+		if (setting_name)
+			g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean
+_nm_utils_wps_method_validate (NMSettingWirelessSecurityWpsMethod wps_method,
+                               const char *setting_name,
+                               const char *property_name,
+                               gboolean wps_required,
+                               GError **error)
+{
+	if (wps_method > NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("property is invalid"));
+		g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+		return FALSE;
+	}
+
+	if (NM_FLAGS_HAS (wps_method, NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DISABLED)) {
+		if (wps_method != NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DISABLED) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("can't be simultaneously disabled and enabled"));
+			g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+			return FALSE;
+		}
+		if (wps_required) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("WPS is required"));
+			g_prefix_error (error, "%s.%s: ", setting_name, property_name);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
 static char *
 _split_word (char *s)
 {
@@ -4666,7 +4835,7 @@ _nm_utils_dhcp_duid_valid (const char *duid, GBytes **out_duid_bin)
 		return TRUE;
 	}
 
-	if (_nm_utils_hexstr2bin_full (duid, FALSE, FALSE, ":", 0, duid_arr, sizeof (duid_arr), &duid_len)) {
+	if (nm_utils_hexstr2bin_full (duid, FALSE, FALSE, ":", 0, duid_arr, sizeof (duid_arr), &duid_len)) {
 		/* MAX DUID length is 128 octects + the type code (2 octects). */
 		if (   duid_len > 2
 		    && duid_len <= (128 + 2)) {
@@ -5316,7 +5485,7 @@ _json_find_object (json_t *json,
 	return json_element;
 }
 
-static inline void
+static void
 _json_delete_object_on_int_match (json_t *json,
                                   const char *key1,
                                   const char *key2,
@@ -5332,7 +5501,7 @@ _json_delete_object_on_int_match (json_t *json,
 		_json_del_object (json, key1, key2, key3);
 }
 
-static inline void
+static void
 _json_delete_object_on_bool_match (json_t *json,
                                    const char *key1,
                                    const char *key2,
@@ -5348,7 +5517,7 @@ _json_delete_object_on_bool_match (json_t *json,
 		_json_del_object (json, key1, key2, key3);
 }
 
-static inline void
+static void
 _json_delete_object_on_string_match (json_t *json,
                                      const char *key1,
                                      const char *key2,
@@ -5982,15 +6151,15 @@ _nm_utils_team_link_watchers_to_variant (GPtrArray *link_watchers)
 
 		name = nm_team_link_watcher_get_name (watcher);
 		g_variant_builder_add (&watcher_builder, "{sv}",
-				       "name",
-				       g_variant_new_string (name));
+		                       "name",
+		                       g_variant_new_string (name));
 
-		if nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL) {
+		if (nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL)) {
 			int_val = nm_team_link_watcher_get_delay_up (watcher);
 			if (int_val) {
 				g_variant_builder_add (&watcher_builder, "{sv}",
-						       "delay-up",
-						       g_variant_new_int32 (int_val));
+				                       "delay-up",
+				                       g_variant_new_int32 (int_val));
 			}
 			int_val = nm_team_link_watcher_get_delay_down (watcher);
 			if (int_val) {
@@ -6025,7 +6194,7 @@ _nm_utils_team_link_watchers_to_variant (GPtrArray *link_watchers)
 		                       "target-host",
 		                       g_variant_new_string (nm_team_link_watcher_get_target_host (watcher)));
 
-		if nm_streq (name, NM_TEAM_LINK_WATCHER_NSNA_PING) {
+		if (nm_streq (name, NM_TEAM_LINK_WATCHER_NSNA_PING)) {
 			g_variant_builder_add (&builder, "a{sv}", &watcher_builder);
 			continue;
 		}
@@ -6117,7 +6286,7 @@ _nm_utils_team_link_watchers_from_variant (GVariant *value)
 				val2 = 0;
 			if (!g_variant_lookup (watcher_var, "missed-max", "i", &val3))
 				val3 = 3;
-			if nm_streq (name, NM_TEAM_LINK_WATCHER_ARP_PING) {
+			if (nm_streq (name, NM_TEAM_LINK_WATCHER_ARP_PING)) {
 				if (!g_variant_lookup (watcher_var, "vlanid", "i", &val4))
 					val4 = -1;
 				if (!g_variant_lookup (watcher_var, "source-host", "&s", &source_host))
@@ -6501,3 +6670,73 @@ nm_utils_version (void)
 	return NM_VERSION;
 }
 
+/*****************************************************************************/
+
+/**
+ * nm_utils_base64secret_decode:
+ * @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): (out): 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.
+ *
+ * Since: 1.16
+ */
+gboolean
+nm_utils_base64secret_decode (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 (out_key)
+		memcpy (out_key, bin_arr, required_key_len);
+
+	nm_explicit_bzero (bin_arr, bin_len);
+	return TRUE;
+}
+
+gboolean
+nm_utils_base64secret_normalize (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_base64secret_decode (base64_key, required_key_len, buf)) {
+		NM_SET_OUT (out_base64_key_norm, NULL);
+		return FALSE;
+	}
+
+	NM_SET_OUT (out_base64_key_norm, g_base64_encode (buf, required_key_len));
+	nm_explicit_bzero (buf, required_key_len);
+	return TRUE;
+}
diff --git a/libnm-core/nm-utils.h b/libnm-core/nm-utils.h
index cf9572b9..2b5baba4 100644
--- a/libnm-core/nm-utils.h
+++ b/libnm-core/nm-utils.h
@@ -40,6 +40,8 @@
 
 G_BEGIN_DECLS
 
+/*****************************************************************************/
+
 typedef struct _NMVariantAttributeSpec NMVariantAttributeSpec;
 
 /* SSID helpers */
@@ -261,6 +263,10 @@ NMSriovVF *nm_utils_sriov_vf_from_str (const char *str, GError **error);
 NM_AVAILABLE_IN_1_12
 gint64 nm_utils_get_timestamp_msec         (void);
 
+NM_AVAILABLE_IN_1_16
+gboolean nm_utils_base64secret_decode (const char *base64_key,
+                                       gsize required_key_len,
+                                       guint8 *out_key);
 
 G_END_DECLS
 
diff --git a/libnm-core/nm-vpn-plugin-info.c b/libnm-core/nm-vpn-plugin-info.c
index 275a9dd5..bfcd70a5 100644
--- a/libnm-core/nm-vpn-plugin-info.c
+++ b/libnm-core/nm-vpn-plugin-info.c
@@ -22,8 +22,6 @@
 
 #include "nm-vpn-plugin-info.h"
 
-#include <string.h>
-#include <errno.h>
 #include <sys/stat.h>
 
 #include "nm-errors.h"
@@ -148,7 +146,7 @@ nm_vpn_plugin_info_check_file_full (const char *filename,
  *   other users.
  * @check_file: pass a callback to do your own validation.
  * @user_data: user data for @check_file.
- * @error: (allow-none): (out): the error reason if the check fails.
+ * @error: (allow-none) (out): the error reason if the check fails.
  *
  * Check whether the file exists and is a valid name file (in keyfile format).
  * Additionally, also check for file permissions.
diff --git a/libnm-core/tests/meson.build b/libnm-core/tests/meson.build
index b9411074..7d772c80 100644
--- a/libnm-core/tests/meson.build
+++ b/libnm-core/tests/meson.build
@@ -47,5 +47,6 @@ foreach test_unit: test_units
     'libnm-core/' + test_unit,
     test_script,
     args: test_args + [exe.full_path()],
+    timeout: default_test_timeout,
   )
 endforeach
diff --git a/libnm-core/tests/test-crypto.c b/libnm-core/tests/test-crypto.c
index 6b63e6fd..4ac9679b 100644
--- a/libnm-core/tests/test-crypto.c
+++ b/libnm-core/tests/test-crypto.c
@@ -26,7 +26,6 @@
 #include <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
-#include <string.h>
 
 #include "nm-crypto-impl.h"
 #include "nm-utils.h"
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index 0806e7b9..7d97296a 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -23,8 +23,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "nm-utils/c-list-util.h"
 #include "nm-utils/nm-enum-utils.h"
 
@@ -5524,6 +5522,158 @@ test_setting_user_data (void)
 
 /*****************************************************************************/
 
+typedef union {
+	struct sockaddr     sa;
+	struct sockaddr_in  in;
+	struct sockaddr_in6 in6;
+} SockAddrUnion;
+
+static void
+_sock_addr_endpoint (const char *endpoint,
+                     const char *host,
+                     gint32 port)
+{
+	nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL;
+	const char *s_endpoint;
+	const char *s_host;
+	gint32 s_port;
+	SockAddrUnion sockaddr = { };
+
+	g_assert (endpoint);
+	g_assert (!host == (port == -1));
+	g_assert (port >= -1 && port <= G_MAXUINT16);
+
+	ep = nm_sock_addr_endpoint_new (endpoint);
+	g_assert (ep);
+
+	s_endpoint = nm_sock_addr_endpoint_get_endpoint (ep);
+	s_host = nm_sock_addr_endpoint_get_host (ep);
+	s_port = nm_sock_addr_endpoint_get_port (ep);
+	g_assert_cmpstr (endpoint, ==, s_endpoint);
+	g_assert_cmpstr (host,     ==, s_host);
+	g_assert_cmpint (port,     ==, s_port);
+
+	g_assert (!nm_sock_addr_endpoint_get_fixed_sockaddr (ep, &sockaddr));
+
+	if (endpoint[0] != ' ') {
+		gs_free char *endpoint2 = NULL;
+
+		/* also test with a leading space */
+		endpoint2 = g_strdup_printf (" %s", endpoint);
+		_sock_addr_endpoint (endpoint2, host, port);
+	}
+
+	if (endpoint[0] && endpoint[strlen (endpoint) - 1] != ' ') {
+		gs_free char *endpoint2 = NULL;
+
+		/* also test with a trailing space */
+		endpoint2 = g_strdup_printf ("%s ", endpoint);
+		_sock_addr_endpoint (endpoint2, host, port);
+	}
+}
+
+static void
+_sock_addr_endpoint_fixed (const char *endpoint,
+                           const char *host,
+                           guint16 port,
+                           guint scope_id)
+{
+	nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL;
+	const char *s_endpoint;
+	const char *s_host;
+	gint32 s_port;
+	int addr_family;
+	NMIPAddr addrbin;
+	SockAddrUnion sockaddr = { };
+
+	g_assert (endpoint);
+	g_assert (host);
+	g_assert (port > 0);
+
+	if (!nm_utils_parse_inaddr_bin (AF_UNSPEC, host, &addr_family, &addrbin))
+		g_assert_not_reached ();
+
+	ep = nm_sock_addr_endpoint_new (endpoint);
+	g_assert (ep);
+
+	s_endpoint = nm_sock_addr_endpoint_get_endpoint (ep);
+	s_host = nm_sock_addr_endpoint_get_host (ep);
+	s_port = nm_sock_addr_endpoint_get_port (ep);
+	g_assert_cmpstr (endpoint, ==, s_endpoint);
+	g_assert_cmpstr (NULL,     !=, s_host);
+	g_assert_cmpint (port,     ==, s_port);
+
+	if (!nm_sock_addr_endpoint_get_fixed_sockaddr (ep, &sockaddr))
+		g_assert_not_reached ();
+
+	g_assert_cmpint (sockaddr.sa.sa_family, ==, addr_family);
+	if (addr_family == AF_INET) {
+		const SockAddrUnion s = {
+			.in = {
+				.sin_family = AF_INET,
+				.sin_addr   = addrbin.addr4_struct,
+				.sin_port   = htons (port),
+			},
+		};
+
+		g_assert_cmpint (sockaddr.in.sin_addr.s_addr, ==, addrbin.addr4);
+		g_assert_cmpint (sockaddr.in.sin_port, ==, htons (port));
+		g_assert (memcmp (&s, &sockaddr, sizeof (s.in)) == 0);
+	} else if (addr_family == AF_INET6) {
+		const SockAddrUnion s = {
+			.in6 = {
+				.sin6_family   = AF_INET6,
+				.sin6_addr     = addrbin.addr6,
+				.sin6_scope_id = scope_id,
+				.sin6_port     = htons (port),
+			},
+		};
+
+		g_assert (memcmp (&sockaddr.in6.sin6_addr, &addrbin, sizeof (addrbin.addr6)) == 0);
+		g_assert_cmpint (sockaddr.in6.sin6_port, ==, htons (port));
+		g_assert_cmpint (sockaddr.in6.sin6_scope_id, ==, scope_id);
+		g_assert_cmpint (sockaddr.in6.sin6_flowinfo, ==, 0);
+		g_assert (memcmp (&s, &sockaddr, sizeof (s.in6)) == 0);
+	} else
+		g_assert_not_reached ();
+}
+
+static void
+test_sock_addr_endpoint (void)
+{
+	_sock_addr_endpoint ("",                NULL, -1);
+	_sock_addr_endpoint (":",               NULL, -1);
+	_sock_addr_endpoint ("a",               NULL, -1);
+	_sock_addr_endpoint ("a:",              NULL, -1);
+	_sock_addr_endpoint (":a",              NULL, -1);
+	_sock_addr_endpoint ("[]:a",            NULL, -1);
+	_sock_addr_endpoint ("[]a",             NULL, -1);
+	_sock_addr_endpoint ("[]:",             NULL, -1);
+	_sock_addr_endpoint ("[a]b",            NULL, -1);
+	_sock_addr_endpoint ("[a:b",            NULL, -1);
+	_sock_addr_endpoint ("[a[:b",           NULL, -1);
+	_sock_addr_endpoint ("a:6",             "a",  6);
+	_sock_addr_endpoint ("a:6",             "a",  6);
+	_sock_addr_endpoint ("[a]:6",           "a",  6);
+	_sock_addr_endpoint ("[a]:6",           "a",  6);
+	_sock_addr_endpoint ("[a]:655",         "a",  655);
+	_sock_addr_endpoint ("[ab]:][6",        NULL, -1);
+	_sock_addr_endpoint ("[ab]:]:[6",       NULL, -1);
+	_sock_addr_endpoint ("[a[]:b",          NULL, -1);
+	_sock_addr_endpoint ("[192.169.6.x]:6", "192.169.6.x", 6);
+	_sock_addr_endpoint ("[192.169.6.x]:0", NULL, -1);
+	_sock_addr_endpoint ("192.169.6.7:0",   NULL, -1);
+
+	_sock_addr_endpoint_fixed ("192.169.6.7:6",   "192.169.6.7", 6, 0);
+	_sock_addr_endpoint_fixed ("[192.169.6.7]:6", "192.169.6.7", 6, 0);
+	_sock_addr_endpoint_fixed ("[a:b::]:6", "a:b::", 6, 0);
+	_sock_addr_endpoint_fixed ("[a:b::%7]:6", "a:b::", 6, 7);
+	_sock_addr_endpoint_fixed ("a:b::1%75:6", "a:b::1", 6, 75);
+	_sock_addr_endpoint_fixed ("a:b::1%0:64", "a:b::1", 64, 0);
+}
+
+/*****************************************************************************/
+
 static void
 test_hexstr2bin (void)
 {
@@ -5889,7 +6039,7 @@ __test_uuid (const char *expected_uuid, const char *str, gssize slen, char *uuid
 static void
 test_nm_utils_uuid_generate_from_strings (void)
 {
-	const NMUuid uuid0 = { 0 };
+	const NMUuid uuid0 = { };
 
 	g_assert_cmpmem (&uuid0, sizeof (uuid0), _uuid ("00000000-0000-0000-0000-000000000000"), 16);
 
@@ -7737,6 +7887,8 @@ int main (int argc, char **argv)
 	g_test_add_func ("/core/general/test_setting_compare_default_strv", test_setting_compare_default_strv);
 	g_test_add_func ("/core/general/test_setting_user_data", test_setting_user_data);
 
+	g_test_add_func ("/core/general/test_sock_addr_endpoint", test_sock_addr_endpoint);
+
 	g_test_add_func ("/core/general/hexstr2bin", test_hexstr2bin);
 	g_test_add_func ("/core/general/nm_strquote", test_nm_strquote);
 	g_test_add_func ("/core/general/test_nm_utils_uuid_generate_from_string", test_nm_utils_uuid_generate_from_string);
diff --git a/libnm-core/tests/test-secrets.c b/libnm-core/tests/test-secrets.c
index d31c43d2..5debe5cf 100644
--- a/libnm-core/tests/test-secrets.c
+++ b/libnm-core/tests/test-secrets.c
@@ -21,8 +21,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "nm-setting-8021x.h"
 #include "nm-setting-cdma.h"
 #include "nm-setting-connection.h"
diff --git a/libnm-core/tests/test-setting.c b/libnm-core/tests/test-setting.c
index d8784d31..2011273a 100644
--- a/libnm-core/tests/test-setting.c
+++ b/libnm-core/tests/test-setting.c
@@ -20,7 +20,6 @@
 #include "nm-default.h"
 
 #include <linux/pkt_sched.h>
-#include <string.h>
 
 #include "nm-utils.h"
 #include "nm-utils-private.h"
@@ -116,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,
@@ -2014,6 +2037,239 @@ 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 ();
+		if (!nm_wireguard_peer_set_public_key (peer, public_key, TRUE))
+			g_assert_not_reached ();
+
+		if (!nm_wireguard_peer_set_preshared_key (peer, nmtst_rand_select (NULL, preshared_key), TRUE))
+			g_assert_not_reached ();
+
+		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 ()));
+
+		if (!nm_wireguard_peer_set_endpoint (peer, nmtst_rand_select (s_endpoint, NULL), TRUE))
+			g_assert_not_reached ();
+
+		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);
+			if (!nm_wireguard_peer_set_preshared_key (b_clone, nm_wireguard_peer_get_preshared_key (a), TRUE))
+				g_assert_not_reached ();
+			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)
 {
@@ -2023,7 +2279,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,
@@ -2032,9 +2299,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:
@@ -2111,6 +2381,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 ();
 	}
@@ -2132,6 +2512,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");
@@ -2160,6 +2541,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 ());
@@ -2169,6 +2579,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));
@@ -2187,6 +2598,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;
+		}
 	}
 }
 
@@ -2269,6 +2695,8 @@ main (int argc, char **argv)
 #endif
 
 	g_test_add_data_func ("/libnm/settings/roundtrip-conversion/general/0", GINT_TO_POINTER (0), test_roundtrip_conversion);
+	g_test_add_data_func ("/libnm/settings/roundtrip-conversion/wireguard/1", GINT_TO_POINTER (1), test_roundtrip_conversion);
+	g_test_add_data_func ("/libnm/settings/roundtrip-conversion/wireguard/2", GINT_TO_POINTER (2), test_roundtrip_conversion);
 
 	return g_test_run ();
 }
diff --git a/libnm-core/tests/test-settings-defaults.c b/libnm-core/tests/test-settings-defaults.c
index 95570b03..9e640543 100644
--- a/libnm-core/tests/test-settings-defaults.c
+++ b/libnm-core/tests/test-settings-defaults.c
@@ -21,8 +21,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "nm-utils.h"
 #include "nm-setting-8021x.h"
 #include "nm-setting-cdma.h"