summary refs log tree commit diff
path: root/libnm-core
diff options
context:
space:
mode:
Diffstat (limited to 'libnm-core')
-rw-r--r--libnm-core/meson.build26
-rw-r--r--libnm-core/nm-connection-private.h3
-rw-r--r--libnm-core/nm-connection.c146
-rw-r--r--libnm-core/nm-core-internal.h64
-rw-r--r--libnm-core/nm-core-types.h1
-rw-r--r--libnm-core/nm-dbus-interface.h89
-rw-r--r--libnm-core/nm-dbus-types.xml162
-rw-r--r--libnm-core/nm-dbus-utils.c46
-rw-r--r--libnm-core/nm-errors.h2
-rw-r--r--libnm-core/nm-keyfile-internal.h180
-rw-r--r--libnm-core/nm-keyfile-utils.c636
-rw-r--r--libnm-core/nm-keyfile-utils.h85
-rw-r--r--libnm-core/nm-keyfile.c4089
-rw-r--r--libnm-core/nm-setting-6lowpan.c1
-rw-r--r--libnm-core/nm-setting-8021x.c129
-rw-r--r--libnm-core/nm-setting-8021x.h8
-rw-r--r--libnm-core/nm-setting-adsl.c16
-rw-r--r--libnm-core/nm-setting-bluetooth.c2
-rw-r--r--libnm-core/nm-setting-bond.c809
-rw-r--r--libnm-core/nm-setting-bond.h4
-rw-r--r--libnm-core/nm-setting-bridge-port.c11
-rw-r--r--libnm-core/nm-setting-bridge.c416
-rw-r--r--libnm-core/nm-setting-bridge.h64
-rw-r--r--libnm-core/nm-setting-cdma.c3
-rw-r--r--libnm-core/nm-setting-connection.c134
-rw-r--r--libnm-core/nm-setting-dcb.c34
-rw-r--r--libnm-core/nm-setting-gsm.c17
-rw-r--r--libnm-core/nm-setting-infiniband.c68
-rw-r--r--libnm-core/nm-setting-ip-config.c98
-rw-r--r--libnm-core/nm-setting-ip-tunnel.c23
-rw-r--r--libnm-core/nm-setting-ip6-config.c12
-rw-r--r--libnm-core/nm-setting-ip6-config.h2
-rw-r--r--libnm-core/nm-setting-macsec.c25
-rw-r--r--libnm-core/nm-setting-macvlan.c13
-rw-r--r--libnm-core/nm-setting-match.c2
-rw-r--r--libnm-core/nm-setting-olpc-mesh.c5
-rw-r--r--libnm-core/nm-setting-ovs-bridge.c10
-rw-r--r--libnm-core/nm-setting-ovs-dpdk.c1
-rw-r--r--libnm-core/nm-setting-ovs-interface.c70
-rw-r--r--libnm-core/nm-setting-ovs-patch.c5
-rw-r--r--libnm-core/nm-setting-ovs-port.c8
-rw-r--r--libnm-core/nm-setting-ppp.c50
-rw-r--r--libnm-core/nm-setting-pppoe.c1
-rw-r--r--libnm-core/nm-setting-proxy.c15
-rw-r--r--libnm-core/nm-setting-serial.c17
-rw-r--r--libnm-core/nm-setting-sriov.c13
-rw-r--r--libnm-core/nm-setting-tc-config.c2
-rw-r--r--libnm-core/nm-setting-tun.c14
-rw-r--r--libnm-core/nm-setting-user.c8
-rw-r--r--libnm-core/nm-setting-vlan.c12
-rw-r--r--libnm-core/nm-setting-vpn.c373
-rw-r--r--libnm-core/nm-setting-vrf.c168
-rw-r--r--libnm-core/nm-setting-vrf.h36
-rw-r--r--libnm-core/nm-setting-vxlan.c41
-rw-r--r--libnm-core/nm-setting-wifi-p2p.c2
-rw-r--r--libnm-core/nm-setting-wimax.c2
-rw-r--r--libnm-core/nm-setting-wired.c30
-rw-r--r--libnm-core/nm-setting-wireless-security.c82
-rw-r--r--libnm-core/nm-setting-wireless.c34
-rw-r--r--libnm-core/nm-setting.c15
-rw-r--r--libnm-core/nm-utils-private.h4
-rw-r--r--libnm-core/nm-utils.c313
-rw-r--r--libnm-core/nm-utils.h2
-rw-r--r--libnm-core/nm-version.h31
-rw-r--r--libnm-core/nm-vpn-plugin-info.c138
-rw-r--r--libnm-core/nm-vpn-plugin-info.h19
-rw-r--r--libnm-core/tests/meson.build3
-rw-r--r--libnm-core/tests/test-general.c430
-rw-r--r--libnm-core/tests/test-keyfile.c6
-rw-r--r--libnm-core/tests/test-setting.c175
70 files changed, 3012 insertions, 6543 deletions
diff --git a/libnm-core/meson.build b/libnm-core/meson.build
index 469d4161..955e1776 100644
--- a/libnm-core/meson.build
+++ b/libnm-core/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 libnm_core_inc = include_directories('.')
 
 libnm_core_nm_default_dep = declare_dependency(
@@ -56,6 +58,7 @@ libnm_core_headers = files(
   'nm-setting-user.h',
   'nm-setting-vlan.h',
   'nm-setting-vpn.h',
+  'nm-setting-vrf.h',
   'nm-setting-vxlan.h',
   'nm-setting-wifi-p2p.h',
   'nm-setting-wimax.h',
@@ -85,7 +88,7 @@ libnm_core_enum_sources = gnome.mkenums_simple(
 
 libnm_libnm_core_intern = static_library(
   'nm-libnm-core-intern',
-  sources: nm_ethtool_utils_source + nm_libnm_core_utils_source + [libnm_core_enum_sources[1]],
+  sources: nm_ethtool_utils_source + nm_libnm_core_utils_source + nm_auth_subject_source + [libnm_core_enum_sources[1]],
   dependencies: libnm_core_nm_default_dep,
   c_args: common_c_flags,
 )
@@ -150,6 +153,7 @@ libnm_core_settings_sources = files(
   'nm-setting-user.c',
   'nm-setting-vlan.c',
   'nm-setting-vpn.c',
+  'nm-setting-vrf.c',
   'nm-setting-vxlan.c',
   'nm-setting-wifi-p2p.c',
   'nm-setting-wimax.c',
@@ -165,8 +169,6 @@ libnm_core_sources = libnm_core_settings_sources + files(
   'nm-crypto.c',
   'nm-dbus-utils.c',
   'nm-errors.c',
-  'nm-keyfile-utils.c',
-  'nm-keyfile.c',
   'nm-property-compare.c',
   'nm-setting.c',
   'nm-simple-connection.c',
@@ -232,6 +234,24 @@ libnm_libnm_core_aux_dep = declare_dependency(
   link_with: libnm_libnm_core_aux,
 )
 
+libnm_keyfile = static_library(
+  'nm-keyfile',
+  sources: nm_keyfile_source + [libnm_core_enum_sources[1]],
+  dependencies: libnm_utils_base_dep,
+  c_args: [
+    '-DG_LOG_DOMAIN="@0@"'.format(libnm_name),
+    '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)',
+  ],
+  link_with: libnm_libnm_core_intern,
+)
+
+libnm_keyfile_dep = declare_dependency(
+  sources: [libnm_core_enum_sources[1], nm_version_macro_header],
+  include_directories: [libnm_core_inc, shared_inc],
+  dependencies: glib_dep,
+  link_with: libnm_keyfile,
+)
+
 enums_to_docbook = join_paths(source_root, 'tools', 'enums-to-docbook.pl')
 
 docbooks = [
diff --git a/libnm-core/nm-connection-private.h b/libnm-core/nm-connection-private.h
index 034c350f..3c505834 100644
--- a/libnm-core/nm-connection-private.h
+++ b/libnm-core/nm-connection-private.h
@@ -24,10 +24,11 @@ gboolean    _nm_connection_verify_required_interface_name (NMConnection *connect
                                                            GError **error);
 
 int _nm_setting_ovs_interface_verify_interface_type (NMSettingOvsInterface *self,
+                                                     const char *type,
                                                      NMConnection *connection,
                                                      gboolean normalize,
                                                      gboolean *out_modified,
-                                                     const char **normalized_type,
+                                                     const char **out_normalized_type,
                                                      GError **error);
 
 #endif  /* __NM_CONNECTION_PRIVATE_H__ */
diff --git a/libnm-core/nm-connection.c b/libnm-core/nm-connection.c
index 4146fd55..5f18b651 100644
--- a/libnm-core/nm-connection.c
+++ b/libnm-core/nm-connection.c
@@ -887,6 +887,7 @@ static gboolean
 _supports_addr_family (NMConnection *self, int family)
 {
 	const char *connection_type = nm_connection_get_connection_type (self);
+	NMSettingConnection *s_con;
 
 	g_return_val_if_fail (connection_type, TRUE);
 	if (strcmp (connection_type, NM_SETTING_OVS_INTERFACE_SETTING_NAME) == 0)
@@ -895,6 +896,9 @@ _supports_addr_family (NMConnection *self, int family)
 		return FALSE;
 	if (strcmp (connection_type, NM_SETTING_6LOWPAN_SETTING_NAME) == 0)
 		return family == AF_INET6 || family == AF_UNSPEC;
+	if (   (s_con = nm_connection_get_setting_connection (self))
+	    && (nm_streq0 (nm_setting_connection_get_slave_type (s_con), NM_SETTING_VRF_SETTING_NAME)))
+		return TRUE;
 
 	return !nm_setting_connection_get_master (nm_connection_get_setting_connection (self));
 }
@@ -995,7 +999,7 @@ _normalize_ip_config (NMConnection *self, GHashTable *parameters)
 
 				if (   inet_pton (AF_INET6, token, &i6_token) == 1
 				    && _nm_utils_inet6_is_token (&i6_token)) {
-					nm_utils_inet6_ntop (&i6_token, normalized);
+					_nm_utils_inet6_ntop (&i6_token, normalized);
 					if (g_strcmp0 (token, normalized)) {
 						g_object_set (s_ip6, NM_SETTING_IP6_CONFIG_TOKEN, normalized, NULL);
 						changed = TRUE;
@@ -1232,6 +1236,7 @@ _normalize_ovs_interface_type (NMConnection *self)
 		return FALSE;
 
 	v = _nm_setting_ovs_interface_verify_interface_type (s_ovs_interface,
+	                                                     nm_setting_ovs_interface_get_interface_type (s_ovs_interface),
 	                                                     self,
 	                                                     TRUE,
 	                                                     &modified,
@@ -1400,24 +1405,19 @@ nm_connection_verify (NMConnection *connection, GError **error)
 NMSettingVerifyResult
 _nm_connection_verify (NMConnection *connection, GError **error)
 {
-	NMConnectionPrivate *priv;
-	NMSettingConnection *s_con;
 	NMSettingIPConfig *s_ip4, *s_ip6;
 	NMSettingProxy *s_proxy;
-	GHashTableIter iter;
-	gpointer value;
-	GSList *all_settings = NULL, *setting_i;
+	gs_free NMSetting **settings = NULL;
 	gs_free_error GError *normalizable_error = NULL;
 	NMSettingVerifyResult normalizable_error_type = NM_SETTING_VERIFY_SUCCESS;
+	guint i;
 
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), NM_SETTING_VERIFY_ERROR);
 	g_return_val_if_fail (!error || !*error, NM_SETTING_VERIFY_ERROR);
 
-	priv = NM_CONNECTION_GET_PRIVATE (connection);
-
-	/* First, make sure there's at least 'connection' setting */
-	s_con = nm_connection_get_setting_connection (connection);
-	if (!s_con) {
+	settings = nm_connection_get_settings (connection, NULL);
+	if (   !settings
+	    || !NM_IS_SETTING_CONNECTION (settings[0])) {
 		g_set_error_literal (error,
 		                     NM_CONNECTION_ERROR,
 		                     NM_CONNECTION_ERROR_MISSING_SETTING,
@@ -1426,25 +1426,13 @@ _nm_connection_verify (NMConnection *connection, GError **error)
 		return NM_SETTING_VERIFY_ERROR;
 	}
 
-	/* Build up the list of settings */
-	g_hash_table_iter_init (&iter, priv->settings);
-	while (g_hash_table_iter_next (&iter, NULL, &value)) {
-		/* Order NMSettingConnection so that it will be verified first.
-		 * The reason is, that errors in this setting might be more fundamental
-		 * and should be checked and reported with higher priority.
-		 */
-		if (value == s_con)
-			all_settings = g_slist_append (all_settings, value);
-		else
-			all_settings = g_slist_prepend (all_settings, value);
-	}
-	all_settings = g_slist_reverse (all_settings);
-
-	/* Now, run the verify function of each setting */
-	for (setting_i = all_settings; setting_i; setting_i = setting_i->next) {
+	for (i = 0; settings[i]; i++) {
 		GError *verify_error = NULL;
 		NMSettingVerifyResult verify_result;
 
+		nm_assert (NM_IS_SETTING (settings[i]));
+		nm_assert (NM_IS_SETTING_CONNECTION (settings[i]) == (i == 0));
+
 		/* verify all settings. We stop if we find the first non-normalizable
 		 * @NM_SETTING_VERIFY_ERROR. If we find normalizable errors we continue
 		 * but remember the error to return it to the user.
@@ -1452,7 +1440,7 @@ _nm_connection_verify (NMConnection *connection, GError **error)
 		 * @NM_SETTING_VERIFY_NORMALIZABLE, so, if we encounter such an error type,
 		 * we remember it instead (to return it as output).
 		 **/
-		verify_result = _nm_setting_verify (NM_SETTING (setting_i->data), connection, &verify_error);
+		verify_result = _nm_setting_verify (settings[i], connection, &verify_error);
 		if (verify_result == NM_SETTING_VERIFY_NORMALIZABLE ||
 		    verify_result == NM_SETTING_VERIFY_NORMALIZABLE_ERROR) {
 			if (   verify_result == NM_SETTING_VERIFY_NORMALIZABLE_ERROR
@@ -1467,13 +1455,11 @@ _nm_connection_verify (NMConnection *connection, GError **error)
 			}
 		} else if (verify_result != NM_SETTING_VERIFY_SUCCESS) {
 			g_propagate_error (error, verify_error);
-			g_slist_free (all_settings);
 			g_return_val_if_fail (verify_result == NM_SETTING_VERIFY_ERROR, NM_SETTING_VERIFY_ERROR);
 			return NM_SETTING_VERIFY_ERROR;
 		}
 		g_clear_error (&verify_error);
 	}
-	g_slist_free (all_settings);
 
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
@@ -1928,7 +1914,7 @@ nm_connection_update_secrets (NMConnection *connection,
 		                                             error);
 		g_signal_handlers_unblock_by_func (setting, (GCallback) setting_changed_cb, connection);
 
-		g_clear_pointer (&setting_dict, g_variant_unref);
+		nm_clear_pointer (&setting_dict, g_variant_unref);
 
 		if (success_detail == NM_SETTING_UPDATE_SECRET_ERROR) {
 			nm_assert (!error || *error);
@@ -2262,39 +2248,33 @@ nm_connection_to_dbus_full (NMConnection *connection,
                             NMConnectionSerializationFlags flags,
                             const NMConnectionSerializationOptions *options)
 {
-	NMConnectionPrivate *priv;
 	GVariantBuilder builder;
-	GHashTableIter iter;
-	gpointer data;
-	GVariant *setting_dict, *ret;
+	gboolean any = FALSE;
+	gs_free NMSetting **settings = NULL;
+	guint settings_len;
+	guint i;
 
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
-	priv = NM_CONNECTION_GET_PRIVATE (connection);
 
-	g_variant_builder_init (&builder, NM_VARIANT_TYPE_CONNECTION);
-
-	/* Add each setting's hash to the main hash */
-
-	/* FIXME: the order of serialized settings must be stable. */
-
-	g_hash_table_iter_init (&iter, priv->settings);
-	while (g_hash_table_iter_next (&iter, NULL, &data)) {
-		NMSetting *setting = NM_SETTING (data);
+	settings = nm_connection_get_settings (connection, &settings_len);
+	for (i = 0; i < settings_len; i++) {
+		NMSetting *setting = settings[i];
+		GVariant *setting_dict;
 
 		setting_dict = _nm_setting_to_dbus (setting, connection, flags, options);
-		if (setting_dict)
-			g_variant_builder_add (&builder, "{s@a{sv}}", nm_setting_get_name (setting), setting_dict);
+		if (!setting_dict)
+			continue;
+		if (!any) {
+			any = TRUE;
+			g_variant_builder_init (&builder, NM_VARIANT_TYPE_CONNECTION);
+		}
+		g_variant_builder_add (&builder, "{s@a{sv}}", nm_setting_get_name (setting), setting_dict);
 	}
 
-	ret = g_variant_builder_end (&builder);
-
-	/* Don't send empty hashes */
-	if (g_variant_n_children (ret) == 0) {
-		g_variant_unref (ret);
-		ret = NULL;
-	}
+	if (!any)
+		return NULL;
 
-	return ret;
+	return g_variant_builder_end (&builder);
 }
 
 /**
@@ -2318,16 +2298,21 @@ nm_connection_is_type (NMConnection *connection, const char *type)
 }
 
 static int
-_for_each_sort (NMSetting **p_a, NMSetting **p_b, void *unused)
+_get_settings_sort (gconstpointer p_a, gconstpointer p_b, gpointer unused)
 {
-	NMSetting *a = *p_a;
-	NMSetting *b = *p_b;
-	int c;
+	NMSetting *a = *((NMSetting **) p_a);
+	NMSetting *b = *((NMSetting **) p_b);
+
+	nm_assert (NM_IS_SETTING (a));
+	nm_assert (NM_IS_SETTING (b));
+	nm_assert (a != b);
+	nm_assert (G_OBJECT_TYPE (a) != G_OBJECT_TYPE (b));
+
+	NM_CMP_RETURN (_nm_setting_compare_priority (a, b));
+	NM_CMP_DIRECT_STRCMP (nm_setting_get_name (a), nm_setting_get_name (b));
 
-	c = _nm_setting_compare_priority (a, b);
-	if (c != 0)
-		return c;
-	return strcmp (nm_setting_get_name (a), nm_setting_get_name (b));
+	nm_assert_not_reached ();
+	return 0;
 }
 
 /**
@@ -2350,38 +2335,12 @@ NMSetting **
 nm_connection_get_settings (NMConnection *connection,
                             guint *out_length)
 {
-	NMConnectionPrivate *priv;
-	NMSetting **arr;
-	GHashTableIter iter;
-	NMSetting *setting;
-	guint i, size;
-
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
 
-	priv = NM_CONNECTION_GET_PRIVATE (connection);
-
-	size = g_hash_table_size (priv->settings);
-
-	if (!size) {
-		NM_SET_OUT (out_length, 0);
-		return NULL;
-	}
-
-	arr = g_new (NMSetting *, size + 1);
-
-	g_hash_table_iter_init (&iter, priv->settings);
-	for (i = 0; g_hash_table_iter_next (&iter, NULL, (gpointer *) &setting); i++)
-		arr[i] = setting;
-	nm_assert (i == size);
-	arr[size] = NULL;
-
-	/* sort the settings. This has an effect on the order in which keyfile
-	 * prints them. */
-	if (size > 1)
-		g_qsort_with_data (arr, size, sizeof (NMSetting *), (GCompareDataFunc) _for_each_sort, NULL);
-
-	NM_SET_OUT (out_length, size);
-	return arr;
+	return (NMSetting **) nm_utils_hash_values_to_array (NM_CONNECTION_GET_PRIVATE (connection)->settings,
+	                                                     _get_settings_sort,
+	                                                     NULL,
+	                                                     out_length);
 }
 
 /**
@@ -2694,6 +2653,7 @@ nm_connection_is_virtual (NMConnection *connection)
 	                        NM_SETTING_TEAM_SETTING_NAME,
 	                        NM_SETTING_TUN_SETTING_NAME,
 	                        NM_SETTING_VLAN_SETTING_NAME,
+	                        NM_SETTING_VRF_SETTING_NAME,
 	                        NM_SETTING_VXLAN_SETTING_NAME,
 	                        NM_SETTING_WIREGUARD_SETTING_NAME))
 		return TRUE;
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h
index b7222df2..ef9e982b 100644
--- a/libnm-core/nm-core-internal.h
+++ b/libnm-core/nm-core-internal.h
@@ -61,6 +61,7 @@
 #include "nm-setting-tun.h"
 #include "nm-setting-vlan.h"
 #include "nm-setting-vpn.h"
+#include "nm-setting-vrf.h"
 #include "nm-setting-vxlan.h"
 #include "nm-setting-wifi-p2p.h"
 #include "nm-setting-wimax.h"
@@ -94,6 +95,7 @@
 #define NM_BR_PORT_DEF_PRIORITY 32
 
 #define NM_BR_PORT_MAX_PATH_COST 65535
+#define NM_BR_PORT_DEF_PATH_COST 100
 
 /* NM_SETTING_COMPARE_FLAG_INFERRABLE: check whether a device-generated
  * connection can be replaced by a already-defined connection. This flag only
@@ -389,15 +391,6 @@ GVariant *_nm_dbus_proxy_call_finish (GDBusProxy           *proxy,
                                       const GVariantType   *reply_type,
                                       GError              **error);
 
-GVariant *_nm_dbus_proxy_call_sync   (GDBusProxy           *proxy,
-                                      const char           *method_name,
-                                      GVariant             *parameters,
-                                      const GVariantType   *reply_type,
-                                      GDBusCallFlags        flags,
-                                      int                   timeout_msec,
-                                      GCancellable         *cancellable,
-                                      GError              **error);
-
 GVariant * _nm_dbus_connection_call_finish (GDBusConnection *dbus_connection,
                                             GAsyncResult *result,
                                             const GVariantType *reply_type,
@@ -495,15 +488,15 @@ NMSettingIPConfig *nm_connection_get_setting_ip_config (NMConnection *connection
 
 typedef enum {
 	NM_BOND_OPTION_TYPE_INT,
-	NM_BOND_OPTION_TYPE_STRING,
 	NM_BOND_OPTION_TYPE_BOTH,
 	NM_BOND_OPTION_TYPE_IP,
 	NM_BOND_OPTION_TYPE_MAC,
 	NM_BOND_OPTION_TYPE_IFNAME,
 } NMBondOptionType;
 
-NMBondOptionType
-_nm_setting_bond_get_option_type (NMSettingBond *setting, const char *name);
+NMBondOptionType _nm_setting_bond_get_option_type (NMSettingBond *setting, const char *name);
+
+const char* nm_setting_bond_get_option_or_default (NMSettingBond *self, const char *option);
 
 /*****************************************************************************/
 
@@ -528,14 +521,17 @@ NMConnectionMultiConnect _nm_connection_get_multi_connect (NMConnection *connect
 /*****************************************************************************/
 
 typedef enum {
-	NM_BOND_MODE_UNKNOWN = 0,
-	NM_BOND_MODE_ROUNDROBIN,
-	NM_BOND_MODE_ACTIVEBACKUP,
-	NM_BOND_MODE_XOR,
-	NM_BOND_MODE_BROADCAST,
-	NM_BOND_MODE_8023AD,
-	NM_BOND_MODE_TLB,
-	NM_BOND_MODE_ALB,
+	NM_BOND_MODE_UNKNOWN      = -1,
+
+	/* The numeric values correspond to kernel's numbering of the modes. */
+	NM_BOND_MODE_ROUNDROBIN   = 0,
+	NM_BOND_MODE_ACTIVEBACKUP = 1,
+	NM_BOND_MODE_XOR          = 2,
+	NM_BOND_MODE_BROADCAST    = 3,
+	NM_BOND_MODE_8023AD       = 4,
+	NM_BOND_MODE_TLB          = 5,
+	NM_BOND_MODE_ALB          = 6,
+	_NM_BOND_MODE_NUM,
 } NMBondMode;
 
 NMBondMode _nm_setting_bond_mode_from_string (const char *str);
@@ -545,34 +541,6 @@ gboolean _nm_setting_bond_option_supported (const char *option, NMBondMode mode)
 
 NMSettingBluetooth *_nm_connection_get_setting_bluetooth_for_nap (NMConnection *connection);
 
-/*****************************************************************************/
-
-const char *nm_utils_inet_ntop (int addr_family, gconstpointer addr, char *dst);
-
-static inline char *
-nm_utils_inet4_ntop_dup (in_addr_t addr)
-{
-	char buf[NM_UTILS_INET_ADDRSTRLEN];
-
-	return g_strdup (nm_utils_inet4_ntop (addr, buf));
-}
-
-static inline char *
-nm_utils_inet6_ntop_dup (const struct in6_addr *addr)
-{
-	char buf[NM_UTILS_INET_ADDRSTRLEN];
-
-	return g_strdup (nm_utils_inet6_ntop (addr, buf));
-}
-
-static inline char *
-nm_utils_inet_ntop_dup (int addr_family, gconstpointer addr)
-{
-	char buf[NM_UTILS_INET_ADDRSTRLEN];
-
-	return g_strdup (nm_utils_inet_ntop (addr_family, addr, buf));
-}
-
 gboolean _nm_utils_inet6_is_token (const struct in6_addr *in6addr);
 
 /*****************************************************************************/
diff --git a/libnm-core/nm-core-types.h b/libnm-core/nm-core-types.h
index d7c1b5b5..d1ce6310 100644
--- a/libnm-core/nm-core-types.h
+++ b/libnm-core/nm-core-types.h
@@ -54,6 +54,7 @@ typedef struct _NMSettingTun              NMSettingTun;
 typedef struct _NMSettingUser             NMSettingUser;
 typedef struct _NMSettingVlan             NMSettingVlan;
 typedef struct _NMSettingVpn              NMSettingVpn;
+typedef struct _NMSettingVrf              NMSettingVrf;
 typedef struct _NMSettingVxlan            NMSettingVxlan;
 typedef struct _NMSettingWifiP2P          NMSettingWifiP2P;
 typedef struct _NMSettingWimax            NMSettingWimax;
diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h
index 1cf1c888..28ee89bf 100644
--- a/libnm-core/nm-dbus-interface.h
+++ b/libnm-core/nm-dbus-interface.h
@@ -52,6 +52,7 @@
 #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_VRF           NM_DBUS_INTERFACE_DEVICE ".Vrf"
 #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"
@@ -92,7 +93,7 @@
  * @NM_CAPABILITY_TEAM: Teams can be managed. This means the team device plugin
  *   is loaded.
  * @NM_CAPABILITY_OVS: OpenVSwitch can be managed. This means the OVS device plugin
- *   is loaded. Since: 1.24, 1.22.2
+ *   is loaded. Since: 1.24
  *
  * #NMCapability names the numbers in the Capabilities property.
  * Capabilities are positive numbers. They are part of stable API
@@ -213,6 +214,7 @@ typedef enum {
  * @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)
+ * @NM_DEVICE_TYPE_VRF: A VRF (Virtual Routing and Forwarding) interface (Since: 1.24)
  *
  * #NMDeviceType values indicate the type of hardware represented by a
  * device object.
@@ -249,6 +251,7 @@ typedef enum {
 	NM_DEVICE_TYPE_6LOWPAN       = 28,
 	NM_DEVICE_TYPE_WIREGUARD     = 29,
 	NM_DEVICE_TYPE_WIFI_P2P      = 30,
+	NM_DEVICE_TYPE_VRF           = 31,
 } NMDeviceType;
 
 /**
@@ -346,6 +349,8 @@ typedef enum { /*< underscore_name=nm_802_11_ap_flags, flags >*/
  * is supported
  * @NM_802_11_AP_SEC_KEY_MGMT_SAE: WPA/RSN Simultaneous Authentication of Equals is
  * supported
+ * @NM_802_11_AP_SEC_KEY_MGMT_OWE: WPA/RSN Opportunistic Wireless Encryption 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
@@ -364,6 +369,7 @@ typedef enum { /*< underscore_name=nm_802_11_ap_security_flags, flags >*/
 	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,
+	NM_802_11_AP_SEC_KEY_MGMT_OWE    = 0x00000800,
 } NM80211ApSecurityFlags;
 
 /**
@@ -1178,4 +1184,85 @@ typedef enum { /*< flags >*/
 	NM_DEVICE_INTERFACE_FLAG_CARRIER     = 0x10000,
 } NMDeviceInterfaceFlags;
 
+/**
+ * NMClientPermission:
+ * @NM_CLIENT_PERMISSION_NONE: unknown or no permission
+ * @NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK: controls whether networking
+ *  can be globally enabled or disabled
+ * @NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI: controls whether Wi-Fi can be
+ *  globally enabled or disabled
+ * @NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN: controls whether WWAN (3G) can be
+ *  globally enabled or disabled
+ * @NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX: controls whether WiMAX can be
+ *  globally enabled or disabled
+ * @NM_CLIENT_PERMISSION_SLEEP_WAKE: controls whether the client can ask
+ *  NetworkManager to sleep and wake
+ * @NM_CLIENT_PERMISSION_NETWORK_CONTROL: controls whether networking connections
+ *  can be started, stopped, and changed
+ * @NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED: controls whether a password
+ *  protected Wi-Fi hotspot can be created
+ * @NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN: controls whether an open Wi-Fi hotspot
+ *  can be created
+ * @NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM: controls whether connections
+ *  that are available to all users can be modified
+ * @NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN: controls whether connections
+ *  owned by the current user can be modified
+ * @NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME: controls whether the
+ *  persistent hostname can be changed
+ * @NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS: modify persistent global
+ *  DNS configuration
+ * @NM_CLIENT_PERMISSION_RELOAD: controls access to Reload.
+ * @NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK: permission to create checkpoints.
+ * @NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS: controls whether device
+ *  statistics can be globally enabled or disabled
+ * @NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK: controls whether
+ *  connectivity check can be enabled or disabled
+ * @NM_CLIENT_PERMISSION_WIFI_SCAN: controls whether wifi scans can be performed
+ * @NM_CLIENT_PERMISSION_LAST: a reserved boundary value
+ *
+ * #NMClientPermission values indicate various permissions that NetworkManager
+ * clients can obtain to perform certain tasks on behalf of the current user.
+ **/
+typedef enum {
+	NM_CLIENT_PERMISSION_NONE = 0,
+	NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK = 1,
+	NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI = 2,
+	NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN = 3,
+	NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX = 4,
+	NM_CLIENT_PERMISSION_SLEEP_WAKE = 5,
+	NM_CLIENT_PERMISSION_NETWORK_CONTROL = 6,
+	NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED = 7,
+	NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN = 8,
+	NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM = 9,
+	NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN = 10,
+	NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME = 11,
+	NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS = 12,
+	NM_CLIENT_PERMISSION_RELOAD = 13,
+	NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK = 14,
+	NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS = 15,
+	NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK = 16,
+	NM_CLIENT_PERMISSION_WIFI_SCAN = 17,
+
+	NM_CLIENT_PERMISSION_LAST = 17,
+} NMClientPermission;
+
+/**
+ * NMClientPermissionResult:
+ * @NM_CLIENT_PERMISSION_RESULT_UNKNOWN: unknown or no authorization
+ * @NM_CLIENT_PERMISSION_RESULT_YES: the permission is available
+ * @NM_CLIENT_PERMISSION_RESULT_AUTH: authorization is necessary before the
+ *  permission is available
+ * @NM_CLIENT_PERMISSION_RESULT_NO: permission to perform the operation is
+ *  denied by system policy
+ *
+ * #NMClientPermissionResult values indicate what authorizations and permissions
+ * the user requires to obtain a given #NMClientPermission
+ **/
+typedef enum {
+	NM_CLIENT_PERMISSION_RESULT_UNKNOWN = 0,
+	NM_CLIENT_PERMISSION_RESULT_YES,
+	NM_CLIENT_PERMISSION_RESULT_AUTH,
+	NM_CLIENT_PERMISSION_RESULT_NO
+} NMClientPermissionResult;
+
 #endif /* __NM_DBUS_INTERFACE_H__ */
diff --git a/libnm-core/nm-dbus-types.xml b/libnm-core/nm-dbus-types.xml
index ad547361..0a5501ae 100644
--- a/libnm-core/nm-dbus-types.xml
+++ b/libnm-core/nm-dbus-types.xml
@@ -35,7 +35,7 @@
             <row role="constant">
               <entry role="enum_member_name"><para>NM_CAPABILITY_OVS</para><para></para></entry>
               <entry role="enum_member_value"><para>= <literal>2</literal></para><para></para></entry>
-              <entry role="enum_member_description"><para>OpenVSwitch can be managed. This means the OVS device plugin is loaded. Since: 1.24, 1.22.2</para><para></para></entry>
+              <entry role="enum_member_description"><para>OpenVSwitch can be managed. This means the OVS device plugin is loaded. Since: 1.24</para><para></para></entry>
             </row>
           </tbody>
         </tgroup>
@@ -317,6 +317,11 @@
               <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>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_DEVICE_TYPE_VRF</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>31</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>A VRF (Virtual Routing and Forwarding) interface (Since: 1.24)</para><para></para></entry>
+            </row>
           </tbody>
         </tgroup>
       </informaltable>
@@ -577,6 +582,11 @@
               <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>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_802_11_AP_SEC_KEY_MGMT_OWE</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x00000800</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>WPA/RSN Opportunistic Wireless Encryption is supported</para><para></para></entry>
+            </row>
           </tbody>
         </tgroup>
       </informaltable>
@@ -1988,4 +1998,154 @@
     </refsect3>
   </refsect2>
 
+  <refsect2 id="NMClientPermission" role="enum">
+    <title>enum NMClientPermission</title>
+    <indexterm zone="NMClientPermission">
+      <primary>NMClientPermission</primary>
+    </indexterm>
+    <para><para><link linkend="NMClientPermission">NMClientPermission</link> values indicate various permissions that NetworkManager clients can obtain to perform certain tasks on behalf of the current user.</para><para></para></para>
+    <refsect3 role="enum_members">
+      <title>Values</title>
+      <informaltable role="enum_members_table" pgwide="1" frame="none">
+        <tgroup cols="4">
+          <colspec colname="enum_members_name" colwidth="300px" />
+          <colspec colname="enum_members_value" colwidth="100px"/>
+          <colspec colname="enum_members_description" />
+          <tbody>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_NONE</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>unknown or no permission</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_ENABLE_DISABLE_NETWORK</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>1</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether networking can be globally enabled or disabled</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIFI</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>2</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether Wi-Fi can be globally enabled or disabled</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_ENABLE_DISABLE_WWAN</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>3</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether WWAN (3G) can be globally enabled or disabled</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_ENABLE_DISABLE_WIMAX</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>4</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether WiMAX can be globally enabled or disabled</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_SLEEP_WAKE</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>5</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether the client can ask NetworkManager to sleep and wake</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_NETWORK_CONTROL</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>6</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether networking connections can be started, stopped, and changed</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_WIFI_SHARE_PROTECTED</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>7</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether a password protected Wi-Fi hotspot can be created</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_WIFI_SHARE_OPEN</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>8</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether an open Wi-Fi hotspot can be created</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_SETTINGS_MODIFY_SYSTEM</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>9</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether connections that are available to all users can be modified</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_SETTINGS_MODIFY_OWN</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>10</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether connections owned by the current user can be modified</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_SETTINGS_MODIFY_HOSTNAME</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>11</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether the persistent hostname can be changed</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>12</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>modify persistent global DNS configuration</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_RELOAD</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>13</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls access to Reload.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_CHECKPOINT_ROLLBACK</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>14</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>permission to create checkpoints.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_ENABLE_DISABLE_STATISTICS</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>15</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether device statistics can be globally enabled or disabled</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>16</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether connectivity check can be enabled or disabled</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_WIFI_SCAN</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>17</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>controls whether wifi scans can be performed</para><para></para></entry>
+            </row>
+          </tbody>
+        </tgroup>
+      </informaltable>
+    </refsect3>
+  </refsect2>
+
+  <refsect2 id="NMClientPermissionResult" role="enum">
+    <title>enum NMClientPermissionResult</title>
+    <indexterm zone="NMClientPermissionResult">
+      <primary>NMClientPermissionResult</primary>
+    </indexterm>
+    <para><para><link linkend="NMClientPermissionResult">NMClientPermissionResult</link> values indicate what authorizations and permissions the user requires to obtain a given <link linkend="NMClientPermission">NMClientPermission</link></para><para></para></para>
+    <refsect3 role="enum_members">
+      <title>Values</title>
+      <informaltable role="enum_members_table" pgwide="1" frame="none">
+        <tgroup cols="4">
+          <colspec colname="enum_members_name" colwidth="300px" />
+          <colspec colname="enum_members_value" colwidth="100px"/>
+          <colspec colname="enum_members_description" />
+          <tbody>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_RESULT_UNKNOWN</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>unknown or no authorization</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_RESULT_YES</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>1</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>the permission is available</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_RESULT_AUTH</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>2</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>authorization is necessary before the permission is available</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_CLIENT_PERMISSION_RESULT_NO</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>3</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>permission to perform the operation is denied by system policy</para><para></para></entry>
+            </row>
+          </tbody>
+        </tgroup>
+      </informaltable>
+    </refsect3>
+  </refsect2>
+
 </refentry>
diff --git a/libnm-core/nm-dbus-utils.c b/libnm-core/nm-dbus-utils.c
index 83092948..72b9fe1f 100644
--- a/libnm-core/nm-dbus-utils.c
+++ b/libnm-core/nm-dbus-utils.c
@@ -221,52 +221,6 @@ _nm_dbus_proxy_call_finish (GDBusProxy          *proxy,
 	return variant;
 }
 
-/**
- * _nm_dbus_proxy_call_sync:
- * @proxy: A #GDBusProxy.
- * @method_name: Name of method to invoke.
- * @parameters: (allow-none): A #GVariant tuple with parameters for the signal
- *   or %NULL if not passing parameters.
- * @reply_type: (allow-none): the expected type of the reply, or %NULL
- * @flags: Flags from the #GDBusCallFlags enumeration.
- * @timeout_msec: The timeout in milliseconds (with %G_MAXINT meaning
- *   "infinite") or -1 to use the proxy default timeout.
- * @cancellable: (allow-none): A #GCancellable or %NULL.
- * @error: Return location for error or %NULL.
- *
- * Synchronously invokes the @method_name method on @proxy, as with
- * g_dbus_proxy_call_sync(), except that if @reply_type is non-%NULL, then the
- * reply to the call will be checked against it, and an error returned if it
- * does not match.
- *
- * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with
- * return values. Free with g_variant_unref().
- */
-GVariant *
-_nm_dbus_proxy_call_sync (GDBusProxy          *proxy,
-                          const char          *method_name,
-                          GVariant            *parameters,
-                          const GVariantType  *reply_type,
-                          GDBusCallFlags       flags,
-                          int                  timeout_msec,
-                          GCancellable        *cancellable,
-                          GError             **error)
-{
-	GVariant *variant;
-
-	variant = g_dbus_proxy_call_sync (proxy,
-	                                  method_name,
-	                                  parameters,
-	                                  flags,
-	                                  timeout_msec,
-	                                  cancellable,
-	                                  error);
-	if (   variant
-	    && !_nm_dbus_typecheck_response (variant, reply_type, error))
-		nm_clear_pointer (&variant, g_variant_unref);
-	return variant;
-}
-
 GVariant *
 _nm_dbus_connection_call_finish (GDBusConnection *dbus_connection,
                                  GAsyncResult *result,
diff --git a/libnm-core/nm-errors.h b/libnm-core/nm-errors.h
index 0b54133d..031cc670 100644
--- a/libnm-core/nm-errors.h
+++ b/libnm-core/nm-errors.h
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0+
+// SPDX-License-Identifier: LGPL-2.1+
 /*
  * Copyright (C) 2004 - 2014 Red Hat, Inc.
  */
diff --git a/libnm-core/nm-keyfile-internal.h b/libnm-core/nm-keyfile-internal.h
deleted file mode 100644
index 42003a5f..00000000
--- a/libnm-core/nm-keyfile-internal.h
+++ /dev/null
@@ -1,180 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2008 Novell, Inc.
- * Copyright (C) 2015 Red Hat, Inc.
- */
-
-#ifndef __NM_KEYFILE_INTERNAL_H__
-#define __NM_KEYFILE_INTERNAL_H__
-
-#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)
-#error Cannot use this header.
-#endif
-
-#include <sys/types.h>
-
-#include "nm-connection.h"
-#include "nm-setting-8021x.h"
-
-#include "nm-core-internal.h"
-#include "nm-meta-setting.h"
-
-/*****************************************************************************/
-
-#define NM_KEYFILE_CERT_SCHEME_PREFIX_PATH "file://"
-#define NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11 "pkcs11:"
-#define NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB "data:;base64,"
-
-char *nm_keyfile_detect_unqualified_path_scheme (const char *base_dir,
-                                                 gconstpointer pdata,
-                                                 gsize data_len,
-                                                 gboolean consider_exists,
-                                                 gboolean *out_exists);
-
-typedef enum {
-	NM_KEYFILE_READ_TYPE_WARN               = 1,
-} NMKeyfileReadType;
-
-/**
- * NMKeyfileReadHandler:
- *
- * Hook to nm_keyfile_read(). The user might fail the reading by setting
- * @error.
- *
- * Returns: should return TRUE, if the reading was handled. Otherwise,
- * a default action will be performed that depends on the @type.
- * For %NM_KEYFILE_READ_TYPE_WARN type, the default action is doing nothing.
- */
-typedef gboolean (*NMKeyfileReadHandler) (GKeyFile *keyfile,
-                                          NMConnection *connection,
-                                          NMKeyfileReadType type,
-                                          void *type_data,
-                                          void *user_data,
-                                          GError **error);
-
-typedef enum {
-	NM_KEYFILE_WARN_SEVERITY_DEBUG                  = 1000,
-	NM_KEYFILE_WARN_SEVERITY_INFO                   = 2000,
-	NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE      = 2901,
-	NM_KEYFILE_WARN_SEVERITY_WARN                   = 3000,
-} NMKeyfileWarnSeverity;
-
-/**
- * NMKeyfileReadTypeDataWarn:
- *
- * this struct is passed as @type_data for the @NMKeyfileReadHandler of
- * type %NM_KEYFILE_READ_TYPE_WARN.
- */
-typedef struct {
-	/* might be %NULL, if the warning is not about a group. */
-	const char *group;
-
-	/* might be %NULL, if the warning is not about a setting. */
-	NMSetting *setting;
-
-	/* might be %NULL, if the warning is not about a property. */
-	const char *property_name;
-
-	NMKeyfileWarnSeverity severity;
-	const char *message;
-} NMKeyfileReadTypeDataWarn;
-
-NMConnection *nm_keyfile_read (GKeyFile *keyfile,
-                               const char *base_dir,
-                               NMKeyfileReadHandler handler,
-                               void *user_data,
-                               GError **error);
-
-gboolean nm_keyfile_read_ensure_id (NMConnection *connection,
-                                    const char *fallback_id);
-
-gboolean nm_keyfile_read_ensure_uuid (NMConnection *connection,
-                                      const char *fallback_uuid_seed);
-
-/*****************************************************************************/
-
-typedef enum {
-	NM_KEYFILE_WRITE_TYPE_CERT              = 1,
-} NMKeyfileWriteType;
-
-/**
- * NMKeyfileWriteHandler:
- *
- * This is a hook to tweak the serialization.
- *
- * Handler for certain properties or events that are not entirely contained
- * within the keyfile or that might be serialized differently. The @type and
- * @type_data arguments tell which kind of argument we have at hand.
- *
- * Currently only the type %NM_KEYFILE_WRITE_TYPE_CERT is supported, which provides
- * @type_data as %NMKeyfileWriteTypeDataCert. However, this handler should be generic enough
- * to support other types as well.
- *
- * This don't have to be only "properties". For example, nm_keyfile_read() uses
- * a similar handler to push warnings to the caller.
- *
- * If the handler raises an error, it should set the @error value. This causes
- * the an overall failure.
- *
- * Returns: whether the issue was handled. If the type was unhandled,
- * a default action will be performed. This might be raise an error,
- * do some fallback parsing, or do nothing.
- */
-typedef gboolean (*NMKeyfileWriteHandler) (NMConnection *connection,
-                                           GKeyFile *keyfile,
-                                           NMKeyfileWriteType type,
-                                           void *type_data,
-                                           void *user_data,
-                                           GError **error);
-
-/**
- * NMKeyfileWriteTypeDataCert:
- *
- * this struct is passed as @type_data for the @NMKeyfileWriteHandler of
- * type %NM_KEYFILE_WRITE_TYPE_CERT.
- */
-typedef struct {
-	const NMSetting8021xSchemeVtable *vtable;
-	NMSetting8021x *setting;
-} NMKeyfileWriteTypeDataCert;
-
-GKeyFile *nm_keyfile_write (NMConnection *connection,
-                            NMKeyfileWriteHandler handler,
-                            void *user_data,
-                            GError **error);
-
-/*****************************************************************************/
-
-char *nm_keyfile_plugin_kf_get_string (GKeyFile *kf, const char *group, const char *key, GError **error);
-void nm_keyfile_plugin_kf_set_string (GKeyFile *kf, const char *group, const char *key, const char *value);
-
-int nm_key_file_get_boolean (GKeyFile *kf, const char *group, const char *key, int default_value);
-
-void _nm_keyfile_copy (GKeyFile *dst, GKeyFile *src);
-gboolean _nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b);
-gboolean _nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order);
-gboolean _nm_keyfile_has_values (GKeyFile *keyfile);
-
-/*****************************************************************************/
-
-#define NM_KEYFILE_GROUP_NMMETA                 ".nmmeta"
-#define NM_KEYFILE_KEY_NMMETA_NM_GENERATED      "nm-generated"
-#define NM_KEYFILE_KEY_NMMETA_VOLATILE          "volatile"
-#define NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE  "shadowed-storage"
-#define NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED    "shadowed-owned"
-
-#define NM_KEYFILE_PATH_NAME_LIB                 NMLIBDIR  "/system-connections"
-#define NM_KEYFILE_PATH_NAME_ETC_DEFAULT         NMCONFDIR "/system-connections"
-#define NM_KEYFILE_PATH_NAME_RUN                 NMRUNDIR  "/system-connections"
-
-#define NM_KEYFILE_PATH_SUFFIX_NMCONNECTION      ".nmconnection"
-
-#define NM_KEYFILE_PATH_SUFFIX_NMMETA            ".nmmeta"
-
-#define NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL      "/dev/null"
-
-gboolean nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension);
-
-char *nm_keyfile_utils_create_filename (const char *filename, gboolean with_extension);
-
-#endif /* __NM_KEYFILE_INTERNAL_H__ */
diff --git a/libnm-core/nm-keyfile-utils.c b/libnm-core/nm-keyfile-utils.c
deleted file mode 100644
index e18db20d..00000000
--- a/libnm-core/nm-keyfile-utils.c
+++ /dev/null
@@ -1,636 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2010 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-keyfile-utils.h"
-
-#include <stdlib.h>
-
-#include "nm-keyfile-internal.h"
-#include "nm-setting-wired.h"
-#include "nm-setting-wireless.h"
-#include "nm-setting-wireless-security.h"
-
-/*****************************************************************************/
-
-/**
- * nm_key_file_get_boolean:
- * @kf: the #GKeyFile
- * @group: the group
- * @key: the key
- * @default_value: the default value if the value is set or not parsable as a boolean.
- *
- * Replacement for g_key_file_get_boolean() (which uses g_key_file_parse_value_as_boolean()).
- * g_key_file_get_boolean() seems odd to me, because it accepts trailing ASCII whitespace,
- * but not leading.
- * This uses _nm_utils_ascii_str_to_bool(), which accepts trailing and leading whitespace,
- * case-insensitive words, and also strings like "on" and "off".
- * _nm_utils_ascii_str_to_bool() is our way to parse booleans from string, and we should
- * use that one consistently.
- *
- * Also, it doesn't have g_key_file_get_boolean()'s odd API to require an error argument
- * to detect parsing failures.
- *
- * Returns: either %TRUE or %FALSE if the key exists and is parsable as a boolean.
- *   Otherwise, @default_value. Sets errno to ENODATA, EINVAL or 0, depending on whether
- *   the key exists, whether the value is invalid, or success.
- */
-int
-nm_key_file_get_boolean (GKeyFile *kf, const char *group, const char *key, int default_value)
-{
-	int v;
-	gs_free char *value = NULL;
-
-	value = g_key_file_get_value (kf, group, key, NULL);
-
-	if (!value) {
-		errno = ENODATA;
-		return default_value;
-	}
-	v = _nm_utils_ascii_str_to_bool (value, -1);
-	if (v != -1) {
-		errno = 0;
-		return v;
-	}
-	errno = EINVAL;
-	return default_value;
-}
-
-/*****************************************************************************/
-
-typedef struct {
-	const char *setting;
-	const char *alias;
-} SettingAlias;
-
-static const SettingAlias alias_list[] = {
-	{ NM_SETTING_WIRED_SETTING_NAME, "ethernet" },
-	{ NM_SETTING_WIRELESS_SETTING_NAME, "wifi" },
-	{ NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, "wifi-security" },
-};
-
-const char *
-nm_keyfile_plugin_get_alias_for_setting_name (const char *setting_name)
-{
-	guint i;
-
-	g_return_val_if_fail (setting_name != NULL, NULL);
-
-	for (i = 0; i < G_N_ELEMENTS (alias_list); i++) {
-		if (nm_streq (setting_name, alias_list[i].setting))
-			return alias_list[i].alias;
-	}
-	return NULL;
-}
-
-const char *
-nm_keyfile_plugin_get_setting_name_for_alias (const char *alias)
-{
-	guint i;
-
-	g_return_val_if_fail (alias != NULL, NULL);
-
-	for (i = 0; i < G_N_ELEMENTS (alias_list); i++) {
-		if (nm_streq (alias, alias_list[i].alias))
-			return alias_list[i].setting;
-	}
-	return NULL;
-}
-
-/*****************************************************************************/
-
-/* List helpers */
-#define DEFINE_KF_LIST_WRAPPER(stype, get_ctype, set_ctype) \
-get_ctype \
-nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \
-                                         const char *group, \
-                                         const char *key, \
-                                         gsize *out_length, \
-                                         GError **error) \
-{ \
-	get_ctype list; \
-	const char *alias; \
-	GError *local = NULL; \
-	gsize l; \
- \
-	list = g_key_file_get_##stype##_list (kf, group, key, &l, &local); \
-	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \
-		alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
-		if (alias) { \
-			g_clear_error (&local); \
-			list = g_key_file_get_##stype##_list (kf, alias, key, &l, &local); \
-		} \
-	} \
-	nm_assert ((!local) != (!list)); \
-	if (local) \
-		g_propagate_error (error, local); \
-	if (!list) \
-		l = 0; \
-	NM_SET_OUT (out_length, l); \
-	return list; \
-} \
- \
-void \
-nm_keyfile_plugin_kf_set_##stype##_list (GKeyFile *kf, \
-                                         const char *group, \
-                                         const char *key, \
-                                         set_ctype list[], \
-                                         gsize length) \
-{ \
-	const char *alias; \
- \
-	alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
-	g_key_file_set_##stype##_list (kf, alias ?: group, key, list, length); \
-}
-
-DEFINE_KF_LIST_WRAPPER(integer, int*, int);
-DEFINE_KF_LIST_WRAPPER(string, char **, const char* const);
-
-void
-nm_keyfile_plugin_kf_set_integer_list_uint8 (GKeyFile *kf,
-                                             const char *group,
-                                             const char *key,
-                                             const guint8 *data,
-                                             gsize length)
-{
-	gsize i;
-	gsize l = length * 4 + 2;
-	gs_free char *value = g_malloc (l);
-	char *s = value;
-
-	g_return_if_fail (kf);
-	g_return_if_fail (!length || data);
-	g_return_if_fail (group && group[0]);
-	g_return_if_fail (key && key[0]);
-
-	value[0] = '\0';
-	for (i = 0; i < length; i++)
-		nm_utils_strbuf_append (&s, &l, "%d;", (int) data[i]);
-	nm_assert (l > 0);
-	nm_keyfile_plugin_kf_set_value (kf, group, key, value);
-}
-
-/* Single value helpers */
-#define DEFINE_KF_WRAPPER(stype, get_ctype, set_ctype) \
-get_ctype \
-nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \
-                                  const char *group, \
-                                  const char *key, \
-                                  GError **error) \
-{ \
-	get_ctype val; \
-	const char *alias; \
-	GError *local = NULL; \
- \
-	val = g_key_file_get_##stype (kf, group, key, &local); \
-	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \
-		alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
-		if (alias) { \
-			g_clear_error (&local); \
-			val = g_key_file_get_##stype (kf, alias, key, &local); \
-		} \
-	} \
-	if (local) \
-		g_propagate_error (error, local); \
-	return val; \
-} \
- \
-void \
-nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \
-                                  const char *group, \
-                                  const char *key, \
-                                  set_ctype value) \
-{ \
-	const char *alias; \
- \
-	alias = nm_keyfile_plugin_get_alias_for_setting_name (group); \
-	g_key_file_set_##stype (kf, alias ?: group, key, value); \
-}
-
-DEFINE_KF_WRAPPER(string, char*, const char*);
-DEFINE_KF_WRAPPER(boolean, gboolean, gboolean);
-DEFINE_KF_WRAPPER(value, char*, const char*);
-
-gint64
-nm_keyfile_plugin_kf_get_int64 (GKeyFile *kf,
-                                const char *group,
-                                const char *key,
-                                guint base,
-                                gint64 min,
-                                gint64 max,
-                                gint64 fallback,
-                                GError **error)
-{
-	gs_free char *s = NULL;
-	int errsv;
-	gint64 v;
-
-	s = nm_keyfile_plugin_kf_get_value (kf, group, key, error);
-	if (!s) {
-		errno = ENODATA;
-		return fallback;
-	}
-
-	v = _nm_utils_ascii_str_to_int64 (s, base, min, max, fallback);
-	errsv = errno;
-	if (   errsv != 0
-	    && error) {
-		g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-		             _("value is not an integer in range [%lld, %lld]"),
-		             (long long) min, (long long) max);
-		errno = errsv;
-	}
-	return v;
-}
-
-char **
-nm_keyfile_plugin_kf_get_keys (GKeyFile *kf,
-                               const char *group,
-                               gsize *out_length,
-                               GError **error)
-{
-	char **keys;
-	const char *alias;
-	GError *local = NULL;
-	gsize l;
-
-	keys = g_key_file_get_keys (kf, group, &l, &local);
-	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
-		alias = nm_keyfile_plugin_get_alias_for_setting_name (group);
-		if (alias) {
-			g_clear_error (&local);
-			keys = g_key_file_get_keys (kf, alias, &l, error ? &local : NULL);
-		}
-	}
-	nm_assert ((!local) != (!keys));
-	if (!keys)
-		l = 0;
-	nm_assert (l == NM_PTRARRAY_LEN (keys));
-	NM_SET_OUT (out_length, l);
-	if (local)
-		g_propagate_error (error, local);
-	return keys;
-}
-
-gboolean
-nm_keyfile_plugin_kf_has_key (GKeyFile *kf,
-                              const char *group,
-                              const char *key,
-                              GError **error)
-{
-	gboolean has;
-	const char *alias;
-	GError *local = NULL;
-
-	has = g_key_file_has_key (kf, group, key, &local);
-	if (g_error_matches (local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
-		alias = nm_keyfile_plugin_get_alias_for_setting_name (group);
-		if (alias) {
-			g_clear_error (&local);
-			has = g_key_file_has_key (kf, alias, key, &local);
-		}
-	}
-	if (local)
-		g_propagate_error (error, local);
-	return has;
-}
-
-/*****************************************************************************/
-
-void
-_nm_keyfile_copy (GKeyFile *dst, GKeyFile *src)
-{
-	gs_strfreev char **groups = NULL;
-	guint g, k;
-
-	groups = g_key_file_get_groups (src, NULL);
-	for (g = 0; groups && groups[g]; g++) {
-		const char *group = groups[g];
-		gs_strfreev char **keys = NULL;
-
-		keys = g_key_file_get_keys (src, group, NULL, NULL);
-		if (!keys)
-			continue;
-
-		for (k = 0; keys[k]; k++) {
-			const char *key = keys[k];
-			gs_free char *value = NULL;
-
-			value = g_key_file_get_value (src, group, key, NULL);
-			if (value)
-				g_key_file_set_value (dst, group, key, value);
-			else
-				g_key_file_remove_key (dst, group, key, NULL);
-		}
-	}
-}
-
-/*****************************************************************************/
-
-gboolean
-_nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b)
-{
-	gs_strfreev char **groups = NULL;
-	guint i, j;
-
-	if (kf_a == kf_b)
-		return TRUE;
-	if (!kf_a || !kf_b)
-		return FALSE;
-
-	groups = g_key_file_get_groups (kf_a, NULL);
-	for (i = 0; groups && groups[i]; i++) {
-		gs_strfreev char **keys = NULL;
-
-		keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL);
-		if (!keys)
-			continue;
-
-		for (j = 0; keys[j]; j++) {
-			gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL);
-			gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL);
-
-			if (g_strcmp0 (key_a, key_b) != 0)
-				return FALSE;
-		}
-	}
-	return TRUE;
-}
-
-static gboolean
-_nm_keyfile_equals_ordered (GKeyFile *kf_a, GKeyFile *kf_b)
-{
-	gs_strfreev char **groups = NULL;
-	gs_strfreev char **groups_b = NULL;
-	guint i, j;
-
-	if (kf_a == kf_b)
-		return TRUE;
-	if (!kf_a || !kf_b)
-		return FALSE;
-
-	groups = g_key_file_get_groups (kf_a, NULL);
-	groups_b = g_key_file_get_groups (kf_b, NULL);
-	if (!groups && !groups_b)
-		return TRUE;
-	if (!groups || !groups_b)
-		return FALSE;
-	for (i = 0; groups[i] && groups_b[i] && !strcmp (groups[i], groups_b[i]); i++)
-		;
-	if (groups[i] || groups_b[i])
-		return FALSE;
-
-	for (i = 0; groups[i]; i++) {
-		gs_strfreev char **keys = NULL;
-		gs_strfreev char **keys_b = NULL;
-
-		keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL);
-		keys_b = g_key_file_get_keys (kf_b, groups[i], NULL, NULL);
-
-		if ((!keys) != (!keys_b))
-			return FALSE;
-		if (!keys)
-			continue;
-
-		for (j = 0; keys[j] && keys_b[j] && !strcmp (keys[j], keys_b[j]); j++)
-			;
-		if (keys[j] || keys_b[j])
-			return FALSE;
-
-		for (j = 0; keys[j]; j++) {
-			gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL);
-			gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL);
-
-			if (g_strcmp0 (key_a, key_b) != 0)
-				return FALSE;
-		}
-	}
-	return TRUE;
-}
-
-gboolean
-_nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order)
-{
-	if (!consider_order) {
-		return    _nm_keyfile_a_contains_all_in_b (kf_a, kf_b)
-		       && _nm_keyfile_a_contains_all_in_b (kf_b, kf_a);
-	} else {
-		return _nm_keyfile_equals_ordered (kf_a, kf_b);
-	}
-}
-
-gboolean
-_nm_keyfile_has_values (GKeyFile *keyfile)
-{
-	gs_strfreev char **groups = NULL;
-
-	g_return_val_if_fail (keyfile, FALSE);
-
-	groups = g_key_file_get_groups (keyfile, NULL);
-	return groups && groups[0];
-}
-
-/*****************************************************************************/
-
-static const char *
-_keyfile_key_encode (const char *name,
-                     char **out_to_free)
-{
-	gsize len, i;
-	GString *str;
-
-	nm_assert (name);
-	nm_assert (out_to_free && !*out_to_free);
-
-	/* See g_key_file_is_key_name().
-	 *
-	 * GKeyFile allows all UTF-8 characters (even non-well formed sequences),
-	 * except:
-	 *  - no empty keys
-	 *  - no leading/trailing ' '
-	 *  - no '=', '[', ']'
-	 *
-	 * We do something more strict here. All non-ASCII characters, all non-printable
-	 * characters, and all invalid characters are escaped with "\\XX".
-	 *
-	 * We don't escape \\, unless it is followed by two hex digits.
-	 */
-
-	if (!name[0]) {
-		/* empty keys are are backslash encoded. Note that usually
-		 * \\00 is not a valid encode, the only exception is the empty
-		 * word. */
-		return "\\00";
-	}
-
-	/* find the first character that needs escaping. */
-	i = 0;
-	if (name[0] != ' ') {
-		for (;; i++) {
-			const guchar ch = (guchar) name[i];
-
-			if (ch == '\0')
-				return name;
-
-			if (   ch < 0x20
-			    || ch >= 127
-			    || NM_IN_SET (ch, '=', '[', ']')
-			    || (   ch == '\\'
-			        && g_ascii_isxdigit (name[i + 1])
-			        && g_ascii_isxdigit (name[i + 2]))
-			    || (   ch == ' '
-			        && name[i + 1] == '\0'))
-				break;
-		}
-	} else if (name[1] == '\0')
-		return "\\20";
-
-	len = i + strlen (&name[i]);
-	nm_assert (len == strlen (name));
-	str = g_string_sized_new (len + 15);
-
-	if (name[0] == ' ') {
-		nm_assert (i == 0);
-		g_string_append (str, "\\20");
-		i = 1;
-	} else
-		g_string_append_len (str, name, i);
-
-	for (;; i++) {
-		const guchar ch = (guchar) name[i];
-
-		if (ch == '\0')
-			break;
-
-		if (   ch < 0x20
-		    || ch >= 127
-		    || NM_IN_SET (ch, '=', '[', ']')
-		    || (   ch == '\\'
-		        && g_ascii_isxdigit (name[i + 1])
-		        && g_ascii_isxdigit (name[i + 2]))
-		    || (   ch == ' '
-		        && name[i + 1] == '\0'))
-			g_string_append_printf (str, "\\%02X", ch);
-		else
-			g_string_append_c (str, (char) ch);
-	}
-
-	return (*out_to_free = g_string_free (str, FALSE));
-}
-
-static const char *
-_keyfile_key_decode (const char *key,
-                     char **out_to_free)
-{
-	gsize i, len;
-	GString *str;
-
-	nm_assert (key);
-	nm_assert (out_to_free && !*out_to_free);
-
-	if (!key[0])
-		return "";
-
-	for (i = 0; TRUE; i++) {
-		const char ch = key[i];
-
-		if (ch == '\0')
-			return key;
-		if (   ch == '\\'
-		    && g_ascii_isxdigit (key[i + 1])
-		    && g_ascii_isxdigit (key[i + 2]))
-			break;
-	}
-
-	len = i + strlen (&key[i]);
-
-	if (   len == 3
-	    && nm_streq (key, "\\00"))
-		return "";
-
-	nm_assert (len == strlen (key));
-	str = g_string_sized_new (len + 3);
-
-	g_string_append_len (str, key, i);
-	for (;;) {
-		const char ch = key[i];
-		char ch1, ch2;
-		unsigned v;
-
-		if (ch == '\0')
-			break;
-
-		if (   ch == '\\'
-		    && g_ascii_isxdigit ((ch1 = key[i + 1]))
-		    && g_ascii_isxdigit ((ch2 = key[i + 2]))) {
-			v = (g_ascii_xdigit_value (ch1) << 4) + g_ascii_xdigit_value (ch2);
-			if (v != 0) {
-				g_string_append_c (str, (char) v);
-				i += 3;
-				continue;
-			}
-		}
-		g_string_append_c (str, ch);
-		i++;
-	}
-
-	return (*out_to_free = g_string_free (str, FALSE));
-}
-
-/*****************************************************************************/
-
-const char *
-nm_keyfile_key_encode (const char *name,
-                       char **out_to_free)
-{
-	const char *key;
-
-	key = _keyfile_key_encode (name, out_to_free);
-#if NM_MORE_ASSERTS > 5
-	nm_assert (key);
-	nm_assert (!*out_to_free || key == *out_to_free);
-	nm_assert (!*out_to_free || !nm_streq0 (name, key));
-	{
-		gs_free char *to_free2 = NULL;
-		const char *name2;
-
-		name2 = _keyfile_key_decode (key, &to_free2);
-		/* name2, the result of encode()+decode() is identical to name.
-		 * That is because
-		 *   - encode() is a injective function.
-		 *   - decode() is a surjective function, however for output
-		 *     values of encode() is behaves injective too. */
-		nm_assert (nm_streq0 (name2, name));
-	}
-#endif
-	return key;
-}
-
-const char *
-nm_keyfile_key_decode (const char *key,
-                       char **out_to_free)
-{
-	const char *name;
-
-	name = _keyfile_key_decode (key, out_to_free);
-#if NM_MORE_ASSERTS > 5
-	nm_assert (name);
-	nm_assert (!*out_to_free || name == *out_to_free);
-	{
-		gs_free char *to_free2 = NULL;
-		const char *key2;
-
-		key2 = _keyfile_key_encode (name, &to_free2);
-		/* key2, the result of decode+encode may not be idential
-		 * to the original key. That is, decode() is a surjective
-		 * function mapping different keys to the same name.
-		 * However, decode() behaves injective for input that
-		 * are valid output of encode(). */
-		nm_assert (key2);
-	}
-#endif
-	return name;
-}
diff --git a/libnm-core/nm-keyfile-utils.h b/libnm-core/nm-keyfile-utils.h
deleted file mode 100644
index 7fb80717..00000000
--- a/libnm-core/nm-keyfile-utils.h
+++ /dev/null
@@ -1,85 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2010 - 2015 Red Hat, Inc.
- */
-
-#ifndef __NM_KEYFILE_UTILS_H__
-#define __NM_KEYFILE_UTILS_H__
-
-#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE)
-#error Cannot use this header.
-#endif
-
-#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);
-
-const char *nm_keyfile_plugin_get_setting_name_for_alias (const char *alias);
-
-/*****************************************************************************/
-
-/* List helpers */
-#define DEFINE_KF_LIST_WRAPPER_PROTO(stype, get_ctype, set_ctype) \
-get_ctype nm_keyfile_plugin_kf_get_##stype##_list (GKeyFile *kf, \
-                                                   const char *group, \
-                                                   const char *key, \
-                                                   gsize *out_length, \
-                                                   GError **error); \
-\
-void nm_keyfile_plugin_kf_set_##stype##_list  (GKeyFile *kf, \
-                                               const char *group, \
-                                               const char *key, \
-                                               set_ctype list[], \
-                                               gsize length);
-DEFINE_KF_LIST_WRAPPER_PROTO(integer, int*, int)
-DEFINE_KF_LIST_WRAPPER_PROTO(string, char**, const char* const)
-
-void nm_keyfile_plugin_kf_set_integer_list_uint8 (GKeyFile *kf,
-                                                  const char *group,
-                                                  const char *key,
-                                                  const guint8 *list,
-                                                  gsize length);
-
-/* Single-value helpers */
-#define DEFINE_KF_WRAPPER_PROTO(stype, get_ctype, set_ctype) \
-get_ctype nm_keyfile_plugin_kf_get_##stype (GKeyFile *kf, \
-                                            const char *group, \
-                                            const char *key, \
-                                            GError **error); \
-\
-void nm_keyfile_plugin_kf_set_##stype (GKeyFile *kf, \
-                                       const char *group, \
-                                       const char *key, \
-                                       set_ctype value);
-DEFINE_KF_WRAPPER_PROTO(string, char*, const char*)
-DEFINE_KF_WRAPPER_PROTO(boolean, gboolean, gboolean)
-DEFINE_KF_WRAPPER_PROTO(value, char*, const char*)
-
-/* Misc */
-gint64 nm_keyfile_plugin_kf_get_int64 (GKeyFile *kf,
-                                       const char *group,
-                                       const char *key,
-                                       guint base,
-                                       gint64 min,
-                                       gint64 max,
-                                       gint64 fallback,
-                                       GError **error);
-
-char ** nm_keyfile_plugin_kf_get_keys    (GKeyFile *kf,
-                                           const char *group,
-                                           gsize *out_length,
-                                           GError **error);
-
-gboolean nm_keyfile_plugin_kf_has_key     (GKeyFile *kf,
-                                           const char *group,
-                                           const char *key,
-                                           GError **error);
-
-const char *nm_keyfile_key_encode (const char *name,
-                                   char **out_to_free);
-
-const char *nm_keyfile_key_decode (const char *key,
-                                   char **out_to_free);
-
-#endif  /* __NM_KEYFILE_UTILS_H__ */
diff --git a/libnm-core/nm-keyfile.c b/libnm-core/nm-keyfile.c
deleted file mode 100644
index 535acccf..00000000
--- a/libnm-core/nm-keyfile.c
+++ /dev/null
@@ -1,4089 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2008 - 2009 Novell, Inc.
- * Copyright (C) 2008 - 2017 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-keyfile-internal.h"
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <arpa/inet.h>
-#include <linux/pkt_sched.h>
-
-#include "nm-glib-aux/nm-secret-utils.h"
-#include "systemd/nm-sd-utils-shared.h"
-#include "nm-libnm-core-intern/nm-common-macros.h"
-#include "nm-core-internal.h"
-#include "nm-keyfile-utils.h"
-
-#include "nm-setting-user.h"
-
-/*****************************************************************************/
-
-typedef struct _ParseInfoProperty ParseInfoProperty;
-
-typedef struct {
-	NMConnection *connection;
-	GKeyFile *keyfile;
-	const char *base_dir;
-	NMKeyfileReadHandler handler;
-	void *user_data;
-	GError *error;
-	const char *group;
-	NMSetting *setting;
-} KeyfileReaderInfo;
-
-typedef struct {
-	NMConnection *connection;
-	GKeyFile *keyfile;
-	GError *error;
-	NMKeyfileWriteHandler handler;
-	void *user_data;
-} KeyfileWriterInfo;
-
-/*****************************************************************************/
-
-static void
-_handle_warn (KeyfileReaderInfo *info,
-              const char *property_name,
-              NMKeyfileWarnSeverity severity,
-              char *message)
-{
-	NMKeyfileReadTypeDataWarn type_data = {
-		.group = info->group,
-		.setting = info->setting,
-		.property_name = property_name,
-		.severity = severity,
-		.message = message,
-	};
-
-	info->handler (info->keyfile,
-	               info->connection,
-	               NM_KEYFILE_READ_TYPE_WARN,
-	               &type_data,
-	               info->user_data,
-	               &info->error);
-	g_free (message);
-}
-#define handle_warn(arg_info, arg_property_name, arg_severity, ...) \
-	({ \
-		KeyfileReaderInfo *_info = (arg_info); \
-		\
-		if (_info->handler) { \
-			_handle_warn (_info, (arg_property_name), (arg_severity), \
-			              g_strdup_printf (__VA_ARGS__)); \
-		} \
-		_info->error == NULL; \
-	})
-
-/*****************************************************************************/
-
-static gboolean
-_secret_flags_persist_secret (NMSettingSecretFlags flags)
-{
-	return flags == NM_SETTING_SECRET_FLAG_NONE;
-}
-
-/*****************************************************************************/
-/* Some setting properties also contain setting names, such as
- * NMSettingConnection's 'type' property (which specifies the base type of the
- * connection, e.g. ethernet or wifi) or 'slave-type' (specifies type of slave
- * connection, e.g. bond or bridge). This function handles translating those
- * properties' values to the real setting name if they are an alias.
- */
-static void
-setting_alias_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	const char *key_setting_name;
-	gs_free char *s = NULL;
-
-	s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
-	if (!s)
-		return;
-
-	key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias (s);
-	g_object_set (G_OBJECT (setting),
-	              key,
-	              key_setting_name ?: s,
-	              NULL);
-}
-
-static void
-sriov_vfs_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_unref_ptrarray GPtrArray *vfs = NULL;
-	gs_strfreev char **keys = NULL;
-	gsize n_keys = 0;
-	int i;
-
-	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
-	if (n_keys == 0)
-		return;
-
-	vfs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_sriov_vf_unref);
-
-	for (i = 0; i < n_keys; i++) {
-		gs_free char *value = NULL;
-		NMSriovVF *vf;
-		const char *rest;
-
-		if (!g_str_has_prefix (keys[i], "vf."))
-			continue;
-
-		rest = &keys[i][3];
-
-		if (!NM_STRCHAR_ALL (rest, ch, g_ascii_isdigit (ch)))
-			continue;
-
-		value = nm_keyfile_plugin_kf_get_string (info->keyfile,
-		                                         setting_name,
-		                                         keys[i],
-		                                         NULL);
-
-		vf = _nm_utils_sriov_vf_from_strparts (rest, value, TRUE, NULL);
-		if (vf)
-			g_ptr_array_add (vfs, vf);
-	}
-
-	g_object_set (G_OBJECT (setting),
-	              key, vfs,
-	              NULL);
-}
-
-static void
-read_array_of_uint (GKeyFile *file,
-                    NMSetting *setting,
-                    const char *key)
-{
-	gs_unref_array GArray *array = NULL;
-	gsize length;
-	gsize i;
-	gs_free int *tmp = NULL;
-
-	tmp = nm_keyfile_plugin_kf_get_integer_list (file, nm_setting_get_name (setting), key, &length, NULL);
-	if (length > G_MAXUINT)
-		return;
-
-	array = g_array_sized_new (FALSE, FALSE, sizeof (guint), length);
-
-	for (i = 0; i < length; i++) {
-		if (tmp[i] < 0)
-			return;
-		g_array_append_val (array, tmp[i]);
-	}
-
-	g_object_set (setting, key, array, NULL);
-}
-
-static gboolean
-get_one_int (KeyfileReaderInfo *info, const char *property_name, const char *str, guint32 max_val, guint32 *out)
-{
-	gint64 tmp;
-
-	g_return_val_if_fail (!info == !property_name, FALSE);
-
-	if (!str || !str[0]) {
-		if (property_name)
-			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("ignoring missing number"));
-		return FALSE;
-	}
-
-	tmp = _nm_utils_ascii_str_to_int64 (str, 10, 0, max_val, -1);
-	if (tmp == -1) {
-		if (property_name) {
-			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("ignoring invalid number '%s'"),
-			            str);
-		}
-		return FALSE;
-	}
-
-	*out = (guint32) tmp;
-	return TRUE;
-}
-
-static gpointer
-build_address (KeyfileReaderInfo *info, int family, const char *address_str, guint32 plen, const char *property_name)
-{
-	NMIPAddress *addr;
-	GError *error = NULL;
-
-	g_return_val_if_fail (address_str, NULL);
-
-	addr = nm_ip_address_new (family, address_str, plen, &error);
-	if (!addr) {
-		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("ignoring invalid %s address: %s"),
-		            family == AF_INET ? "IPv4" : "IPv6", error->message);
-		g_error_free (error);
-	}
-
-	return addr;
-}
-
-static gpointer
-build_route (KeyfileReaderInfo *info,
-             const char *property_name,
-             int family,
-             const char *dest_str,
-             guint32 plen,
-             const char *gateway_str,
-             const char *metric_str)
-{
-	NMIPRoute *route;
-	guint32 u32;
-	gint64 metric = -1;
-	GError *error = NULL;
-
-	g_return_val_if_fail (dest_str, NULL);
-
-	/* Next hop */
-	if (gateway_str && gateway_str[0]) {
-		if (!nm_utils_ipaddr_valid (family, gateway_str)) {
-			/* Try workaround for routes written by broken keyfile writer.
-			 * Due to bug bgo#719851, an older version of writer would have
-			 * written "a:b:c:d::/plen,metric" if the gateway was ::, instead
-			 * of "a:b:c:d::/plen,,metric" or "a:b:c:d::/plen,::,metric"
-			 * Try workaround by interpreting gateway_str as metric to accept such
-			 * invalid routes. This broken syntax should not be not officially
-			 * supported.
-			 **/
-			if (   family == AF_INET6
-			    && !metric_str
-			    && get_one_int (NULL, NULL, gateway_str, G_MAXUINT32, &u32)) {
-				metric = u32;
-				gateway_str = NULL;
-			} else {
-				if (!info->error) {
-					handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-					             _("ignoring invalid gateway '%s' for %s route"),
-					             gateway_str, family == AF_INET ? "IPv4" : "IPv6");
-				}
-				return NULL;
-			}
-		}
-	} else
-		gateway_str = NULL;
-
-	/* parse metric, default to -1 */
-	if (metric_str) {
-		if (!get_one_int (info, property_name, metric_str, G_MAXUINT32, &u32))
-			return NULL;
-		metric = u32;
-	}
-
-	route = nm_ip_route_new (family,
-	                         dest_str,
-	                         plen,
-	                         gateway_str,
-	                         metric,
-	                         &error);
-	if (!route) {
-		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("ignoring invalid %s route: %s"),
-		             family == AF_INET ? "IPv4" : "IPv6",
-		             error->message);
-		g_error_free (error);
-	}
-
-	return route;
-}
-
-/* On success, returns pointer to the zero-terminated field (original @current).
- * The @current * pointer target is set to point to the rest of the input
- * or %NULL if there is no more input. Sets error to %NULL for convenience.
- *
- * On failure, returns %NULL (unspecified). The @current pointer target is
- * resets to its original value to allow skipping fields. The @error target
- * is set to the character that breaks the parsing or %NULL if @current was %NULL.
- *
- * When @current target is %NULL, gracefully fail returning %NULL while
- * leaving the @current target %NULL end setting @error to %NULL;
- */
-static const char *
-read_field (char **current, const char **out_err_str, const char *characters, const char *delimiters)
-{
-	const char *start;
-
-	nm_assert (current);
-	nm_assert (out_err_str);
-	nm_assert (characters);
-	nm_assert (delimiters);
-
-	*out_err_str = NULL;
-
-	if (!*current) {
-		/* graceful failure, leave '*current' NULL */
-		return NULL;
-	}
-
-	/* fail on empty input */
-	if (!**current)
-		return NULL;
-
-	/* remember beginning of input */
-	start = *current;
-
-	while (**current && strchr (characters, **current))
-		(*current)++;
-	if (**current)
-		if (strchr (delimiters, **current)) {
-			/* success, more data available */
-			*(*current)++ = '\0';
-			return start;
-		} else {
-			/* error, bad character */
-			*out_err_str = *current;
-			*current = (char *) start;
-			return NULL;
-		}
-	else {
-		/* success, end of input */
-		*current = NULL;
-		return start;
-	}
-}
-
-/*****************************************************************************/
-
-#define NM_DBUS_SERVICE_OPENCONNECT    "org.freedesktop.NetworkManager.openconnect"
-#define NM_OPENCONNECT_KEY_GATEWAY     "gateway"
-#define NM_OPENCONNECT_KEY_COOKIE      "cookie"
-#define NM_OPENCONNECT_KEY_GWCERT      "gwcert"
-#define NM_OPENCONNECT_KEY_XMLCONFIG   "xmlconfig"
-#define NM_OPENCONNECT_KEY_LASTHOST    "lasthost"
-#define NM_OPENCONNECT_KEY_AUTOCONNECT "autoconnect"
-#define NM_OPENCONNECT_KEY_CERTSIGS    "certsigs"
-
-static void
-openconnect_fix_secret_flags (NMSetting *setting)
-{
-	NMSettingVpn *s_vpn;
-	NMSettingSecretFlags flags;
-
-	/* Huge hack.  There were some openconnect changes that needed to happen
-	 * pretty late, too late to get into distros.  Migration has already
-	 * happened for many people, and their secret flags are wrong.  But we
-	 * don't want to requrie re-migration, so we have to fix it up here. Ugh.
-	 */
-
-	if (!NM_IS_SETTING_VPN (setting))
-		return;
-
-	s_vpn = NM_SETTING_VPN (setting);
-
-	if (!nm_streq0 (nm_setting_vpn_get_service_type (s_vpn), NM_DBUS_SERVICE_OPENCONNECT))
-		return;
-
-	/* These are different for every login session, and should not be stored */
-	flags = NM_SETTING_SECRET_FLAG_NOT_SAVED;
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GATEWAY, flags, NULL);
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_COOKIE, flags, NULL);
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GWCERT, flags, NULL);
-
-	/* These are purely internal data for the auth-dialog, and should be stored */
-	flags = 0;
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_XMLCONFIG, flags, NULL);
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_LASTHOST, flags, NULL);
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_AUTOCONNECT, flags, NULL);
-	nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_CERTSIGS, flags, NULL);
-}
-
-/*****************************************************************************/
-
-#define IP_ADDRESS_CHARS "0123456789abcdefABCDEF:.%"
-#define DIGITS "0123456789"
-#define DELIMITERS "/;,"
-
-/* The following IPv4 and IPv6 address formats are supported:
- *
- * address (DEPRECATED)
- * address/plen
- * address/gateway (DEPRECATED)
- * address/plen,gateway
- *
- * The following IPv4 and IPv6 route formats are supported:
- *
- * address/plen (NETWORK dev DEVICE)
- * address/plen,gateway (NETWORK via GATEWAY dev DEVICE)
- * address/plen,,metric (NETWORK dev DEVICE metric METRIC)
- * address/plen,gateway,metric (NETWORK via GATEWAY dev DEVICE metric METRIC)
- *
- * For backward, forward and sideward compatibility, slash (/),
- * semicolon (;) and comma (,) are interchangeable. The choice of
- * separator in the above examples is therefore not significant.
- *
- * Leaving out the prefix length is discouraged and DEPRECATED. The
- * default value of IPv6 prefix length was 64 and has not been
- * changed. The default for IPv4 is now 24, which is the closest
- * IPv4 equivalent. These defaults may just as well be changed to
- * match the iproute2 defaults (32 for IPv4 and 128 for IPv6).
- */
-static gpointer
-read_one_ip_address_or_route (KeyfileReaderInfo *info,
-                              const char *property_name,
-                              const char *setting_name,
-                              const char *key_name,
-                              gboolean ipv6,
-                              gboolean route,
-                              char **out_gateway,
-                              NMSetting *setting)
-{
-	guint plen;
-	gpointer result;
-	const char *address_str;
-	const char *plen_str;
-	const char *gateway_str;
-	const char *metric_str;
-	const char *err_str = NULL;
-	char *current;
-	gs_free char *value = NULL;
-	gs_free char *value_orig = NULL;
-
-#define VALUE_ORIG()   (value_orig ?: (value_orig = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL)))
-
-	value = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key_name, NULL);
-	if (!value)
-		return NULL;
-
-	current = value;
-
-	/* get address field */
-	address_str = read_field (&current, &err_str, IP_ADDRESS_CHARS, DELIMITERS);
-	if (err_str) {
-		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("unexpected character '%c' for address %s: '%s' (position %td)"),
-		             *err_str, key_name, VALUE_ORIG (), err_str - current);
-		return NULL;
-	}
-	/* get prefix length field (skippable) */
-	plen_str = read_field (&current, &err_str, DIGITS, DELIMITERS);
-	/* get gateway field */
-	gateway_str = read_field (&current, &err_str, IP_ADDRESS_CHARS, DELIMITERS);
-	if (err_str) {
-		handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("unexpected character '%c' for %s: '%s' (position %td)"),
-		             *err_str, key_name, VALUE_ORIG (), err_str - current);
-		return NULL;
-	}
-	/* for routes, get metric */
-	if (route) {
-		metric_str = read_field (&current, &err_str, DIGITS, DELIMITERS);
-		if (err_str) {
-			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("unexpected character '%c' in prefix length for %s: '%s' (position %td)"),
-			             *err_str, key_name, VALUE_ORIG (), err_str - current);
-			return NULL;
-		}
-	} else
-		metric_str = NULL;
-	if (current) {
-		/* there is still some data */
-		if (*current) {
-			/* another field follows */
-			handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("garbage at the end of value %s: '%s'"),
-			             key_name, VALUE_ORIG ());
-			return NULL;
-		} else {
-			/* semicolon at the end of input */
-			if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_INFO,
-			                  _("deprecated semicolon at the end of value %s: '%s'"),
-			                  key_name, VALUE_ORIG ()))
-				return NULL;
-		}
-	}
-
-#define DEFAULT_PREFIX(for_route, for_ipv6) ( (for_route) ? ( (for_ipv6) ? 128 : 24 ) : ( (for_ipv6) ? 64 : 24 ) )
-
-	/* parse plen, fallback to defaults */
-	if (plen_str) {
-		if (!get_one_int (info, property_name, plen_str, ipv6 ? 128 : 32, &plen)) {
-			plen = DEFAULT_PREFIX (route, ipv6);
-			if (   info->error
-			    || !handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-			                     _("invalid prefix length for %s '%s', defaulting to %d"),
-			                     key_name, VALUE_ORIG (), plen))
-				return NULL;
-		}
-	} else {
-		plen = DEFAULT_PREFIX (route, ipv6);
-		if (!handle_warn (info, property_name, NM_KEYFILE_WARN_SEVERITY_WARN,
-		                  _("missing prefix length for %s '%s', defaulting to %d"),
-		                  key_name, VALUE_ORIG (), plen))
-			return NULL;
-	}
-
-	/* build the appropriate data structure for NetworkManager settings */
-	if (route) {
-		result = build_route (info,
-		                      property_name,
-		                      ipv6 ? AF_INET6 : AF_INET,
-		                      address_str,
-		                      plen,
-		                      gateway_str,
-		                      metric_str);
-	} else {
-		result = build_address (info,
-		                        ipv6 ? AF_INET6 : AF_INET,
-		                        address_str,
-		                        plen,
-		                        property_name);
-		if (!result)
-			return NULL;
-		if (gateway_str)
-			NM_SET_OUT (out_gateway, g_strdup (gateway_str));
-	}
-
-#undef VALUE_ORIG
-
-	return result;
-}
-
-static void
-fill_route_attributes (GKeyFile *kf, NMIPRoute *route, const char *setting, const char *key, int family)
-{
-	gs_free char *value = NULL;
-	gs_unref_hashtable GHashTable *hash = NULL;
-	GHashTableIter iter;
-	char *name;
-	GVariant *variant;
-
-	value = nm_keyfile_plugin_kf_get_string (kf, setting, key, NULL);
-	if (!value || !value[0])
-		return;
-
-	hash = nm_utils_parse_variant_attributes (value, ',', '=', TRUE,
-	                                          nm_ip_route_get_variant_attribute_spec (),
-	                                          NULL);
-	if (hash) {
-		g_hash_table_iter_init (&iter, hash);
-		while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &variant)) {
-			if (nm_ip_route_attribute_validate (name, variant, family, NULL, NULL))
-				nm_ip_route_set_attribute (route, name, g_variant_ref (variant));
-		}
-	}
-}
-
-typedef struct {
-	const char *s_key;
-	gint32 key_idx;
-	gint8 key_type;
-} BuildListData;
-
-typedef enum {
-	BUILD_LIST_TYPE_ADDRESSES,
-	BUILD_LIST_TYPE_ROUTES,
-	BUILD_LIST_TYPE_ROUTING_RULES,
-} BuildListType;
-
-static int
-_build_list_data_cmp (gconstpointer p_a, gconstpointer p_b, gpointer user_data)
-{
-	const BuildListData *a = p_a;
-	const BuildListData *b = p_b;
-
-	NM_CMP_FIELD (a, b, key_idx);
-	NM_CMP_FIELD (a, b, key_type);
-	NM_CMP_FIELD_STR (a, b, s_key);
-	return 0;
-}
-
-static gboolean
-_build_list_data_is_shadowed (const BuildListData *build_list,
-                              gsize build_list_len,
-                              gsize idx)
-{
-	/* the keyfile contains duplicate keys, which are both returned
-	 * by g_key_file_get_keys() (WHY??).
-	 *
-	 * Skip the earlier one. */
-	return    idx + 1 < build_list_len
-	       && build_list[idx].key_idx == build_list[idx + 1].key_idx
-	       && build_list[idx].key_type == build_list[idx + 1].key_type
-	       && nm_streq (build_list[idx].s_key, build_list[idx + 1].s_key);
-}
-
-static gboolean
-_build_list_match_key_w_name_impl (const char *key,
-                                   const char *base_name,
-                                   gsize base_name_l,
-                                   gint32 *out_key_idx)
-{
-	gint64 v;
-
-	/* some very strict parsing. */
-
-	/* the key must start with base_name. */
-	if (strncmp (key, base_name, base_name_l) != 0)
-		return FALSE;
-
-	key += base_name_l;
-	if (key[0] == '\0') {
-		/* if key is identical to base_name, that's good. */
-		NM_SET_OUT (out_key_idx, -1);
-		return TRUE;
-	}
-
-	/* if base_name is followed by a zero, then it must be
-	 * only a zero, nothing else. */
-	if (key[0] == '0') {
-		if (key[1] != '\0')
-			return FALSE;
-		NM_SET_OUT (out_key_idx, 0);
-		return TRUE;
-	}
-
-	/* otherwise, it can only be followed by a non-zero decimal. */
-	if (!(key[0] >= '1' && key[0] <= '9'))
-		return FALSE;
-	/* and all remaining chars must be decimals too. */
-	if (!NM_STRCHAR_ALL (&key[1], ch, g_ascii_isdigit (ch)))
-		return FALSE;
-
-	/* and it must be convertible to a (positive) int. */
-	v = _nm_utils_ascii_str_to_int64 (key, 10, 0, G_MAXINT32, -1);
-	if (v < 0)
-		return FALSE;
-
-	/* good */
-	NM_SET_OUT (out_key_idx, v);
-	return TRUE;
-}
-
-#define _build_list_match_key_w_name(key, base_name, out_key_idx) \
-	_build_list_match_key_w_name_impl (key, base_name, NM_STRLEN (base_name), out_key_idx)
-
-static BuildListData *
-_build_list_create (GKeyFile *keyfile,
-                    const char *group_name,
-                    BuildListType build_list_type,
-                    gsize *out_build_list_len,
-                    char ***out_keys_strv)
-{
-	gs_strfreev char **keys = NULL;
-	gsize i_keys, n_keys;
-	gs_free BuildListData *build_list = NULL;
-	gsize build_list_len = 0;
-
-	nm_assert (out_build_list_len && *out_build_list_len == 0);
-	nm_assert (out_keys_strv && !*out_keys_strv);
-
-	keys = nm_keyfile_plugin_kf_get_keys (keyfile, group_name, &n_keys, NULL);
-	if (n_keys == 0)
-		return NULL;
-
-	for (i_keys = 0; i_keys < n_keys; i_keys++) {
-		const char *s_key = keys[i_keys];
-		gint32 key_idx;
-		gint8 key_type = 0;
-
-		switch (build_list_type) {
-		case BUILD_LIST_TYPE_ROUTES:
-			if (_build_list_match_key_w_name (s_key, "route", &key_idx))
-				key_type = 0;
-			else if (_build_list_match_key_w_name (s_key, "routes", &key_idx))
-				key_type = 1;
-			else
-				continue;
-			break;
-		case BUILD_LIST_TYPE_ADDRESSES:
-			if (_build_list_match_key_w_name (s_key, "address", &key_idx))
-				key_type = 0;
-			else if (_build_list_match_key_w_name (s_key, "addresses", &key_idx))
-				key_type = 1;
-			else
-				continue;
-			break;
-		case BUILD_LIST_TYPE_ROUTING_RULES:
-			if (_build_list_match_key_w_name (s_key, "routing-rule", &key_idx))
-				key_type = 0;
-			else
-				continue;
-			break;
-		default:
-			nm_assert_not_reached ();
-			break;
-		}
-
-		if (G_UNLIKELY (!build_list))
-			build_list = g_new (BuildListData, n_keys - i_keys);
-
-		build_list[build_list_len++] = (BuildListData) {
-			.s_key    = s_key,
-			.key_idx  = key_idx,
-			.key_type = key_type,
-		};
-	}
-
-	if (build_list_len == 0)
-		return NULL;
-
-	if (build_list_len > 1) {
-		g_qsort_with_data (build_list,
-		                   build_list_len,
-		                   sizeof (BuildListData),
-		                   _build_list_data_cmp,
-		                   NULL);
-	}
-
-	*out_build_list_len = build_list_len;
-	*out_keys_strv = g_steal_pointer (&keys);
-	return g_steal_pointer (&build_list);
-}
-
-static void
-ip_address_or_route_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *setting_key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gboolean is_ipv6 = nm_streq (setting_name, "ipv6");
-	gboolean is_routes = nm_streq (setting_key, "routes");
-	gs_free char *gateway = NULL;
-	gs_unref_ptrarray GPtrArray *list = NULL;
-	gs_strfreev char **keys = NULL;
-	gs_free BuildListData *build_list = NULL;
-	gsize i_build_list, build_list_len = 0;
-
-	build_list = _build_list_create (info->keyfile,
-	                                 setting_name,
-	                                   is_routes
-	                                 ? BUILD_LIST_TYPE_ROUTES
-	                                 : BUILD_LIST_TYPE_ADDRESSES,
-	                                 &build_list_len,
-	                                 &keys);
-	if (!build_list)
-		return;
-
-	list = g_ptr_array_new_with_free_func (is_routes
-	                                       ? (GDestroyNotify) nm_ip_route_unref
-	                                       : (GDestroyNotify) nm_ip_address_unref);
-
-	for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) {
-		const char *s_key;
-		gpointer item;
-
-		if (_build_list_data_is_shadowed (build_list, build_list_len, i_build_list))
-			continue;
-
-		s_key = build_list[i_build_list].s_key;
-		item = read_one_ip_address_or_route (info,
-		                                     setting_key,
-		                                     setting_name,
-		                                     s_key,
-		                                     is_ipv6,
-		                                     is_routes,
-		                                     gateway ? NULL : &gateway,
-		                                     setting);
-		if (item && is_routes) {
-			char options_key[128];
-
-			nm_sprintf_buf (options_key, "%s_options", s_key);
-			fill_route_attributes (info->keyfile,
-			                       item,
-			                       setting_name,
-			                       options_key,
-			                       is_ipv6 ? AF_INET6 : AF_INET);
-		}
-
-		if (info->error)
-			return;
-
-		if (item)
-			g_ptr_array_add (list, item);
-	}
-
-	if (list->len >= 1)
-		g_object_set (setting, setting_key, list, NULL);
-
-	if (gateway)
-		g_object_set (setting, "gateway", gateway, NULL);
-}
-
-static void
-ip_routing_rule_parser_full (KeyfileReaderInfo *info,
-                             const NMMetaSettingInfo *setting_info,
-                             const NMSettInfoProperty *property_info,
-                             const ParseInfoProperty *pip,
-                             NMSetting *setting)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gboolean is_ipv6 = nm_streq (setting_name, "ipv6");
-	gs_strfreev char **keys = NULL;
-	gs_free BuildListData *build_list = NULL;
-	gsize i_build_list, build_list_len = 0;
-
-	build_list = _build_list_create (info->keyfile,
-	                                 setting_name,
-	                                 BUILD_LIST_TYPE_ROUTING_RULES,
-	                                 &build_list_len,
-	                                 &keys);
-	if (!build_list)
-		return;
-
-	for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) {
-		nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL;
-		gs_free char *value = NULL;
-		gs_free_error GError *local = NULL;
-
-		if (_build_list_data_is_shadowed (build_list, build_list_len, i_build_list))
-			continue;
-
-		value = nm_keyfile_plugin_kf_get_string (info->keyfile,
-		                                         setting_name,
-		                                         build_list[i_build_list].s_key,
-		                                         NULL);
-		if (!value)
-			continue;
-
-		rule = nm_ip_routing_rule_from_string (value,
-		                                       (  NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE
-		                                        | (  is_ipv6
-		                                           ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6
-		                                           : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET)),
-		                                       NULL,
-		                                       &local);
-		if (!rule) {
-			handle_warn (info, property_info->name, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid value for \"%s\": %s"),
-			             build_list[i_build_list].s_key,
-			             local->message);
-			if (info->error)
-				return;
-			continue;
-		}
-
-		nm_setting_ip_config_add_routing_rule (NM_SETTING_IP_CONFIG (setting), rule);
-	}
-}
-
-static void
-ip_dns_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	int addr_family;
-	gs_strfreev char **list = NULL;
-	gsize i, n, length;
-
-	nm_assert (NM_IS_SETTING_IP4_CONFIG (setting) || NM_IS_SETTING_IP6_CONFIG (setting));
-
-	list = nm_keyfile_plugin_kf_get_string_list (info->keyfile,
-	                                             nm_setting_get_name (setting),
-	                                             key,
-	                                             &length,
-	                                             NULL);
-	nm_assert (length == NM_PTRARRAY_LEN (list));
-	if (length == 0)
-		return;
-
-	addr_family = NM_IS_SETTING_IP4_CONFIG (setting) ? AF_INET : AF_INET6;
-
-	n = 0;
-	for (i = 0; i < length; i++) {
-		NMIPAddr addr;
-
-		if (inet_pton (addr_family, list[i], &addr) <= 0) {
-			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			                  _("ignoring invalid DNS server IPv%c address '%s'"),
-			                  nm_utils_addr_family_to_char (addr_family),
-			                  list[i])) {
-				do {
-					nm_clear_g_free (&list[i]);
-				} while (++i < length);
-				return;
-			}
-			nm_clear_g_free (&list[i]);
-			continue;
-		}
-
-		if (n != i)
-			list[n] = g_steal_pointer (&list[i]);
-		n++;
-	}
-
-	g_object_set (setting, key, list, NULL);
-}
-
-static void
-ip6_addr_gen_mode_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	NMSettingIP6ConfigAddrGenMode addr_gen_mode;
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_free char *s = NULL;
-
-	s = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
-	if (s) {
-		if (!nm_utils_enum_from_str (nm_setting_ip6_config_addr_gen_mode_get_type (), s,
-		                             (int *) &addr_gen_mode, NULL)) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid option '%s', use one of [%s]"),
-			             s, "eui64,stable-privacy");
-			return;
-		}
-	} else
-		addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64;
-
-	g_object_set (G_OBJECT (setting), key, (int) addr_gen_mode, NULL);
-}
-
-static void
-mac_address_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key, gsize enforce_length, gboolean cloned_mac_addr)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_free char *tmp_string = NULL;
-	const char *p, *mac_str;
-	gs_free guint8 *buf_arr = NULL;
-	guint buf_len = 0;
-
-	tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
-
-	if (   cloned_mac_addr
-	    && NM_CLONED_MAC_IS_SPECIAL (tmp_string)) {
-		mac_str = tmp_string;
-		goto out;
-	}
-
-	if (tmp_string && tmp_string[0]) {
-		/* Look for enough ':' characters to signify a MAC address */
-		guint i = 0;
-
-		p = tmp_string;
-		while (*p) {
-			if (*p == ':')
-				i++;
-			p++;
-		}
-
-		if (enforce_length == 0 || enforce_length == i+1) {
-			/* If we found enough it's probably a string-format MAC address */
-			buf_len = i + 1;
-			buf_arr = g_new (guint8, buf_len);
-			if (!nm_utils_hwaddr_aton (tmp_string, buf_arr, buf_len))
-				g_clear_pointer (&buf_arr, g_free);
-		}
-	}
-	g_clear_pointer (&tmp_string, g_free);
-
-	if (!buf_arr) {
-		gs_free int *tmp_list = NULL;
-		gsize length;
-
-		/* Old format; list of ints */
-		tmp_list = nm_keyfile_plugin_kf_get_integer_list (info->keyfile, setting_name, key, &length, NULL);
-		if (length > 0 && (enforce_length == 0 || enforce_length == length)) {
-			gsize i;
-
-			buf_len = length;
-			buf_arr = g_new (guint8, buf_len);
-			for (i = 0; i < length; i++) {
-				int val = tmp_list[i];
-
-				if (val < 0 || val > 255) {
-					handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-					             _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"),
-					             val);
-					return;
-				}
-				buf_arr[i] = (guint8) val;
-			}
-		}
-	}
-
-	if (!buf_arr) {
-		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("ignoring invalid MAC address"));
-		return;
-	}
-
-	tmp_string = nm_utils_hwaddr_ntoa (buf_arr, buf_len);
-	mac_str = tmp_string;
-
-out:
-	g_object_set (setting, key, mac_str, NULL);
-}
-
-static void
-mac_address_parser_ETHER (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	mac_address_parser (info, setting, key, ETH_ALEN, FALSE);
-}
-
-static void
-mac_address_parser_ETHER_cloned (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	mac_address_parser (info, setting, key, ETH_ALEN, TRUE);
-}
-
-static void
-mac_address_parser_INFINIBAND (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	mac_address_parser (info, setting, key, INFINIBAND_ALEN, FALSE);
-}
-
-static void
-read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
-{
-	gs_strfreev char **keys = NULL;
-	const char *const*iter;
-	const char *setting_name = nm_setting_get_name (setting);
-	gboolean is_vpn;
-	gsize n_keys;
-
-	nm_assert (   (NM_IS_SETTING_VPN (setting)  && nm_streq (key, NM_SETTING_VPN_DATA))
-	           || (NM_IS_SETTING_VPN (setting)  && nm_streq (key, NM_SETTING_VPN_SECRETS))
-	           || (NM_IS_SETTING_BOND (setting) && nm_streq (key, NM_SETTING_BOND_OPTIONS))
-	           || (NM_IS_SETTING_USER (setting) && nm_streq (key, NM_SETTING_USER_DATA)));
-
-	keys = nm_keyfile_plugin_kf_get_keys (file, setting_name, &n_keys, NULL);
-	if (n_keys == 0)
-		return;
-
-	if (   (is_vpn = NM_IS_SETTING_VPN (setting))
-	    || NM_IS_SETTING_BOND (setting)) {
-		for (iter = (const char *const*) keys; *iter; iter++) {
-			gs_free char *to_free = NULL;
-			gs_free char *value = NULL;
-			const char *name;
-
-			value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL);
-			if (!value)
-				continue;
-
-			name = nm_keyfile_key_decode (*iter, &to_free);
-
-			if (is_vpn) {
-				/* Add any item that's not a class property to the data hash */
-				if (!g_object_class_find_property (G_OBJECT_GET_CLASS (setting), name))
-					nm_setting_vpn_add_data_item (NM_SETTING_VPN (setting), name, value);
-			} else {
-				if (!nm_streq (name, "interface-name"))
-					nm_setting_bond_add_option (NM_SETTING_BOND (setting), name, value);
-			}
-		}
-		openconnect_fix_secret_flags (setting);
-		return;
-	}
-
-	if (NM_IS_SETTING_USER (setting)) {
-		gs_unref_hashtable GHashTable *data = NULL;
-
-		data = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
-		for (iter = (const char *const*) keys; *iter; iter++) {
-			gs_free char *to_free = NULL;
-			char *value = NULL;
-			const char *name;
-
-			value = nm_keyfile_plugin_kf_get_string (file, setting_name, *iter, NULL);
-			if (!value)
-				continue;
-			name = nm_keyfile_key_decode (*iter, &to_free);
-			g_hash_table_insert (data,
-			                     g_steal_pointer (&to_free) ?: g_strdup (name),
-			                     value);
-		}
-		g_object_set (setting, NM_SETTING_USER_DATA, data, NULL);
-		return;
-	}
-
-	nm_assert_not_reached ();
-}
-
-static gsize
-unescape_semicolons (char *str)
-{
-	gsize i, j;
-
-	for (i = 0, j = 0; str[i]; ) {
-		if (str[i] == '\\' && str[i+1] == ';')
-			i++;
-		str[j++] = str[i++];;
-	}
-	nm_explicit_bzero (&str[j], i - j);
-	return j;
-}
-
-static GBytes *
-get_bytes (KeyfileReaderInfo *info,
-           const char *setting_name,
-           const char *key,
-           gboolean zero_terminate,
-           gboolean unescape_semicolon)
-{
-	nm_auto_free_secret char *tmp_string = NULL;
-	gboolean may_be_int_list = TRUE;
-	gsize length;
-	GBytes *result;
-
-	/* New format: just a string
-	 * Old format: integer list; e.g. 11;25;38;
-	 */
-	tmp_string = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
-	if (!tmp_string)
-		return NULL;
-
-	/* if the string is empty, we return an empty GBytes array.
-	 * Note that for NM_SETTING_802_1X_PASSWORD_RAW both %NULL and
-	 * an empty GBytes are valid, and shall be destinguished. */
-	if (!tmp_string[0]) {
-		/* note that even if @zero_terminate is TRUE, we return an empty
-		 * byte-array. The reason is that zero_terminate is there to terminate
-		 * *valid* strings. It's not there to terminated invalid (empty) strings.
-		 */
-		return g_bytes_new_static ("", 0);
-	}
-
-	for (length = 0; tmp_string[length]; length++) {
-		const char ch = tmp_string[length];
-
-		if (   !g_ascii_isspace (ch)
-		    && !g_ascii_isdigit (ch)
-		    && ch != ';') {
-			may_be_int_list = FALSE;
-			length += strlen (&tmp_string[length]);
-			break;
-		}
-	}
-
-	/* Try to parse the string as a integer list. */
-	if (may_be_int_list && length > 0) {
-		nm_auto_free_secret_buf NMSecretBuf *bin = NULL;
-		const char *const s = tmp_string;
-		gsize i, d;
-
-		bin = nm_secret_buf_new (length / 2 + 3);
-
-#define DIGIT(c) ((c) - '0')
-		i = 0;
-		d = 0;
-		while (TRUE) {
-			int n;
-
-			/* leading whitespace */
-			while (g_ascii_isspace (s[i]))
-				i++;
-			if (s[i] == '\0')
-				break;
-			/* then expect 1 to 3 digits */
-			if (!g_ascii_isdigit (s[i])) {
-				d = 0;
-				break;
-			}
-			n = DIGIT (s[i]);
-			i++;
-			if (g_ascii_isdigit (s[i])) {
-				n = 10 * n + DIGIT (s[i]);
-				i++;
-				if (g_ascii_isdigit (s[i])) {
-					n = 10 * n + DIGIT (s[i]);
-					i++;
-				}
-			}
-			if (n > 255) {
-				d = 0;
-				break;
-			}
-
-			nm_assert (d < bin->len);
-			bin->bin[d++] = n;
-
-			/* allow whitespace after the digit. */
-			while (g_ascii_isspace (s[i]))
-				i++;
-			/* need a semicolon as separator. */
-			if (s[i] != ';') {
-				d = 0;
-				break;
-			}
-			i++;
-		}
-#undef DIGIT
-
-		/* Old format; list of ints. We already did a strict validation of the
-		 * string format before. We expect that this conversion cannot fail. */
-		if (d > 0) {
-			/* note that @zero_terminate does not add a terminating '\0' to
-			 * binary data as an integer list. If the bytes are expressed as
-			 * an integer list, all potential NUL characters are supposed to
-			 * be included there explicitly.
-			 *
-			 * However, in the spirit of defensive programming, we do append a
-			 * NUL character to the buffer, although this character is hidden
-			 * and only a mitigation for bugs. */
-
-			if (d + 10 < bin->len) {
-				/* hm, too much unused memory. Copy the memory to a suitable
-				 * sized buffer. */
-				return nm_secret_copy_to_gbytes (bin->bin, d);
-			}
-
-			nm_assert (d < bin->len);
-			bin->bin[d] = '\0';
-			return nm_secret_buf_to_gbytes_take (g_steal_pointer (&bin), d);
-		}
-	}
-
-	/* Handle as a simple string (ie, new format) */
-	if (unescape_semicolon)
-		length = unescape_semicolons (tmp_string);
-	if (zero_terminate)
-		length++;
-	if (length == 0)
-		return NULL;
-
-	result = g_bytes_new_with_free_func (tmp_string,
-	                                     length,
-	                                     (GDestroyNotify) nm_free_secret,
-	                                     tmp_string);
-	tmp_string = NULL;
-	return result;
-}
-
-static void
-ssid_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	GBytes *bytes;
-
-	bytes = get_bytes (info, setting_name, key, FALSE, TRUE);
-	if (bytes) {
-		g_object_set (setting, key, bytes, NULL);
-		g_bytes_unref (bytes);
-	} else if (!info->error) {
-		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("ignoring invalid SSID"));
-	}
-}
-
-static void
-password_raw_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	GBytes *bytes;
-
-	bytes = get_bytes (info, setting_name, key, FALSE, TRUE);
-	if (bytes) {
-		g_object_set (setting, key, bytes, NULL);
-		g_bytes_unref (bytes);
-	} else if (!info->error) {
-		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("ignoring invalid raw password"));
-	}
-}
-
-static char *
-get_cert_path (const char *base_dir, const guint8 *cert_path, gsize cert_path_len)
-{
-	const char *base;
-	char *p = NULL, *path, *tmp;
-
-	g_return_val_if_fail (base_dir != NULL, NULL);
-	g_return_val_if_fail (cert_path != NULL, NULL);
-
-	path = g_strndup ((char *) cert_path, cert_path_len);
-
-	if (path[0] == '/')
-		return path;
-
-	base = path;
-	p = strrchr (path, '/');
-	if (p)
-		base = p + 1;
-
-	tmp = g_build_path ("/", base_dir, base, NULL);
-	g_free (path);
-	return tmp;
-}
-
-static const char *certext[] = { ".pem", ".cert", ".crt", ".cer", ".p12", ".der", ".key" };
-
-static gboolean
-has_cert_ext (const char *path)
-{
-	int i;
-
-	for (i = 0; i < G_N_ELEMENTS (certext); i++) {
-		if (g_str_has_suffix (path, certext[i]))
-			return TRUE;
-	}
-	return FALSE;
-}
-
-char *
-nm_keyfile_detect_unqualified_path_scheme (const char *base_dir,
-                                           gconstpointer pdata,
-                                           gsize data_len,
-                                           gboolean consider_exists,
-                                           gboolean *out_exists)
-{
-	const char *data = pdata;
-	gboolean exists = FALSE;
-	gsize validate_len;
-	gsize path_len, pathuri_len;
-	gs_free char *path = NULL;
-	gs_free char *pathuri = NULL;
-
-	g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL);
-
-	if (!pdata)
-		return NULL;
-	if (data_len == -1)
-		data_len = strlen (data);
-	if (data_len > 500 || data_len < 1)
-		return NULL;
-
-	/* If there's a trailing zero tell g_utf8_validate() to validate until the zero */
-	if (data[data_len - 1] == '\0') {
-		/* setting it to -1, would mean we accept data to contain NUL characters before the
-		 * end. Don't accept any NUL in [0 .. data_len-1[ . */
-		validate_len = data_len - 1;
-	} else
-		validate_len = data_len;
-	if (   validate_len == 0
-	    || g_utf8_validate ((const char *) data, validate_len, NULL) == FALSE)
-		 return NULL;
-
-	/* Might be a bare path without the file:// prefix; in that case
-	 * if it's an absolute path, use that, otherwise treat it as a
-	 * relative path to the current directory.
-	 */
-
-	path = get_cert_path (base_dir, (const guint8 *) data, data_len);
-
-	/* FIXME(keyfile-parse-in-memory): it is wrong that keyfile reader makes decisions based on
-	 * the file systems content. The serialization/parsing should be entirely in-memory. */
-	if (   !memchr (data, '/', data_len)
-	    && !has_cert_ext (path)) {
-		if (!consider_exists)
-			return NULL;
-		exists = g_file_test (path, G_FILE_TEST_EXISTS);
-		if (!exists)
-			return NULL;
-	} else if (out_exists)
-		exists = g_file_test (path, G_FILE_TEST_EXISTS);
-
-	/* Construct the proper value as required for the PATH scheme.
-	 *
-	 * When returning TRUE, we must also be sure that @data_len does not look like
-	 * the deprecated format of list of integers. With this implementation that is the
-	 * case, as long as @consider_exists is FALSE. */
-	path_len = strlen (path);
-	pathuri_len = (NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + 1) + path_len;
-	pathuri = g_new (char, pathuri_len);
-	memcpy (pathuri, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH));
-	memcpy (&pathuri[NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)], path, path_len + 1);
-	if (nm_setting_802_1x_check_cert_scheme (pathuri, pathuri_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PATH)
-		return NULL;
-
-	NM_SET_OUT (out_exists, exists);
-	return g_steal_pointer (&pathuri);
-}
-
-#define HAS_SCHEME_PREFIX(bin, bin_len, scheme) \
-	({ \
-		const char *const _bin = (bin); \
-		const gsize _bin_len = (bin_len); \
-		\
-		nm_assert (_bin && _bin_len > 0); \
-		\
-		(   _bin_len > NM_STRLEN (scheme) + 1 \
-		 && _bin[_bin_len - 1] == '\0' \
-		 && memcmp (_bin, scheme, NM_STRLEN (scheme)) == 0); \
-	})
-
-static void
-cert_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_unref_bytes GBytes *bytes = NULL;
-	const char *bin = NULL;
-	gsize bin_len = 0;
-	char *path;
-	gboolean path_exists;
-
-	bytes = get_bytes (info, setting_name, key, TRUE, FALSE);
-	if (bytes)
-		bin = g_bytes_get_data (bytes, &bin_len);
-	if (bin_len == 0) {
-		if (!info->error) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid key/cert value"));
-		}
-		return;
-	}
-
-	if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)) {
-		const char *path2 = &bin[NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)];
-		gs_free char *path2_free = NULL;
-
-		if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PATH) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid key/cert value path \"%s\""), bin);
-			return;
-		}
-
-		g_object_set (setting, key, bytes, NULL);
-
-		if (path2[0] != '/') {
-			/* we want to read absolute paths because we use keyfile as exchange
-			 * between different processes which might not have the same cwd. */
-			path2_free = get_cert_path (info->base_dir, (const guint8 *) path2,
-			                            bin_len - NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) - 1);
-			path2 = path2_free;
-		}
-
-		/* FIXME(keyfile-parse-in-memory): keyfile reader must not access the file system and
-		 * (in a first step) only operate in memory-only. If the presence of files should be checked,
-		 * then by invoking a callback (and possibly keyfile settings plugin would
-		 * collect the file names to be checked and check them later). */
-		if (!g_file_test (path2, G_FILE_TEST_EXISTS)) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
-			             _("certificate or key file '%s' does not exist"),
-			             path2);
-		}
-		return;
-	}
-
-	if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11)) {
-		if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid PKCS#11 URI \"%s\""), bin);
-			return;
-		}
-
-		g_object_set (setting, key, bytes, NULL);
-		return;
-	}
-
-	if (HAS_SCHEME_PREFIX (bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)) {
-		const char *cdata = bin + NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB);
-		gsize cdata_len = bin_len - NM_STRLEN (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB) - 1;
-		gs_free guchar *bin_decoded = NULL;
-		gsize bin_decoded_len = 0;
-		gsize i;
-		gboolean valid_base64;
-		gs_unref_bytes GBytes *val = NULL;
-
-		/* Let's be strict here. We expect valid base64, no funny stuff!!
-		 * We didn't write such invalid data ourselfes and refuse to read it as blob. */
-		if ((valid_base64 = (cdata_len % 4 == 0))) {
-			for (i = 0; i < cdata_len; i++) {
-				char c = cdata[i];
-
-				if (!(   (c >= 'a' && c <= 'z')
-				      || (c >= 'A' && c <= 'Z')
-				      || (c >= '0' && c <= '9')
-				      || (c == '+' || c == '/'))) {
-					if (c != '=' || i < cdata_len - 2)
-						valid_base64 = FALSE;
-					else {
-						for (; i < cdata_len; i++) {
-							if (cdata[i] != '=')
-								valid_base64 = FALSE;
-						}
-					}
-					break;
-				}
-			}
-		}
-		if (valid_base64)
-			bin_decoded = g_base64_decode (cdata, &bin_decoded_len);
-
-		if (bin_decoded_len == 0) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid key/cert value data:;base64, is not base64"));
-			return;
-		}
-
-		if (nm_setting_802_1x_check_cert_scheme (bin_decoded, bin_decoded_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) {
-			/* The blob probably starts with "file://". Setting the cert data will confuse NMSetting8021x.
-			 * In fact this is a limitation of NMSetting8021x which does not support setting blobs that start
-			 * with file://. Just warn and return TRUE to signal that we ~handled~ the setting. */
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid key/cert value data:;base64,file://"));
-			return;
-		}
-
-		val = g_bytes_new_take (g_steal_pointer (&bin_decoded), bin_decoded_len);
-		g_object_set (setting, key, val, NULL);
-		return;
-	}
-
-	/* If not, it might be a plain path */
-	path = nm_keyfile_detect_unqualified_path_scheme (info->base_dir, bin, bin_len, TRUE, &path_exists);
-	if (path) {
-		gs_unref_bytes GBytes *val = NULL;
-
-		/* Construct the proper value as required for the PATH scheme */
-		val = g_bytes_new_take (path, strlen (path) + 1);
-		g_object_set (setting, key, val, NULL);
-
-		/* Warn if the certificate didn't exist */
-		if (!path_exists) {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE,
-			             _("certificate or key file '%s' does not exist"),
-			             path);
-		}
-		return;
-	}
-
-	if (nm_setting_802_1x_check_cert_scheme (bin, bin_len, NULL) != NM_SETTING_802_1X_CK_SCHEME_BLOB) {
-		/* The blob probably starts with "file://" but contains invalid characters for a path.
-		 * Setting the cert data will confuse NMSetting8021x.
-		 * In fact, NMSetting8021x does not support setting such binary data, so just warn and
-		 * continue. */
-		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("invalid key/cert value is not a valid blob"));
-		return;
-	}
-
-	g_object_set (setting, key, bytes, NULL);
-}
-
-static int
-_parity_from_char (int ch)
-{
-#if NM_MORE_ASSERTS > 5
-	{
-		static char check = 0;
-
-		if (check == 0) {
-			nm_auto_unref_gtypeclass GEnumClass *klass = g_type_class_ref (NM_TYPE_SETTING_SERIAL_PARITY);
-			guint i;
-
-			check = 1;
-
-			/* In older versions, parity was G_TYPE_CHAR/gint8, and the character
-			 * value was stored as integer.
-			 * For example parity=69 equals parity=E, meaning NM_SETTING_SERIAL_PARITY_EVEN.
-			 *
-			 * That means, certain values are reserved. Assert that these numbers
-			 * are not reused when we extend NMSettingSerialParity enum.
-			 * Actually, since NM_SETTING_SERIAL_PARITY is g_param_spec_enum(),
-			 * we anyway cannot extend the enum without breaking API...
-			 *
-			 * [1] commit "a91e60902e libnm-core: make NMSettingSerial:parity an enum"
-			 * [2] https://cgit.freedesktop.org/NetworkManager/NetworkManager/commit/?id=a91e60902eabae1de93d61323dae6ac894b5d40f
-			 */
-			g_assert (G_IS_ENUM_CLASS (klass));
-			for (i = 0; i < klass->n_values; i++) {
-				const GEnumValue *v = &klass->values[i];
-				int num = v->value;
-
-				g_assert (_parity_from_char (num) == -1);
-				g_assert (!NM_IN_SET (num, 'e', 'E', 'o', 'O', 'n', 'N'));
-			}
-		}
-	}
-#endif
-
-	switch (ch) {
-	case 'E':
-	case 'e':
-		return NM_SETTING_SERIAL_PARITY_EVEN;
-	case 'O':
-	case 'o':
-		return NM_SETTING_SERIAL_PARITY_ODD;
-	case 'N':
-	case 'n':
-		return NM_SETTING_SERIAL_PARITY_NONE;
-	}
-
-	return -1;
-}
-
-static void
-parity_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_free_error GError *err = NULL;
-	int parity;
-	gs_free char *tmp_str = NULL;
-	gint64 i64;
-
-	/* Keyfile traditionally stored this as the ASCII value for 'E', 'o', or 'n'.
-	 * We now accept either that or the (case-insensitive) character itself (but
-	 * still always write it the old way, for backward compatibility).
-	 */
-	tmp_str = nm_keyfile_plugin_kf_get_value (info->keyfile, setting_name, key, &err);
-	if (err)
-		goto out_err;
-
-	if (   tmp_str
-	    && tmp_str[0] != '\0'
-	    && tmp_str[1] == '\0') {
-		/* the ASCII characters like 'E' are taken directly... */
-		parity = _parity_from_char (tmp_str[0]);
-		if (parity >= 0)
-			goto parity_good;
-	}
-
-	i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64);
-	if (   i64 != G_MININT64
-	    && errno == 0) {
-
-		if ((parity = _parity_from_char (i64)) >= 0) {
-			/* another oddity: the string is a valid number. However, if the numeric values
-			 * is one of the supported ASCII codes, accept it (like 69 for 'E').
-			 */
-			goto parity_good;
-		}
-
-		/* Finally, take the numeric value as is. */
-		parity = i64;
-		goto parity_good;
-	}
-
-	handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-	             _("invalid parity value '%s'"),
-	             tmp_str ?: "");
-	return;
-
-parity_good:
-	nm_g_object_set_property_enum (G_OBJECT (setting), key, NM_TYPE_SETTING_SERIAL_PARITY, parity, &err);
-
-out_err:
-	if (!err)
-		return;
-	if (   err->domain == G_KEY_FILE_ERROR
-	    && NM_IN_SET (err->code, G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
-	                             G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
-		/* ignore such errors. The key is not present. */
-		return;
-	}
-	handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-	             _("invalid setting: %s"), err->message);
-}
-
-static void
-team_config_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_free char *conf = NULL;
-	gs_free_error GError *error = NULL;
-
-	conf = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, key, NULL);
-
-	g_object_set (G_OBJECT (setting), key, conf, NULL);
-
-	if (   conf
-	    && !nm_setting_verify (setting, NULL, &error)) {
-		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("ignoring invalid team configuration: %s"),
-		             error->message);
-		g_object_set (G_OBJECT (setting), key, NULL, NULL);
-	}
-}
-
-static void
-bridge_vlan_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	gs_unref_ptrarray GPtrArray *vlans = NULL;
-	gs_free char *value = NULL;
-	gs_free const char **strv = NULL;
-	const char *const *iter;
-	GError *local = NULL;
-	NMBridgeVlan *vlan;
-
-	value = nm_keyfile_plugin_kf_get_string (info->keyfile,
-	                                         nm_setting_get_name (setting),
-	                                         key,
-	                                         NULL);
-	if (!value || !value[0])
-		return;
-
-	vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref);
-
-	strv = nm_utils_escaped_tokens_split (value, ",");
-	if (strv) {
-		for (iter = strv; *iter; iter++) {
-			vlan = nm_bridge_vlan_from_str (*iter, &local);
-			if (!vlan) {
-				handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-				             "invalid bridge VLAN: %s", local->message);
-				g_clear_error (&local);
-				continue;
-			}
-			g_ptr_array_add (vlans, vlan);
-		}
-	}
-
-	if (vlans->len > 0)
-		g_object_set (setting, key, vlans, NULL);
-}
-
-static void
-qdisc_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_unref_ptrarray GPtrArray *qdiscs = NULL;
-	gs_strfreev char **keys = NULL;
-	gsize n_keys = 0;
-	int i;
-
-	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
-	if (n_keys == 0)
-		return;
-
-	qdiscs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_qdisc_unref);
-
-	for (i = 0; i < n_keys; i++) {
-		NMTCQdisc *qdisc;
-		const char *qdisc_parent;
-		gs_free char *qdisc_rest = NULL;
-		gs_free char *qdisc_str = NULL;
-		gs_free_error GError *err = NULL;
-
-		if (!g_str_has_prefix (keys[i], "qdisc."))
-			continue;
-
-		qdisc_parent = keys[i] + sizeof ("qdisc.") - 1;
-		qdisc_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL);
-		qdisc_str = g_strdup_printf ("%s%s %s",
-		                             _nm_utils_parse_tc_handle (qdisc_parent, NULL) != TC_H_UNSPEC ? "parent " : "",
-		                             qdisc_parent,
-		                             qdisc_rest);
-
-		qdisc = nm_utils_tc_qdisc_from_str (qdisc_str, &err);
-		if (!qdisc) {
-			handle_warn (info, keys[i], NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid qdisc: %s"),
-			             err->message);
-		} else {
-			g_ptr_array_add (qdiscs, qdisc);
-		}
-	}
-
-	if (qdiscs->len >= 1)
-		g_object_set (setting, key, qdiscs, NULL);
-}
-
-static void
-tfilter_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	gs_unref_ptrarray GPtrArray *tfilters = NULL;
-	gs_strfreev char **keys = NULL;
-	gsize n_keys = 0;
-	int i;
-
-	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
-	if (n_keys == 0)
-		return;
-
-	tfilters = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_tfilter_unref);
-
-	for (i = 0; i < n_keys; i++) {
-		NMTCTfilter *tfilter;
-		const char *tfilter_parent;
-		gs_free char *tfilter_rest = NULL;
-		gs_free char *tfilter_str = NULL;
-		gs_free_error GError *err = NULL;
-
-		if (!g_str_has_prefix (keys[i], "tfilter."))
-			continue;
-
-		tfilter_parent = keys[i] + sizeof ("tfilter.") - 1;
-		tfilter_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL);
-		tfilter_str = g_strdup_printf ("%s%s %s",
-		                             _nm_utils_parse_tc_handle (tfilter_parent, NULL) != TC_H_UNSPEC ? "parent " : "",
-		                             tfilter_parent,
-		                             tfilter_rest);
-
-		tfilter = nm_utils_tc_tfilter_from_str (tfilter_str, &err);
-		if (!tfilter) {
-			handle_warn (info, keys[i], NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid tfilter: %s"),
-			             err->message);
-		} else {
-			g_ptr_array_add (tfilters, tfilter);
-		}
-	}
-
-	if (tfilters->len >= 1)
-		g_object_set (setting, key, tfilters, NULL);
-}
-
-/*****************************************************************************/
-
-/* Some setting properties also contain setting names, such as
- * NMSettingConnection's 'type' property (which specifies the base type of the
- * connection, eg ethernet or wifi) or the 802-11-wireless setting's
- * 'security' property which specifies whether or not the AP requires
- * encryption.  This function handles translating those properties' values
- * from the real setting name to the more-readable alias.
- */
-static void
-setting_alias_writer (KeyfileWriterInfo *info,
-                      NMSetting *setting,
-                      const char *key,
-                      const GValue *value)
-{
-	const char *str, *alias;
-
-	str = g_value_get_string (value);
-	alias = nm_keyfile_plugin_get_alias_for_setting_name (str);
-	nm_keyfile_plugin_kf_set_string (info->keyfile,
-	                                 nm_setting_get_name (setting),
-	                                 key,
-	                                 alias ?: str);
-}
-
-static void
-sriov_vfs_writer (KeyfileWriterInfo *info,
-                  NMSetting *setting,
-                  const char *key,
-                  const GValue *value)
-{
-	GPtrArray *vfs;
-	guint i;
-
-	vfs = g_value_get_boxed (value);
-	if (!vfs)
-		return;
-
-	for (i = 0; i < vfs->len; i++) {
-		const NMSriovVF *vf = vfs->pdata[i];
-		gs_free char *kf_value = NULL;
-		char kf_key[32];
-
-		kf_value = nm_utils_sriov_vf_to_str (vf, TRUE, NULL);
-		if (!kf_value)
-			continue;
-
-		nm_sprintf_buf (kf_key, "vf.%u", nm_sriov_vf_get_index (vf));
-
-		nm_keyfile_plugin_kf_set_string (info->keyfile,
-		                                 nm_setting_get_name (setting),
-		                                 kf_key,
-		                                 kf_value);
-	}
-}
-
-static void
-write_array_of_uint (GKeyFile *file,
-                     NMSetting *setting,
-                     const char *key,
-                     const GValue *value)
-{
-	GArray *array;
-	guint i;
-	gs_free int *tmp_array = NULL;
-
-	array = (GArray *) g_value_get_boxed (value);
-	if (!array || !array->len)
-		return;
-
-	g_return_if_fail (g_array_get_element_size (array) == sizeof (guint));
-
-	tmp_array = g_new (int, array->len);
-	for (i = 0; i < array->len; i++) {
-		guint v = g_array_index (array, guint, i);
-
-		if (v > G_MAXINT)
-			g_return_if_reached ();
-		tmp_array[i] = (int) v;
-	}
-
-	nm_keyfile_plugin_kf_set_integer_list (file, nm_setting_get_name (setting), key, tmp_array, array->len);
-}
-
-static void
-dns_writer (KeyfileWriterInfo *info,
-            NMSetting *setting,
-            const char *key,
-            const GValue *value)
-{
-	char **list;
-
-	list = g_value_get_boxed (value);
-	if (list && list[0]) {
-		nm_keyfile_plugin_kf_set_string_list (info->keyfile, nm_setting_get_name (setting), key,
-		                                      (const char **) list, g_strv_length (list));
-	}
-}
-
-static void
-ip6_addr_gen_mode_writer (KeyfileWriterInfo *info,
-                          NMSetting *setting,
-                          const char *key,
-                          const GValue *value)
-{
-	NMSettingIP6ConfigAddrGenMode addr_gen_mode;
-	gs_free char *str = NULL;
-
-	addr_gen_mode = (NMSettingIP6ConfigAddrGenMode) g_value_get_int (value);
-	str = nm_utils_enum_to_str (nm_setting_ip6_config_addr_gen_mode_get_type (),
-	                            addr_gen_mode);
-	nm_keyfile_plugin_kf_set_string (info->keyfile,
-	                                 nm_setting_get_name (setting),
-	                                 key,
-	                                 str);
-}
-
-static void
-write_ip_values (GKeyFile *file,
-                 const char *setting_name,
-                 GPtrArray *array,
-                 const char *gateway,
-                 gboolean is_route)
-{
-	nm_auto_free_gstring GString *output = NULL;
-	int addr_family;
-	guint i;
-	const char *addr;
-	const char *gw;
-	guint32 plen;
-	char key_name[64];
-	char *key_name_idx;
-
-	if (!array->len)
-		return;
-
-	addr_family =   nm_streq (setting_name, NM_SETTING_IP4_CONFIG_SETTING_NAME)
-	              ? AF_INET
-	              : AF_INET6;
-
-	strcpy (key_name, is_route ? "route" : "address");
-	key_name_idx = key_name + strlen (key_name);
-
-	output = g_string_sized_new (2*INET_ADDRSTRLEN + 10);
-	for (i = 0; i < array->len; i++) {
-		gint64 metric = -1;
-
-		if (is_route) {
-			NMIPRoute *route = array->pdata[i];
-
-			addr = nm_ip_route_get_dest (route);
-			plen = nm_ip_route_get_prefix (route);
-			gw = nm_ip_route_get_next_hop (route);
-			metric = nm_ip_route_get_metric (route);
-		} else {
-			NMIPAddress *address = array->pdata[i];
-
-			addr = nm_ip_address_get_address (address);
-			plen = nm_ip_address_get_prefix (address);
-			gw =   (i == 0)
-			     ? gateway
-			     : NULL;
-		}
-
-		g_string_set_size (output, 0);
-		g_string_append_printf (output, "%s/%u", addr, plen);
-		if (   metric != -1
-		    || gw) {
-			/* Older versions of the plugin do not support the form
-			 * "a.b.c.d/plen,,metric", so, we always have to write the
-			 * gateway, even if there isn't one.
-			 * The current version supports reading of the above form.
-			 */
-			if (!gw) {
-				if (addr_family == AF_INET)
-					gw = "0.0.0.0";
-				else
-					gw = "::";
-			}
-
-			g_string_append_printf (output, ",%s", gw);
-			if (   is_route
-			    && metric != -1)
-				g_string_append_printf (output, ",%lu", (unsigned long) metric);
-		}
-
-		sprintf (key_name_idx, "%u", i + 1);
-		nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, output->str);
-
-		if (is_route) {
-			gs_free char *attributes = NULL;
-
-			attributes = nm_utils_format_variant_attributes (_nm_ip_route_get_attributes (array->pdata[i]),
-			                                                 ',', '=');
-			if (attributes) {
-				g_strlcat (key_name, "_options", sizeof (key_name));
-				nm_keyfile_plugin_kf_set_string (file, setting_name, key_name, attributes);
-			}
-		}
-	}
-}
-
-static void
-addr_writer (KeyfileWriterInfo *info,
-             NMSetting *setting,
-             const char *key,
-             const GValue *value)
-{
-	GPtrArray *array;
-	const char *setting_name = nm_setting_get_name (setting);
-	const char *gateway = nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (setting));
-
-	array = (GPtrArray *) g_value_get_boxed (value);
-	if (array && array->len)
-		write_ip_values (info->keyfile, setting_name, array, gateway, FALSE);
-}
-
-static void
-route_writer (KeyfileWriterInfo *info,
-              NMSetting *setting,
-              const char *key,
-              const GValue *value)
-{
-	GPtrArray *array;
-	const char *setting_name = nm_setting_get_name (setting);
-
-	array = (GPtrArray *) g_value_get_boxed (value);
-	if (array && array->len)
-		write_ip_values (info->keyfile, setting_name, array, NULL, TRUE);
-}
-
-static void
-bridge_vlan_writer (KeyfileWriterInfo *info,
-                    NMSetting *setting,
-                    const char *key,
-                    const GValue *value)
-{
-	NMBridgeVlan *vlan;
-	GPtrArray *vlans;
-	GString *string;
-	guint i;
-
-	vlans = (GPtrArray *) g_value_get_boxed (value);
-	if (!vlans || !vlans->len)
-		return;
-
-	string = g_string_new ("");
-	for (i = 0; i < vlans->len; i++) {
-		gs_free char *vlan_str = NULL;
-
-		vlan = vlans->pdata[i];
-		vlan_str = nm_bridge_vlan_to_str (vlan, NULL);
-		if (!vlan_str)
-			continue;
-		if (string->len > 0)
-			g_string_append (string, ",");
-		nm_utils_escaped_tokens_escape_gstr_assert (vlan_str, ",", string);
-	}
-
-	nm_keyfile_plugin_kf_set_string (info->keyfile,
-	                                 nm_setting_get_name (setting),
-	                                 "vlans",
-	                                 string->str);
-
-	g_string_free (string, TRUE);
-}
-
-
-#define ETHERNET_S390_OPTIONS_GROUP_NAME "ethernet-s390-options"
-
-static void
-wired_s390_options_parser_full (KeyfileReaderInfo *info,
-                                const NMMetaSettingInfo *setting_info,
-                                const NMSettInfoProperty *property_info,
-                                const ParseInfoProperty *pip,
-                                NMSetting *setting)
-{
-	NMSettingWired *s_wired = NM_SETTING_WIRED (setting);
-	gs_strfreev char **keys = NULL;
-	gsize n_keys;
-	gsize i;
-
-	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, ETHERNET_S390_OPTIONS_GROUP_NAME, &n_keys, NULL);
-	for (i = 0; i < n_keys; i++) {
-		gs_free char *value = NULL;
-		gs_free char *key_to_free = NULL;
-
-		value = nm_keyfile_plugin_kf_get_string (info->keyfile,
-		                                         ETHERNET_S390_OPTIONS_GROUP_NAME,
-		                                         keys[i],
-		                                         NULL);
-		if (!value)
-			continue;
-
-		nm_setting_wired_add_s390_option (s_wired,
-		                                  nm_keyfile_key_decode (keys[i],
-		                                                         &key_to_free),
-		                                  value);
-	}
-}
-
-static void
-wired_s390_options_writer_full (KeyfileWriterInfo *info,
-                                const NMMetaSettingInfo *setting_info,
-                                const NMSettInfoProperty *property_info,
-                                const ParseInfoProperty *pip,
-                                NMSetting *setting)
-{
-	NMSettingWired *s_wired = NM_SETTING_WIRED (setting);
-	guint i, n;
-
-	n = nm_setting_wired_get_num_s390_options (s_wired);
-	for (i = 0; i < n; i++) {
-		const char *opt_key;
-		const char *opt_val;
-		gs_free char *key_to_free = NULL;
-
-		nm_setting_wired_get_s390_option (s_wired, i, &opt_key, &opt_val);
-		nm_keyfile_plugin_kf_set_string (info->keyfile,
-		                                 ETHERNET_S390_OPTIONS_GROUP_NAME,
-		                                 nm_keyfile_key_encode (opt_key, &key_to_free),
-		                                 opt_val);
-	}
-}
-
-static void
-ip_routing_rule_writer_full (KeyfileWriterInfo *info,
-                             const NMMetaSettingInfo *setting_info,
-                             const NMSettInfoProperty *property_info,
-                             const ParseInfoProperty *pip,
-                             NMSetting *setting)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	NMSettingIPConfig *s_ip = NM_SETTING_IP_CONFIG (setting);
-	guint i, j, n;
-	char key_name_full[100] = "routing-rule";
-	char *key_name_num = &key_name_full[NM_STRLEN ("routing-rule")];
-
-	n = nm_setting_ip_config_get_num_routing_rules (s_ip);
-	j = 0;
-	for (i = 0; i < n; i++) {
-		NMIPRoutingRule *rule = nm_setting_ip_config_get_routing_rule (s_ip, i);
-		gs_free char *str = NULL;
-
-		str = nm_ip_routing_rule_to_string (rule,
-		                                    NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE,
-		                                    NULL,
-		                                    NULL);
-		if (!str)
-			continue;
-
-		sprintf (key_name_num, "%u", ++j);
-		nm_keyfile_plugin_kf_set_string (info->keyfile,
-		                                 setting_name,
-		                                 key_name_full,
-		                                 str);
-	}
-}
-
-static void
-qdisc_writer (KeyfileWriterInfo *info,
-              NMSetting *setting,
-              const char *key,
-              const GValue *value)
-{
-	gsize i;
-	GPtrArray *array;
-
-	array = (GPtrArray *) g_value_get_boxed (value);
-	if (!array || !array->len)
-		return;
-
-	for (i = 0; i < array->len; i++) {
-		NMTCQdisc *qdisc = array->pdata[i];
-		GString *key_name = g_string_sized_new (16);
-		GString *value_str = g_string_sized_new (60);
-
-		g_string_append (key_name, "qdisc.");
-		_nm_utils_string_append_tc_parent (key_name, NULL,
-		                                   nm_tc_qdisc_get_parent (qdisc));
-		_nm_utils_string_append_tc_qdisc_rest (value_str, qdisc);
-
-		nm_keyfile_plugin_kf_set_string (info->keyfile,
-		                                 NM_SETTING_TC_CONFIG_SETTING_NAME,
-		                                 key_name->str,
-		                                 value_str->str);
-
-		g_string_free (key_name, TRUE);
-		g_string_free (value_str, TRUE);
-	}
-}
-
-static void
-tfilter_writer (KeyfileWriterInfo *info,
-              NMSetting *setting,
-              const char *key,
-              const GValue *value)
-{
-	gsize i;
-	GPtrArray *array;
-
-	array = (GPtrArray *) g_value_get_boxed (value);
-	if (!array || !array->len)
-		return;
-
-	for (i = 0; i < array->len; i++) {
-		NMTCTfilter *tfilter = array->pdata[i];
-		GString *key_name = g_string_sized_new (16);
-		GString *value_str = g_string_sized_new (60);
-
-		g_string_append (key_name, "tfilter.");
-		_nm_utils_string_append_tc_parent (key_name, NULL,
-		                                   nm_tc_tfilter_get_parent (tfilter));
-		_nm_utils_string_append_tc_tfilter_rest (value_str, tfilter, NULL);
-
-		nm_keyfile_plugin_kf_set_string (info->keyfile,
-		                                 NM_SETTING_TC_CONFIG_SETTING_NAME,
-		                                 key_name->str,
-		                                 value_str->str);
-
-		g_string_free (key_name, TRUE);
-		g_string_free (value_str, TRUE);
-	}
-}
-
-static void
-write_hash_of_string (GKeyFile *file,
-                      NMSetting *setting,
-                      const char *key,
-                      const GValue *value)
-{
-	GHashTable *hash;
-	const char *group_name = nm_setting_get_name (setting);
-	gboolean vpn_secrets = FALSE;
-	gs_free const char **keys = NULL;
-	guint i, l;
-
-	nm_assert (   (NM_IS_SETTING_VPN (setting)  && nm_streq (key, NM_SETTING_VPN_DATA))
-	           || (NM_IS_SETTING_VPN (setting)  && nm_streq (key, NM_SETTING_VPN_SECRETS))
-	           || (NM_IS_SETTING_BOND (setting) && nm_streq (key, NM_SETTING_BOND_OPTIONS))
-	           || (NM_IS_SETTING_USER (setting) && nm_streq (key, NM_SETTING_USER_DATA)));
-
-	/* Write VPN secrets out to a different group to keep them separate */
-	if (   NM_IS_SETTING_VPN (setting)
-	    && nm_streq (key, NM_SETTING_VPN_SECRETS)) {
-		group_name = NM_KEYFILE_GROUP_VPN_SECRETS;
-		vpn_secrets = TRUE;
-	}
-
-	hash = g_value_get_boxed (value);
-
-	keys = nm_utils_strdict_get_keys (hash, TRUE, &l);
-	for (i = 0; i < l; i++) {
-		gs_free char *to_free = NULL;
-		const char *property, *data;
-
-		property = keys[i];
-
-		/* Handle VPN secrets specially; they are nested in the property's hash;
-		 * we don't want to write them if the secret is not saved, not required,
-		 * or owned by a user's secret agent.
-		 */
-		if (vpn_secrets) {
-			NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
-
-			if (!nm_setting_get_secret_flags (setting, property, &secret_flags, NULL))
-				nm_assert_not_reached ();
-			if (!_secret_flags_persist_secret (secret_flags))
-				continue;
-		}
-
-		data = g_hash_table_lookup (hash, property);
-		nm_keyfile_plugin_kf_set_string (file, group_name,
-		                                 nm_keyfile_key_encode (property, &to_free),
-		                                 data);
-	}
-}
-
-static void
-ssid_writer (KeyfileWriterInfo *info,
-             NMSetting *setting,
-             const char *key,
-             const GValue *value)
-{
-	GBytes *bytes;
-	const guint8 *ssid_data;
-	gsize ssid_len;
-	const char *setting_name = nm_setting_get_name (setting);
-	gboolean new_format = TRUE;
-	gsize semicolons = 0;
-	gsize i;
-
-	g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES));
-
-	bytes = g_value_get_boxed (value);
-	if (!bytes)
-		return;
-	ssid_data = g_bytes_get_data (bytes, &ssid_len);
-	if (!ssid_data || !ssid_len) {
-		nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, "");
-		return;
-	}
-
-	/* Check whether each byte is printable.  If not, we have to use an
-	 * integer list, otherwise we can just use a string.
-	 */
-	for (i = 0; i < ssid_len; i++) {
-		const char c = ssid_data[i];
-
-		if (!g_ascii_isprint (c)) {
-			new_format = FALSE;
-			break;
-		}
-		if (c == ';')
-			semicolons++;
-	}
-
-	if (new_format) {
-		gs_free char *ssid = NULL;
-
-		if (semicolons == 0)
-			ssid = g_strndup ((char *) ssid_data, ssid_len);
-		else {
-			/* Escape semicolons with backslashes to make strings
-			 * containing ';', such as '16;17;' unambiguous */
-			gsize j = 0;
-
-			ssid = g_malloc (ssid_len + semicolons + 1);
-			for (i = 0; i < ssid_len; i++) {
-				if (ssid_data[i] == ';')
-					ssid[j++] = '\\';
-				ssid[j++] = ssid_data[i];
-			}
-			ssid[j] = '\0';
-		}
-		nm_keyfile_plugin_kf_set_string (info->keyfile, setting_name, key, ssid);
-	} else
-		nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_name, key, ssid_data, ssid_len);
-}
-
-static void
-password_raw_writer (KeyfileWriterInfo *info,
-                     NMSetting *setting,
-                     const char *key,
-                     const GValue *value)
-{
-	const char *setting_name = nm_setting_get_name (setting);
-	GBytes *array;
-	gsize len;
-	const guint8 *data;
-
-	g_return_if_fail (G_VALUE_HOLDS (value, G_TYPE_BYTES));
-
-	array = (GBytes *) g_value_get_boxed (value);
-	if (!array)
-		return;
-	data = g_bytes_get_data (array, &len);
-	if (!data)
-		len = 0;
-	nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_name, key, data, len);
-}
-
-/*****************************************************************************/
-
-static void
-cert_writer_default (NMConnection *connection,
-                     GKeyFile *file,
-                     NMKeyfileWriteTypeDataCert *cert_data)
-{
-	const char *setting_name = nm_setting_get_name (NM_SETTING (cert_data->setting));
-	NMSetting8021xCKScheme scheme;
-
-	scheme = cert_data->vtable->scheme_func (cert_data->setting);
-	if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
-		gs_free char *path_free = NULL;
-		gs_free char *base_dir = NULL;
-		gs_free char *tmp = NULL;
-		const char *path;
-
-		path = cert_data->vtable->path_func (cert_data->setting);
-		g_assert (path);
-
-		/* If the path is relative, make it an absolute path.
-		 * Relative paths make a keyfile not easily usable in another
-		 * context. */
-		if (path[0] && path[0] != '/') {
-			base_dir = g_get_current_dir ();
-			path_free = g_strconcat (base_dir, "/", path, NULL);
-			path = path_free;
-		} else
-			base_dir = g_path_get_dirname (path);
-
-		/* path cannot start with "file://" or "data:;base64,", because it is an absolute path.
-		 * Still, make sure that a prefix-less path will be recognized. This can happen
-		 * for example if the path is longer then 500 chars. */
-		tmp = nm_keyfile_detect_unqualified_path_scheme (base_dir, path, -1, FALSE, NULL);
-		if (tmp)
-			g_clear_pointer (&tmp, g_free);
-		else {
-			tmp = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL);
-			path = tmp;
-		}
-
-		/* Path contains at least a '/', hence it cannot be recognized as the old
-		 * binary format consisting of a list of integers. */
-
-		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, path);
-	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
-		GBytes *blob;
-		const guint8 *blob_data;
-		gsize blob_len;
-		gs_free char *blob_base64 = NULL;
-		gs_free char *val = NULL;
-
-		blob = cert_data->vtable->blob_func (cert_data->setting);
-		g_assert (blob);
-		blob_data = g_bytes_get_data (blob, &blob_len);
-
-		blob_base64 = g_base64_encode (blob_data, blob_len);
-		val = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB, blob_base64, NULL);
-
-		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, val);
-	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
-		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key,
-		                                 cert_data->vtable->uri_func (cert_data->setting));
-	} else {
-		/* scheme_func() returns UNKNOWN in all other cases. The only valid case
-		 * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this
-		 * case, we don't expect the writer to be called, because the default value
-		 * will not be serialized.
-		 * The only other reason for the scheme to be UNKNOWN is an invalid cert.
-		 * But our connection verifies, so that cannot happen either. */
-		g_return_if_reached ();
-	}
-}
-
-static void
-cert_writer (KeyfileWriterInfo *info,
-             NMSetting *setting,
-             const char *key,
-             const GValue *value)
-{
-	const NMSetting8021xSchemeVtable *objtype = NULL;
-	guint i;
-	NMKeyfileWriteTypeDataCert type_data = { 0 };
-
-	for (i = 0; nm_setting_8021x_scheme_vtable[i].setting_key; i++) {
-		if (nm_streq0 (nm_setting_8021x_scheme_vtable[i].setting_key, key)) {
-			objtype = &nm_setting_8021x_scheme_vtable[i];
-			break;
-		}
-	}
-	if (!objtype)
-		g_return_if_reached ();
-
-	type_data.setting = NM_SETTING_802_1X (setting);
-	type_data.vtable = objtype;
-
-	if (info->handler) {
-		if (info->handler (info->connection,
-		                   info->keyfile,
-		                   NM_KEYFILE_WRITE_TYPE_CERT,
-		                   &type_data,
-		                   info->user_data,
-		                   &info->error))
-			return;
-		if (info->error)
-			return;
-	}
-
-	cert_writer_default (info->connection, info->keyfile, &type_data);
-}
-
-/*****************************************************************************/
-
-struct _ParseInfoProperty {
-	const char *property_name;
-	union {
-		void (*parser) (KeyfileReaderInfo *info,
-		                NMSetting *setting,
-		                const char *key);
-		void (*parser_full) (KeyfileReaderInfo *info,
-		                     const NMMetaSettingInfo *setting_info,
-		                     const NMSettInfoProperty *property_info,
-		                     const ParseInfoProperty *pip,
-		                     NMSetting *setting);
-	};
-	union {
-		void (*writer) (KeyfileWriterInfo *info,
-		                NMSetting *setting,
-		                const char *key,
-		                const GValue *value);
-		void (*writer_full) (KeyfileWriterInfo *info,
-		                     const NMMetaSettingInfo *setting_info,
-		                     const NMSettInfoProperty *property_info,
-		                     const ParseInfoProperty *pip,
-		                     NMSetting *setting);
-	};
-	bool parser_skip;
-	bool parser_no_check_key:1;
-	bool writer_skip:1;
-	bool has_writer_full:1;
-	bool has_parser_full:1;
-
-	/* usually, we skip to write values that have their
-	 * default value. By setting this flag to TRUE, also
-	 * default values are written. */
-	bool writer_persist_default:1;
-};
-
-#define PARSE_INFO_PROPERTY(_property_name, ...) \
-	(&((const ParseInfoProperty) { \
-		.property_name = _property_name, \
-		__VA_ARGS__ \
-	}))
-
-#define PARSE_INFO_PROPERTIES(...) \
-	.properties = ((const ParseInfoProperty*const[]) { \
-		__VA_ARGS__ \
-		NULL, \
-	})
-
-typedef struct {
-	const ParseInfoProperty*const*properties;
-} ParseInfoSetting;
-
-#define PARSE_INFO_SETTING(setting_type, ...) \
-	[setting_type] = (&((const ParseInfoSetting) { \
-		__VA_ARGS__ \
-	}))
-
-static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = {
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIRELESS,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_BSSID,
-				.parser        = mac_address_parser_ETHER,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
-				.parser        = mac_address_parser_ETHER_cloned,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_MAC_ADDRESS,
-				.parser        = mac_address_parser_ETHER,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRELESS_SSID,
-				.parser        = ssid_parser,
-				.writer        = ssid_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_802_1X,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_CA_CERT,
-				.parser        = cert_parser,
-				.writer        = cert_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_CLIENT_CERT,
-				.parser        = cert_parser,
-				.writer        = cert_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PASSWORD_RAW,
-				.parser        = password_raw_parser,
-				.writer        = password_raw_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_CA_CERT,
-				.parser        = cert_parser,
-				.writer        = cert_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_CLIENT_CERT,
-				.parser        = cert_parser,
-				.writer        = cert_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
-				.parser        = cert_parser,
-				.writer        = cert_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_802_1X_PRIVATE_KEY,
-				.parser        = cert_parser,
-				.writer        = cert_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIRED,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
-				.parser        = mac_address_parser_ETHER_cloned,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRED_MAC_ADDRESS,
-				.parser        = mac_address_parser_ETHER,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_WIRED_S390_OPTIONS,
-				.parser_no_check_key = TRUE,
-				.parser_full   = wired_s390_options_parser_full,
-				.writer_full   = wired_s390_options_writer_full,
-				.has_parser_full = TRUE,
-				.has_writer_full = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BLUETOOTH,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_BLUETOOTH_BDADDR,
-				.parser        = mac_address_parser_ETHER,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BOND,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_BOND_OPTIONS,
-				.parser_no_check_key = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BRIDGE,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_MAC_ADDRESS,
-				.parser        = mac_address_parser_ETHER,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_VLANS,
-				.parser_no_check_key = TRUE,
-				.parser        = bridge_vlan_parser,
-				.writer        = bridge_vlan_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_BRIDGE_PORT,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_BRIDGE_PORT_VLANS,
-				.parser_no_check_key = TRUE,
-				.parser        = bridge_vlan_parser,
-				.writer        = bridge_vlan_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_CONNECTION,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_CONNECTION_READ_ONLY,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_CONNECTION_TYPE,
-				.parser        = setting_alias_parser,
-				.writer        = setting_alias_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_INFINIBAND,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_INFINIBAND_MAC_ADDRESS,
-				.parser        = mac_address_parser_INFINIBAND,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_IP4_CONFIG,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ADDRESSES,
-				.parser_no_check_key = TRUE,
-				.parser        = ip_address_or_route_parser,
-				.writer        = addr_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_DNS,
-				.parser_no_check_key = TRUE,
-				.parser        = ip_dns_parser,
-				.writer        = dns_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_GATEWAY,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTES,
-				.parser_no_check_key = TRUE,
-				.parser        = ip_address_or_route_parser,
-				.writer        = route_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTING_RULES,
-				.parser_no_check_key = TRUE,
-				.parser_full   = ip_routing_rule_parser_full,
-				.writer_full   = ip_routing_rule_writer_full,
-				.has_parser_full = TRUE,
-				.has_writer_full = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_IP6_CONFIG,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
-				.parser_no_check_key = TRUE,
-				.parser        = ip6_addr_gen_mode_parser,
-				.writer        = ip6_addr_gen_mode_writer,
-				.writer_persist_default = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ADDRESSES,
-				.parser_no_check_key = TRUE,
-				.parser        = ip_address_or_route_parser,
-				.writer        = addr_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_DNS,
-				.parser_no_check_key = TRUE,
-				.parser        = ip_dns_parser,
-				.writer        = dns_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_GATEWAY,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTES,
-				.parser_no_check_key = TRUE,
-				.parser        = ip_address_or_route_parser,
-				.writer        = route_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_IP_CONFIG_ROUTING_RULES,
-				.parser_no_check_key = TRUE,
-				.parser_full   = ip_routing_rule_parser_full,
-				.writer_full   = ip_routing_rule_writer_full,
-				.has_parser_full = TRUE,
-				.has_writer_full = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_SERIAL,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_SERIAL_PARITY,
-				.parser        = parity_parser,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_SRIOV,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_SRIOV_VFS,
-				.parser_no_check_key = TRUE,
-				.parser        = sriov_vfs_parser,
-				.writer        = sriov_vfs_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TC_CONFIG,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_TC_CONFIG_QDISCS,
-				.parser_no_check_key = TRUE,
-				.parser        = qdisc_parser,
-				.writer        = qdisc_writer,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TC_CONFIG_TFILTERS,
-				.parser_no_check_key = TRUE,
-				.parser        = tfilter_parser,
-				.writer        = tfilter_writer,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TEAM,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_CONFIG,
-				.parser        = team_config_parser,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_LINK_WATCHERS,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_MCAST_REJOIN_COUNT,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_NOTIFY_PEERS_COUNT,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_ACTIVE,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_FAST_RATE,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_HWADDR_POLICY,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_MIN_PORTS,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_SYS_PRIO,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_BALANCER,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_RUNNER_TX_HASH,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_TEAM_PORT,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_CONFIG,
-				.parser        = team_config_parser,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LACP_KEY,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LACP_PRIO,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_LINK_WATCHERS,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_PRIO,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_QUEUE_ID,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_TEAM_PORT_STICKY,
-				.parser_skip   = TRUE,
-				.writer_skip   = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_USER,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_USER_DATA,
-				.parser_no_check_key = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_VLAN,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_VLAN_FLAGS,
-				.writer_persist_default = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_VPN,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_VPN_DATA,
-				.parser_no_check_key = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_VPN_PERSISTENT,
-				.parser_no_check_key = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_VPN_SECRETS,
-				.parser_no_check_key = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_VPN_SERVICE_TYPE,
-				.parser_no_check_key = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_VPN_TIMEOUT,
-				.parser_no_check_key = TRUE,
-			),
-			PARSE_INFO_PROPERTY (NM_SETTING_VPN_USER_NAME,
-				.parser_no_check_key = TRUE,
-			),
-		),
-	),
-	PARSE_INFO_SETTING (NM_META_SETTING_TYPE_WIMAX,
-		PARSE_INFO_PROPERTIES (
-			PARSE_INFO_PROPERTY (NM_SETTING_WIMAX_MAC_ADDRESS,
-				.parser        = mac_address_parser_ETHER,
-			),
-		),
-	),
-};
-
-static void
-_parse_info_find (NMSetting *setting,
-                  const char *property_name,
-                  const NMMetaSettingInfo **out_setting_info,
-                  const ParseInfoSetting **out_parse_info_setting,
-                  const ParseInfoProperty **out_parse_info_property)
-{
-	const NMMetaSettingInfo *setting_info;
-	const ParseInfoSetting *pis;
-	const ParseInfoProperty *pip;
-
-#if NM_MORE_ASSERTS > 10
-	{
-		guint i, j;
-		static int asserted = FALSE;
-
-		if (!asserted) {
-			for (i = 0; i < G_N_ELEMENTS (parse_infos); i++) {
-				pis = parse_infos[i];
-
-				if (!pis)
-					continue;
-				if (!pis->properties)
-					continue;
-
-				g_assert (pis->properties[0]);
-				for (j = 0; pis->properties[j]; j++) {
-					const ParseInfoProperty *pip0;
-					const ParseInfoProperty *pipj = pis->properties[j];
-
-					g_assert (pipj->property_name);
-					if (   j > 0
-					    && (pip0 = pis->properties[j - 1])
-					    && strcmp (pip0->property_name, pipj->property_name) >= 0) {
-						g_error ("Wrong order at index #%d.%d: \"%s.%s\" before \"%s.%s\"",
-						         i, j - 1,
-						         nm_meta_setting_infos[i].setting_name, pip0->property_name,
-						         nm_meta_setting_infos[i].setting_name, pipj->property_name);
-					}
-				}
-			}
-			asserted = TRUE;
-		}
-	}
-#endif
-
-	if (   !NM_IS_SETTING (setting)
-	    || !(setting_info = NM_SETTING_GET_CLASS (setting)->setting_info)) {
-		/* handle invalid setting objects gracefully. */
-		NM_SET_OUT (out_setting_info, NULL);
-		NM_SET_OUT (out_parse_info_setting, NULL);
-		NM_SET_OUT (out_parse_info_property, NULL);
-		return;
-	}
-
-	nm_assert (setting_info->setting_name);
-	nm_assert (_NM_INT_NOT_NEGATIVE (setting_info->meta_type));
-	nm_assert (setting_info->meta_type < G_N_ELEMENTS (parse_infos));
-
-	pis = parse_infos[setting_info->meta_type];
-
-	pip = NULL;
-	if (   pis
-	    && property_name) {
-		gssize idx;
-
-		G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (ParseInfoProperty, property_name) == 0);
-
-		idx = nm_utils_ptrarray_find_binary_search ((gconstpointer *) pis->properties,
-		                                            NM_PTRARRAY_LEN (pis->properties),
-		                                            &property_name,
-		                                            nm_strcmp_p_with_data,
-		                                            NULL,
-		                                            NULL,
-		                                            NULL);
-		if (idx >= 0)
-			pip = pis->properties[idx];
-	}
-
-	NM_SET_OUT (out_setting_info, setting_info);
-	NM_SET_OUT (out_parse_info_setting, pis);
-	NM_SET_OUT (out_parse_info_property, pip);
-}
-
-/*****************************************************************************/
-
-static void
-read_one_setting_value (KeyfileReaderInfo *info,
-                        NMSetting *setting,
-                        const NMSettInfoProperty *property_info)
-{
-	GKeyFile *keyfile = info->keyfile;
-	gs_free_error GError *err = NULL;
-	const NMMetaSettingInfo *setting_info;
-	const ParseInfoProperty *pip;
-	gs_free char *tmp_str = NULL;
-	const char *key;
-	GType type;
-	guint64 u64;
-	gint64 i64;
-
-	nm_assert (!info->error);
-	nm_assert (   !property_info->param_spec
-	           || nm_streq (property_info->param_spec->name, property_info->name));
-
-	key = property_info->name;
-
-	_parse_info_find (setting, key, &setting_info, NULL, &pip);
-
-	nm_assert (setting_info);
-
-	if (!pip) {
-		if (nm_streq (key, NM_SETTING_NAME))
-			return;
-		if (!property_info->param_spec)
-			return;
-		if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE)
-			return;
-	} else {
-		if (pip->parser_skip)
-			return;
-		if (pip->has_parser_full) {
-			pip->parser_full (info, setting_info, property_info, pip, setting);
-			return;
-		}
-	}
-
-	nm_assert (property_info->param_spec);
-	nm_assert ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) == G_PARAM_WRITABLE);
-
-	/* Check for the exact key in the GKeyFile if required.  Most setting
-	 * properties map 1:1 to a key in the GKeyFile, but for those properties
-	 * like IP addresses and routes where more than one value is actually
-	 * encoded by the setting property, this won't be true.
-	 */
-	if (   (!pip || !pip->parser_no_check_key)
-	    && !nm_keyfile_plugin_kf_has_key (keyfile, setting_info->setting_name, key, &err)) {
-		/* Key doesn't exist or an error occurred, thus nothing to do. */
-		if (err) {
-			if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			                  _("error loading setting value: %s"),
-			                  err->message))
-				return;
-		}
-		return;
-	}
-
-	if (   pip
-	    && pip->parser) {
-		pip->parser (info, setting, key);
-		return;
-	}
-
-	type = G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec);
-
-	if (type == G_TYPE_STRING) {
-		gs_free char *str_val = NULL;
-
-		str_val = nm_keyfile_plugin_kf_get_string (keyfile, setting_info->setting_name, key, &err);
-		if (!err)
-			nm_g_object_set_property_string_take (G_OBJECT (setting), key, g_steal_pointer (&str_val), &err);
-	} else if (type == G_TYPE_UINT) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64);
-			if (   u64 == G_MAXUINT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_uint (G_OBJECT (setting), key, u64, &err);
-		}
-	} else if (type == G_TYPE_INT) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MININT64);
-			if (   i64 == G_MININT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_int (G_OBJECT (setting), key, i64, &err);
-		}
-	} else if (type == G_TYPE_BOOLEAN) {
-		gboolean bool_val;
-
-		bool_val = nm_keyfile_plugin_kf_get_boolean (keyfile, setting_info->setting_name, key, &err);
-		if (!err)
-			nm_g_object_set_property_boolean (G_OBJECT (setting), key, bool_val, &err);
-	} else if (type == G_TYPE_CHAR) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			/* As documented by glib, G_TYPE_CHAR is really a (signed!) gint8. */
-			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT8, G_MAXINT8, G_MININT64);
-			if (   i64 == G_MININT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_char (G_OBJECT (setting), key, i64, &err);
-		}
-	} else if (type == G_TYPE_UINT64) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT64, G_MAXUINT64);
-			if (   u64 == G_MAXUINT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_uint64 (G_OBJECT (setting), key, u64, &err);
-		}
-	} else if (type == G_TYPE_INT64) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT64, G_MAXINT64, G_MAXINT64);
-			if (   i64 == G_MAXINT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_int64 (G_OBJECT (setting), key, i64, &err);
-		}
-	} else if (type == G_TYPE_BYTES) {
-		gs_free int *tmp = NULL;
-		GByteArray *array;
-		GBytes *bytes;
-		gsize length;
-		int i;
-		gboolean already_warned = FALSE;
-
-		tmp = nm_keyfile_plugin_kf_get_integer_list (keyfile, setting_info->setting_name, key, &length, NULL);
-
-		array = g_byte_array_sized_new (length);
-		for (i = 0; i < length; i++) {
-			const int val = tmp[i];
-			unsigned char v = (unsigned char) (val & 0xFF);
-
-			if (val < 0 || val > 255) {
-				if (   !already_warned
-				    && !handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-				                     _("ignoring invalid byte element '%d' (not between 0 and 255 inclusive)"),
-				                     val)) {
-					g_byte_array_unref (array);
-					return;
-				}
-				already_warned = TRUE;
-			} else
-				g_byte_array_append (array, (const unsigned char *) &v, sizeof (v));
-		}
-
-		bytes = g_byte_array_free_to_bytes (array);
-		g_object_set (setting, key, bytes, NULL);
-		g_bytes_unref (bytes);
-	} else if (type == G_TYPE_STRV) {
-		gs_strfreev char **sa = NULL;
-		gsize length;
-
-		sa = nm_keyfile_plugin_kf_get_string_list (keyfile, setting_info->setting_name, key, &length, NULL);
-		g_object_set (setting, key, sa, NULL);
-	} else if (type == G_TYPE_HASH_TABLE) {
-		read_hash_of_string (keyfile, setting, key);
-	} else if (type == G_TYPE_ARRAY) {
-		read_array_of_uint (keyfile, setting, key);
-	} else if (G_TYPE_IS_FLAGS (type)) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			u64 = _nm_utils_ascii_str_to_uint64 (tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64);
-			if (   u64 == G_MAXUINT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_flags (G_OBJECT (setting), key, type, u64, &err);
-		}
-	} else if (G_TYPE_IS_ENUM (type)) {
-		tmp_str = nm_keyfile_plugin_kf_get_value (keyfile, setting_info->setting_name, key, &err);
-		if (!err) {
-			i64 = _nm_utils_ascii_str_to_int64 (tmp_str, 0, G_MININT, G_MAXINT, G_MAXINT64);
-			if (   i64 == G_MAXINT64
-			    && errno != 0) {
-				g_set_error_literal (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
-				                     _("value cannot be interpreted as integer"));
-			} else
-				nm_g_object_set_property_enum (G_OBJECT (setting), key, type, i64, &err);
-		}
-	} else
-		g_return_if_reached ();
-
-	if (err) {
-		if (   err->domain == G_KEY_FILE_ERROR
-		    && NM_IN_SET (err->code, G_KEY_FILE_ERROR_GROUP_NOT_FOUND,
-		                             G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
-			/* ignore such errors. The key is not present. */
-		} else {
-			handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-			             _("invalid setting: %s"), err->message);
-		}
-	}
-}
-
-static void
-_read_setting (KeyfileReaderInfo *info)
-{
-	const NMSettInfoSetting *sett_info;
-	gs_unref_object NMSetting *setting = NULL;
-	const char *alias;
-	GType type;
-	guint i;
-
-	alias = nm_keyfile_plugin_get_setting_name_for_alias (info->group);
-	if (!alias)
-		alias = info->group;
-
-	type = nm_setting_lookup_type (alias);
-	if (!type) {
-		handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("invalid setting name '%s'"), info->group);
-		return;
-	}
-
-	setting = g_object_new (type, NULL);
-
-	info->setting = setting;
-
-	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
-
-	if (sett_info->detail.gendata_info) {
-		gs_free char **keys = NULL;
-		gsize k, n_keys;
-
-		keys = g_key_file_get_keys (info->keyfile, info->group, &n_keys, NULL);
-		if (!keys)
-			n_keys = 0;
-		if (n_keys > 0) {
-			GHashTable *h = _nm_setting_gendata_hash (setting, TRUE);
-
-			nm_utils_strv_sort (keys, n_keys);
-			for (k = 0; k < n_keys; k++) {
-				gs_free char *key = keys[k];
-				gs_free_error GError *local = NULL;
-				const GVariantType *variant_type;
-				GVariant *variant;
-
-				/* a GKeyFile can return duplicate keys, there is just no API to make sense
-				 * of them. Skip them. */
-				if (   k + 1 < n_keys
-				    && nm_streq (key, keys[k + 1]))
-					continue;
-
-				/* currently, the API is very simple. The setting class just returns
-				 * the desired variant type, and keyfile reader will try to parse
-				 * it accordingly. Note, that this does currently not allow, that
-				 * a particular key can contain different variant types, nor is it
-				 * very flexible in general.
-				 *
-				 * We add flexibility when we need it. Keep it simple for now. */
-				variant_type = sett_info->detail.gendata_info->get_variant_type (sett_info,
-				                                                                 key,
-				                                                                 &local);
-				if (!variant_type) {
-					if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-					                  _("invalid key '%s.%s'"),
-					                  info->group, key))
-						break;
-					continue;
-				}
-
-				if (g_variant_type_equal (variant_type, G_VARIANT_TYPE_BOOLEAN)) {
-					gboolean v;
-
-					v = g_key_file_get_boolean (info->keyfile,
-					                            info->group,
-					                            key,
-					                            &local);
-					if (local) {
-						if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-						                  _("key '%s.%s' is not boolean"),
-						                  info->group, key))
-							break;
-						continue;
-					}
-					variant = g_variant_new_boolean (v);
-				} else {
-					nm_assert_not_reached ();
-					continue;
-				}
-
-				g_hash_table_insert (h,
-				                     g_steal_pointer (&key),
-				                     g_variant_take_ref (variant));
-			}
-			for (; k < n_keys; k++)
-				g_free (keys[k]);
-		}
-	}
-
-	for (i = 0; i < sett_info->property_infos_len; i++) {
-		read_one_setting_value (info,
-		                        setting,
-		                        &sett_info->property_infos[i]);
-		if (info->error)
-			goto out;
-	}
-
-out:
-	info->setting = NULL;
-	if (!info->error)
-		nm_connection_add_setting (info->connection, g_steal_pointer (&setting));
-}
-
-static void
-_read_setting_wireguard_peer (KeyfileReaderInfo *info)
-{
-	gs_unref_object NMSettingWireGuard *s_wg_new = NULL;
-	nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
-	gs_free_error GError *error = NULL;
-	NMSettingWireGuard *s_wg;
-	gs_free char *str = NULL;
-	const char *cstr = NULL;
-	const char *key;
-	gint64 i64;
-	gs_strfreev char **sa = NULL;
-	gsize n_sa;
-
-	peer = nm_wireguard_peer_new ();
-
-	nm_assert (g_str_has_prefix (info->group, NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER));
-	cstr = &info->group[NM_STRLEN (NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)];
-	if (   !nm_utils_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 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 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 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 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)) {
-		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
-		             _("peer '%s' is invalid: %s"),
-		             info->group, error->message);
-		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;
-	gsize i, n_keys;
-	NMSettingVpn *s_vpn;
-
-	s_vpn = nm_connection_get_setting_vpn (info->connection);
-	if (!s_vpn) {
-		/* if we don't also have a [vpn] section (which must be parsed earlier),
-		 * we don't do anything. */
-		nm_assert (!g_key_file_has_group (info->keyfile, "vpn"));
-		return;
-	}
-
-	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, &n_keys, NULL);
-	for (i = 0; i < n_keys; i++) {
-		gs_free char *secret = NULL;
-
-		secret = nm_keyfile_plugin_kf_get_string (info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, keys[i], NULL);
-		if (secret)
-			nm_setting_vpn_add_secret (s_vpn, keys[i], secret);
-	}
-}
-
-gboolean
-nm_keyfile_read_ensure_id (NMConnection *connection,
-                           const char *fallback_id)
-{
-	NMSettingConnection *s_con;
-
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
-	g_return_val_if_fail (fallback_id, FALSE);
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_return_val_if_fail (NM_IS_SETTING_CONNECTION (s_con), FALSE);
-
-	if (nm_setting_connection_get_id (s_con))
-		return FALSE;
-
-	g_object_set (s_con, NM_SETTING_CONNECTION_ID, fallback_id, NULL);
-	return TRUE;
-}
-
-gboolean
-nm_keyfile_read_ensure_uuid (NMConnection *connection,
-                             const char *fallback_uuid_seed)
-{
-	NMSettingConnection *s_con;
-	gs_free char *hashed_uuid = NULL;
-
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
-	g_return_val_if_fail (fallback_uuid_seed, FALSE);
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_return_val_if_fail (NM_IS_SETTING_CONNECTION (s_con), FALSE);
-
-	if (nm_setting_connection_get_uuid (s_con))
-		return FALSE;
-
-	hashed_uuid = _nm_utils_uuid_generate_from_strings ("keyfile", fallback_uuid_seed, NULL);
-	g_object_set (s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL);
-	return TRUE;
-}
-
-/**
- * nm_keyfile_read:
- * @keyfile: the keyfile from which to create the connection
- * @base_dir: when reading certificates from files with relative name,
- *   the relative path is made absolute using @base_dir. This must
- *   be an absolute path.
- * @handler: read handler
- * @user_data: user data for read handler
- * @error: error
- *
- * Tries to create a NMConnection from a keyfile. The resulting keyfile is
- * not normalized and might not even verify.
- *
- * Returns: (transfer full): on success, returns the created connection.
- */
-NMConnection *
-nm_keyfile_read (GKeyFile *keyfile,
-                 const char *base_dir,
-                 NMKeyfileReadHandler handler,
-                 void *user_data,
-                 GError **error)
-{
-	gs_unref_object NMConnection *connection = NULL;
-	NMSettingConnection *s_con;
-	gs_strfreev char **groups = NULL;
-	gsize n_groups;
-	gsize i;
-	gboolean vpn_secrets = FALSE;
-	KeyfileReaderInfo info;
-
-	g_return_val_if_fail (keyfile, NULL);
-	g_return_val_if_fail (!error || !*error, NULL);
-	g_return_val_if_fail (base_dir && base_dir[0] == '/', NULL);
-
-	connection = nm_simple_connection_new ();
-
-	info = (KeyfileReaderInfo) {
-		.connection = connection,
-		.keyfile    = keyfile,
-		.base_dir   = base_dir,
-		.handler    = handler,
-		.user_data  = user_data,
-	};
-
-	groups = g_key_file_get_groups (keyfile, &n_groups);
-	if (!groups)
-		n_groups = 0;
-
-	for (i = 0; i < n_groups; i++) {
-
-		info.group = groups[i];
-
-		if (nm_streq (groups[i], NM_KEYFILE_GROUP_VPN_SECRETS)) {
-			/* Only read out secrets when needed */
-			vpn_secrets = TRUE;
-		} else if (NM_STR_HAS_PREFIX (groups[i], NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER))
-			_read_setting_wireguard_peer (&info);
-		else if (nm_streq (groups[i], NM_KEYFILE_GROUP_NMMETA)) {
-			/* pass */
-		} else
-			_read_setting (&info);
-
-		info.group = NULL;
-
-		if (info.error)
-			goto out_with_info_error;
-	}
-
-	s_con = nm_connection_get_setting_connection (connection);
-	if (!s_con) {
-		s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
-		nm_connection_add_setting (connection, NM_SETTING (s_con));
-	}
-
-	/* Make sure that we have 'interface-name' even if it was specified in the
-	 * "wrong" (ie, deprecated) group.
-	 */
-	if (   !nm_setting_connection_get_interface_name (s_con)
-	    && nm_setting_connection_get_connection_type (s_con)) {
-		gs_free char *interface_name = NULL;
-
-		interface_name = g_key_file_get_string (keyfile,
-		                                        nm_setting_connection_get_connection_type (s_con),
-		                                        "interface-name",
-		                                        NULL);
-		if (interface_name)
-			g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, interface_name, NULL);
-	}
-
-	if (vpn_secrets) {
-		info.group = NM_KEYFILE_GROUP_VPN_SECRETS;
-		_read_setting_vpn_secrets (&info);
-		info.group = NULL;;
-		if (info.error)
-			goto out_with_info_error;
-	}
-
-	return g_steal_pointer (&connection);
-
-out_with_info_error:
-	g_propagate_error (error, info.error);
-	return NULL;
-}
-
-/*****************************************************************************/
-
-static void
-write_setting_value (KeyfileWriterInfo *info,
-                     NMSetting *setting,
-                     const NMSettInfoProperty *property_info)
-{
-	const NMMetaSettingInfo *setting_info;
-	const ParseInfoProperty *pip;
-	const char *key;
-	char numstr[64];
-	GValue value;
-	GType type;
-
-	nm_assert (!info->error);
-	nm_assert (   !property_info->param_spec
-	           || nm_streq (property_info->param_spec->name, property_info->name));
-
-	key = property_info->name;
-
-	_parse_info_find (setting, key, &setting_info, NULL, &pip);
-
-	if (!pip) {
-		if (!setting_info) {
-			/* the setting type is unknown. That is highly unexpected
-			 * (and as this is currently only called from NetworkManager
-			 * daemon, not possible).
-			 *
-			 * Still, handle it gracefully, because later keyfile writer will become
-			 * public API of libnm, where @setting is (untrusted) user input.
-			 *
-			 * Gracefully here just means: ignore the setting. */
-			return;
-		}
-		if (!property_info->param_spec)
-			return;
-		if (nm_streq (key, NM_SETTING_NAME))
-			return;
-	} else {
-		if (pip->has_writer_full) {
-			pip->writer_full (info, setting_info, property_info, pip, setting);
-			return;
-		}
-		if (pip->writer_skip)
-			return;
-	}
-
-	nm_assert (property_info->param_spec);
-
-	/* Don't write secrets that are owned by user secret agents or aren't
-	 * supposed to be saved.  VPN secrets are handled specially though since
-	 * the secret flags there are in a third-level hash in the 'secrets'
-	 * property.
-	 */
-	if (   (property_info->param_spec->flags & NM_SETTING_PARAM_SECRET)
-	    && !NM_IS_SETTING_VPN (setting)) {
-		NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
-
-		if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL))
-			g_return_if_reached ();
-		if (!_secret_flags_persist_secret (secret_flags))
-			return;
-	}
-
-	value = (GValue) { 0 };
-
-	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);
-
-	if (   (!pip || !pip->writer_persist_default)
-	    && g_param_value_defaults (property_info->param_spec, &value)) {
-		nm_assert (!g_key_file_has_key (info->keyfile, setting_info->setting_name, key, NULL));
-		goto out_unset_value;
-	}
-
-	if (   pip
-	    && pip->writer) {
-		pip->writer (info, setting, key, &value);
-		goto out_unset_value;
-	}
-
-	type = G_VALUE_TYPE (&value);
-	if (type == G_TYPE_STRING) {
-		const char *str;
-
-		str = g_value_get_string (&value);
-		if (str)
-			nm_keyfile_plugin_kf_set_string (info->keyfile, setting_info->setting_name, key, str);
-	} else if (type == G_TYPE_UINT) {
-		nm_sprintf_buf (numstr, "%u", g_value_get_uint (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else if (type == G_TYPE_INT) {
-		nm_sprintf_buf (numstr, "%d", g_value_get_int (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else if (type == G_TYPE_UINT64) {
-		nm_sprintf_buf (numstr, "%" G_GUINT64_FORMAT, g_value_get_uint64 (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else if (type == G_TYPE_INT64) {
-		nm_sprintf_buf (numstr, "%" G_GINT64_FORMAT, g_value_get_int64 (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else if (type == G_TYPE_BOOLEAN) {
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key,
-		                                  g_value_get_boolean (&value)
-		                                ? "true"
-		                                : "false");
-	} else if (type == G_TYPE_CHAR) {
-		nm_sprintf_buf (numstr, "%d", (int) g_value_get_schar (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else if (type == G_TYPE_BYTES) {
-		GBytes *bytes;
-		const guint8 *data;
-		gsize len = 0;
-
-		bytes = g_value_get_boxed (&value);
-		data = bytes ? g_bytes_get_data (bytes, &len) : NULL;
-
-		if (data != NULL && len > 0)
-			nm_keyfile_plugin_kf_set_integer_list_uint8 (info->keyfile, setting_info->setting_name, key, data, len);
-	} else if (type == G_TYPE_STRV) {
-		char **array;
-
-		array = (char **) g_value_get_boxed (&value);
-		nm_keyfile_plugin_kf_set_string_list (info->keyfile, setting_info->setting_name, key, (const char **const) array, g_strv_length (array));
-	} else if (type == G_TYPE_HASH_TABLE) {
-		write_hash_of_string (info->keyfile, setting, key, &value);
-	} else if (type == G_TYPE_ARRAY) {
-		write_array_of_uint (info->keyfile, setting, key, &value);
-	} else if (G_VALUE_HOLDS_FLAGS (&value)) {
-		nm_sprintf_buf (numstr, "%u", g_value_get_flags (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else if (G_VALUE_HOLDS_ENUM (&value)) {
-		nm_sprintf_buf (numstr, "%d", g_value_get_enum (&value));
-		nm_keyfile_plugin_kf_set_value (info->keyfile, setting_info->setting_name, key, numstr);
-	} else
-		g_return_if_reached ();
-
-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,
-                  void *user_data,
-                  GError **error)
-{
-	gs_unref_keyfile GKeyFile *keyfile = NULL;
-	KeyfileWriterInfo info;
-	gs_free NMSetting **settings = NULL;
-	guint i, j, n_settings = 0;
-
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
-	g_return_val_if_fail (!error || !*error, NULL);
-
-	if (!nm_connection_verify (connection, error))
-		return NULL;
-
-	keyfile = g_key_file_new ();
-
-	info = (KeyfileWriterInfo) {
-		.connection = connection,
-		.keyfile    = keyfile,
-		.error      = NULL,
-		.handler    = handler,
-		.user_data  = user_data,
-	};
-
-	settings = nm_connection_get_settings (connection, &n_settings);
-	for (i = 0; i < n_settings; i++) {
-		const NMSettInfoSetting *sett_info;
-		NMSetting *setting = settings[i];
-		const char *setting_name;
-		const char *setting_alias;
-
-		sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
-
-		setting_name = sett_info->setting_class->setting_info->setting_name;
-
-		if (sett_info->detail.gendata_info) {
-			guint k, n_keys;
-			const char *const*keys;
-
-			nm_assert (!nm_keyfile_plugin_get_alias_for_setting_name (sett_info->setting_class->setting_info->setting_name));
-
-			n_keys = _nm_setting_gendata_get_all (setting, &keys, NULL);
-
-			if (n_keys > 0) {
-				GHashTable *h = _nm_setting_gendata_hash (setting, FALSE);
-
-				for (k = 0; k < n_keys; k++) {
-					const char *key = keys[k];
-					GVariant *v;
-
-					v = g_hash_table_lookup (h, key);
-
-					if (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) {
-						g_key_file_set_boolean (info.keyfile,
-						                        setting_name,
-						                        key,
-						                        g_variant_get_boolean (v));
-					} else {
-						/* BUG: The variant type is not implemented. Since the connection
-						 * verifies, this can only mean we either wrongly didn't reject
-						 * the connection as invalid, or we didn't properly implement the
-						 * variant type. */
-						nm_assert_not_reached ();
-						continue;
-					}
-				}
-			}
-		}
-
-		for (j = 0; j < sett_info->property_infos_len; j++) {
-			const NMSettInfoProperty *property_info = _nm_sett_info_property_info_get_sorted (sett_info, j);
-
-			write_setting_value (&info, setting, property_info);
-			if (info.error)
-				goto out_with_info_error;
-		}
-
-		setting_alias = nm_keyfile_plugin_get_alias_for_setting_name (setting_name);
-		if (   (   setting_alias
-		        && g_key_file_has_group (info.keyfile, setting_alias))
-		    || g_key_file_has_group (info.keyfile, setting_name)) {
-			/* we have a section for the setting. Nothing to do. */
-		} else {
-			/* ensure the group is present. There is no API for that, so add and remove
-			 * a dummy key. */
-			g_key_file_set_value  (info.keyfile, setting_alias ?: setting_name, ".X", "1");
-			g_key_file_remove_key (info.keyfile, setting_alias ?: setting_name, ".X", NULL);
-		}
-
-		if (NM_IS_SETTING_WIREGUARD (setting)) {
-			_write_setting_wireguard (setting, &info);
-			if (info.error)
-				goto out_with_info_error;
-		}
-
-		nm_assert (!info.error);
-	}
-
-	nm_assert (!info.error);
-
-	return g_steal_pointer (&keyfile);
-
-out_with_info_error:
-	g_propagate_error (error, info.error);
-	return NULL;
-}
-
-/*****************************************************************************/
-
-static const char temp_letters[] =
-"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
-
-/*
- * Check '.[a-zA-Z0-9]{6}' file suffix used for temporary files by g_file_set_contents() (mkstemp()).
- */
-static gboolean
-check_mkstemp_suffix (const char *path)
-{
-	const char *ptr;
-
-	nm_assert (path);
-
-	/* Matches *.[a-zA-Z0-9]{6} suffix of mkstemp()'s temporary files */
-	ptr = strrchr (path, '.');
-	if (   ptr
-	    && strspn (&ptr[1], temp_letters) == 6
-	    && ptr[7] == '\0')
-		return TRUE;
-	return FALSE;
-}
-
-static gboolean
-_check_suffix_impl (const char *base, const char *tag, gsize tag_len)
-{
-	gsize len;
-
-	nm_assert (base);
-	nm_assert (tag);
-	nm_assert (strlen (tag) == tag_len);
-
-	len = strlen (base);
-	if (   len > tag_len
-	    && !g_ascii_strcasecmp (base + len - tag_len, tag))
-		return TRUE;
-	return FALSE;
-}
-#define check_suffix(base, tag) _check_suffix_impl ((base), ""tag"", NM_STRLEN (tag))
-
-#define SWP_TAG ".swp"
-#define SWPX_TAG ".swpx"
-#define PEM_TAG ".pem"
-#define DER_TAG ".der"
-
-gboolean
-nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension)
-{
-	const char *base;
-	gsize l;
-
-	/* ignore_filename() must mirror nm_keyfile_utils_create_filename() */
-
-	g_return_val_if_fail (filename, TRUE);
-
-	base = strrchr (filename, '/');
-	if (base)
-		base++;
-	else
-		base = filename;
-
-	if (!base[0]) {
-		/* this check above with strrchr() also rejects "/some/path/with/trailing/slash/",
-		 * but that is fine, because such a path would name a directory, and we are not
-		 * interested in directories. */
-		return TRUE;
-	}
-
-	if (base[0] == '.') {
-		/* don't allow hidden files */
-		return TRUE;
-	}
-
-	l = strlen (base);
-
-	if (require_extension) {
-		if (   l <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)
-		    || !NM_STR_HAS_SUFFIX (base, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION))
-			return TRUE;
-		return FALSE;
-	}
-
-	/* Ignore backup files */
-	if (base[l - 1] == '~')
-		return TRUE;
-
-	/* Ignore temporary files
-	 *
-	 * This check is also important to ignore .nmload files (see
-	 * %NM_KEYFILE_PATH_SUFFIX_NMMETA). */
-	if (check_mkstemp_suffix (base))
-		return TRUE;
-
-	/* Ignore 802.1x certificates and keys */
-	if (   check_suffix (base, PEM_TAG)
-	    || check_suffix (base, DER_TAG))
-		return TRUE;
-
-	return FALSE;
-}
-
-char *
-nm_keyfile_utils_create_filename (const char *name,
-                                  gboolean with_extension)
-{
-	GString *str;
-	const char *f = name;
-	/* keyfile used to escape with '*', do not change that behavior.
-	 *
-	 * But for newly added escapings, use '_' instead.
-	 * Also, @with_extension is new-style. */
-	const char ESCAPE_CHAR = with_extension ? '_' : '*';
-	const char ESCAPE_CHAR2 = '_';
-
-	g_return_val_if_fail (name && name[0], NULL);
-
-	str = g_string_sized_new (60);
-
-	/* Convert '/' to ESCAPE_CHAR */
-	for (f = name; f[0]; f++) {
-		if (f[0] == '/')
-			g_string_append_c (str, ESCAPE_CHAR);
-		else
-			g_string_append_c (str, f[0]);
-	}
-
-	/* nm_keyfile_utils_create_filename() must avoid anything that ignore_filename() would reject.
-	 * We can escape here more aggressivly then what we would read back. */
-	if (str->str[0] == '.')
-		str->str[0] = ESCAPE_CHAR2;
-	if (str->str[str->len - 1] == '~')
-		str->str[str->len - 1] = ESCAPE_CHAR2;
-	if (   check_mkstemp_suffix (str->str)
-	    || check_suffix (str->str, PEM_TAG)
-	    || check_suffix (str->str, DER_TAG))
-		g_string_append_c (str, ESCAPE_CHAR2);
-
-	if (with_extension)
-		g_string_append (str, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
-
-	/* nm_keyfile_utils_create_filename() must mirror ignore_filename() */
-	nm_assert (!strchr (str->str, '/'));
-	nm_assert (!nm_keyfile_utils_ignore_filename (str->str, with_extension));
-
-	return g_string_free (str, FALSE);;
-}
diff --git a/libnm-core/nm-setting-6lowpan.c b/libnm-core/nm-setting-6lowpan.c
index d6aa5978..a25910ea 100644
--- a/libnm-core/nm-setting-6lowpan.c
+++ b/libnm-core/nm-setting-6lowpan.c
@@ -214,7 +214,6 @@ nm_setting_6lowpan_class_init (NMSetting6LowpanClass *klass)
 	    g_param_spec_string (NM_SETTING_6LOWPAN_PARENT, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c
index 8464100d..ade34ff5 100644
--- a/libnm-core/nm-setting-8021x.c
+++ b/libnm-core/nm-setting-8021x.c
@@ -92,6 +92,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSetting8021x,
 	PROP_SUBJECT_MATCH,
 	PROP_ALTSUBJECT_MATCHES,
 	PROP_DOMAIN_SUFFIX_MATCH,
+	PROP_DOMAIN_MATCH,
 	PROP_CLIENT_CERT,
 	PROP_CLIENT_CERT_PASSWORD,
 	PROP_CLIENT_CERT_PASSWORD_FLAGS,
@@ -108,6 +109,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSetting8021x,
 	PROP_PHASE2_SUBJECT_MATCH,
 	PROP_PHASE2_ALTSUBJECT_MATCHES,
 	PROP_PHASE2_DOMAIN_SUFFIX_MATCH,
+	PROP_PHASE2_DOMAIN_MATCH,
 	PROP_PHASE2_CLIENT_CERT,
 	PROP_PHASE2_CLIENT_CERT_PASSWORD,
 	PROP_PHASE2_CLIENT_CERT_PASSWORD_FLAGS,
@@ -135,45 +137,47 @@ typedef struct {
 	char *pac_file;
 	GBytes *ca_cert;
 	char *ca_cert_password;
-	NMSettingSecretFlags ca_cert_password_flags;
 	char *ca_path;
 	char *subject_match;
 	GSList *altsubject_matches;
 	char *domain_suffix_match;
+	char *domain_match;
 	GBytes *client_cert;
 	char *client_cert_password;
-	NMSettingSecretFlags client_cert_password_flags;
 	char *phase1_peapver;
 	char *phase1_peaplabel;
 	char *phase1_fast_provisioning;
-	NMSetting8021xAuthFlags phase1_auth_flags;
 	char *phase2_auth;
 	char *phase2_autheap;
 	GBytes *phase2_ca_cert;
 	char *phase2_ca_cert_password;
-	NMSettingSecretFlags phase2_ca_cert_password_flags;
 	char *phase2_ca_path;
 	char *phase2_subject_match;
 	GSList *phase2_altsubject_matches;
 	char *phase2_domain_suffix_match;
+	char *phase2_domain_match;
 	GBytes *phase2_client_cert;
 	char *phase2_client_cert_password;
-	NMSettingSecretFlags phase2_client_cert_password_flags;
 	char *password;
-	NMSettingSecretFlags password_flags;
 	GBytes *password_raw;
-	NMSettingSecretFlags password_raw_flags;
 	char *pin;
-	NMSettingSecretFlags pin_flags;
 	GBytes *private_key;
 	char *private_key_password;
-	NMSettingSecretFlags private_key_password_flags;
 	GBytes *phase2_private_key;
 	char *phase2_private_key_password;
-	NMSettingSecretFlags phase2_private_key_password_flags;
-	gboolean system_ca_certs;
 	int auth_timeout;
-	gboolean optional;
+	NMSetting8021xAuthFlags phase1_auth_flags;
+	NMSettingSecretFlags ca_cert_password_flags;
+	NMSettingSecretFlags client_cert_password_flags;
+	NMSettingSecretFlags phase2_ca_cert_password_flags;
+	NMSettingSecretFlags phase2_client_cert_password_flags;
+	NMSettingSecretFlags password_flags;
+	NMSettingSecretFlags password_raw_flags;
+	NMSettingSecretFlags pin_flags;
+	NMSettingSecretFlags private_key_password_flags;
+	NMSettingSecretFlags phase2_private_key_password_flags;
+	bool optional:1;
+	bool system_ca_certs:1;
 } NMSetting8021xPrivate;
 
 G_DEFINE_TYPE (NMSetting8021x, nm_setting_802_1x, NM_TYPE_SETTING)
@@ -1245,6 +1249,22 @@ nm_setting_802_1x_get_domain_suffix_match (NMSetting8021x *setting)
 }
 
 /**
+ * nm_setting_802_1x_get_domain_match:
+ * @setting: the #NMSetting8021x
+ *
+ * Returns: the #NMSetting8021x:domain-match property.
+ *
+ * Since: 1.24
+ **/
+const char *
+nm_setting_802_1x_get_domain_match (NMSetting8021x *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NULL);
+
+	return NM_SETTING_802_1X_GET_PRIVATE (setting)->domain_match;
+}
+
+/**
  * nm_setting_802_1x_get_client_cert_scheme:
  * @setting: the #NMSetting8021x
  *
@@ -1692,6 +1712,22 @@ nm_setting_802_1x_get_phase2_domain_suffix_match (NMSetting8021x *setting)
 }
 
 /**
+ * nm_setting_802_1x_get_phase2_domain_match:
+ * @setting: the #NMSetting8021x
+ *
+ * Returns: the #NMSetting8021x:phase2-domain-match property.
+ *
+ * Since: 1.24
+ **/
+const char *
+nm_setting_802_1x_get_phase2_domain_match (NMSetting8021x *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_802_1X (setting), NULL);
+
+	return NM_SETTING_802_1X_GET_PRIVATE (setting)->phase2_domain_match;
+}
+
+/**
  * nm_setting_802_1x_get_phase2_altsubject_match:
  * @setting: the #NMSettingConnection
  * @i: the zero-based index of the array of "phase 2" altSubjectName matches
@@ -3074,6 +3110,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_DOMAIN_SUFFIX_MATCH:
 		g_value_set_string (value, priv->domain_suffix_match);
 		break;
+	case PROP_DOMAIN_MATCH:
+		g_value_set_string (value, priv->domain_match);
+		break;
 	case PROP_CLIENT_CERT:
 		g_value_set_boxed (value, priv->client_cert);
 		break;
@@ -3122,6 +3161,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_PHASE2_DOMAIN_SUFFIX_MATCH:
 		g_value_set_string (value, priv->phase2_domain_suffix_match);
 		break;
+	case PROP_PHASE2_DOMAIN_MATCH:
+		g_value_set_string (value, priv->phase2_domain_match);
+		break;
 	case PROP_PHASE2_CLIENT_CERT:
 		g_value_set_boxed (value, priv->phase2_client_cert);
 		break;
@@ -3233,6 +3275,10 @@ set_property (GObject *object, guint prop_id,
 		g_free (priv->domain_suffix_match);
 		priv->domain_suffix_match = nm_strdup_not_empty (g_value_get_string (value));
 		break;
+	case PROP_DOMAIN_MATCH:
+		g_free (priv->domain_match);
+		priv->domain_match = nm_strdup_not_empty (g_value_get_string (value));
+		break;
 	case PROP_CLIENT_CERT:
 		g_bytes_unref (priv->client_cert);
 		priv->client_cert = g_value_dup_boxed (value);
@@ -3294,6 +3340,10 @@ set_property (GObject *object, guint prop_id,
 		g_free (priv->phase2_domain_suffix_match);
 		priv->phase2_domain_suffix_match = nm_strdup_not_empty (g_value_get_string (value));
 		break;
+	case PROP_PHASE2_DOMAIN_MATCH:
+		g_free (priv->phase2_domain_match);
+		priv->phase2_domain_match = nm_strdup_not_empty (g_value_get_string (value));
+		break;
 	case PROP_PHASE2_CLIENT_CERT:
 		g_bytes_unref (priv->phase2_client_cert);
 		priv->phase2_client_cert = g_value_dup_boxed (value);
@@ -3656,6 +3706,8 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *klass)
 	 * the authentication server.  If a matching dNSName is found, this
 	 * constraint is met.  If no dNSName values are present, this constraint is
 	 * matched against SubjectName CN using same suffix match comparison.
+	 * Since version 1.24, multiple valid FQDNs can be passed as a ";" delimited
+	 * list.
 	 *
 	 * Since: 1.2
 	 **/
@@ -3672,6 +3724,30 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *klass)
 	                         G_PARAM_STATIC_STRINGS);
 
 	/**
+	 * NMSetting8021x:domain-match:
+	 *
+	 * Constraint for server domain name. If set, this list of FQDNs is used as
+	 * a match requirement for dNSName element(s) of the certificate presented
+	 * by the authentication server.  If a matching dNSName is found, this
+	 * constraint is met.  If no dNSName values are present, this constraint is
+	 * matched against SubjectName CN using the same comparison.
+	 * Multiple valid FQDNs can be passed as a ";" delimited list.
+	 *
+	 * Since: 1.24
+	 **/
+	/* ---ifcfg-rh---
+	 * property: domain-match
+	 * description: Value to match domain of server certificate against.
+	 * variable: IEEE_8021X_DOMAIN_MATCH(+)
+	 * ---end---
+	 */
+	obj_properties[PROP_DOMAIN_MATCH] =
+	    g_param_spec_string (NM_SETTING_802_1X_DOMAIN_MATCH, "", "",
+	                         NULL,
+	                         G_PARAM_READWRITE |
+	                         G_PARAM_STATIC_STRINGS);
+
+	/**
 	 * NMSetting8021x:client-cert:
 	 *
 	 * Contains the client certificate if used by the EAP method specified in
@@ -3828,7 +3904,6 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *klass)
 	obj_properties[PROP_PHASE1_AUTH_FLAGS] =
 	    g_param_spec_uint (NM_SETTING_802_1X_PHASE1_AUTH_FLAGS, "", "",
 	                       0, G_MAXUINT32, NM_SETTING_802_1X_AUTH_FLAGS_NONE,
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_READWRITE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -4007,6 +4082,8 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *klass)
 	 * a matching dNSName is found, this constraint is met.  If no dNSName
 	 * values are present, this constraint is matched against SubjectName CN
 	 * using same suffix match comparison.
+	 * Since version 1.24, multiple valid FQDNs can be passed as a ";" delimited
+	 * list.
 	 *
 	 * Since: 1.2
 	 **/
@@ -4023,6 +4100,31 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *klass)
 	                         G_PARAM_STATIC_STRINGS);
 
 	/**
+	 * NMSetting8021x:phase2-domain-match:
+	 *
+	 * Constraint for server domain name. If set, this list of FQDNs is used as
+	 * a match requirement for dNSName element(s) of the certificate presented
+	 * by the authentication server during the inner "phase 2" authentication.
+	 * If a matching dNSName is found, this constraint is met.  If no dNSName
+	 * values are present, this constraint is matched against SubjectName CN
+	 * using the same comparison.
+	 * Multiple valid FQDNs can be passed as a ";" delimited list.
+	 *
+	 * Since: 1.24
+	 **/
+	/* ---ifcfg-rh---
+	 * property: phase2-domain-match
+	 * description: Value to match domain of server certificate for phase 2 against.
+	 * variable: IEEE_8021X_PHASE2_DOMAIN_MATCH(+)
+	 * ---end---
+	 */
+	obj_properties[PROP_PHASE2_DOMAIN_MATCH] =
+	    g_param_spec_string (NM_SETTING_802_1X_PHASE2_DOMAIN_MATCH, "", "",
+	                         NULL,
+	                         G_PARAM_READWRITE |
+	                         G_PARAM_STATIC_STRINGS);
+
+	/**
 	 * NMSetting8021x:phase2-client-cert:
 	 *
 	 * Contains the "phase 2" client certificate if used by the EAP method
@@ -4401,7 +4503,6 @@ nm_setting_802_1x_class_init (NMSetting8021xClass *klass)
 	    g_param_spec_boolean (NM_SETTING_802_1X_SYSTEM_CA_CERTS, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
diff --git a/libnm-core/nm-setting-8021x.h b/libnm-core/nm-setting-8021x.h
index 1bcca30c..6b433aa0 100644
--- a/libnm-core/nm-setting-8021x.h
+++ b/libnm-core/nm-setting-8021x.h
@@ -104,6 +104,7 @@ typedef enum { /*< flags, underscore_name=nm_setting_802_1x_auth_flags >*/
 #define NM_SETTING_802_1X_SUBJECT_MATCH "subject-match"
 #define NM_SETTING_802_1X_ALTSUBJECT_MATCHES "altsubject-matches"
 #define NM_SETTING_802_1X_DOMAIN_SUFFIX_MATCH "domain-suffix-match"
+#define NM_SETTING_802_1X_DOMAIN_MATCH "domain-match"
 #define NM_SETTING_802_1X_CLIENT_CERT "client-cert"
 #define NM_SETTING_802_1X_CLIENT_CERT_PASSWORD "client-cert-password"
 #define NM_SETTING_802_1X_CLIENT_CERT_PASSWORD_FLAGS "client-cert-password-flags"
@@ -120,6 +121,7 @@ typedef enum { /*< flags, underscore_name=nm_setting_802_1x_auth_flags >*/
 #define NM_SETTING_802_1X_PHASE2_SUBJECT_MATCH "phase2-subject-match"
 #define NM_SETTING_802_1X_PHASE2_ALTSUBJECT_MATCHES "phase2-altsubject-matches"
 #define NM_SETTING_802_1X_PHASE2_DOMAIN_SUFFIX_MATCH "phase2-domain-suffix-match"
+#define NM_SETTING_802_1X_PHASE2_DOMAIN_MATCH "phase2-domain-match"
 #define NM_SETTING_802_1X_PHASE2_CLIENT_CERT "phase2-client-cert"
 #define NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD "phase2-client-cert-password"
 #define NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD_FLAGS "phase2-client-cert-password-flags"
@@ -230,6 +232,9 @@ void              nm_setting_802_1x_clear_altsubject_matches         (NMSetting8
 NM_AVAILABLE_IN_1_2
 const char *      nm_setting_802_1x_get_domain_suffix_match          (NMSetting8021x *setting);
 
+NM_AVAILABLE_IN_1_24
+const char *      nm_setting_802_1x_get_domain_match                 (NMSetting8021x *setting);
+
 NMSetting8021xCKScheme nm_setting_802_1x_get_client_cert_scheme      (NMSetting8021x *setting);
 GBytes *               nm_setting_802_1x_get_client_cert_blob        (NMSetting8021x *setting);
 const char *           nm_setting_802_1x_get_client_cert_path        (NMSetting8021x *setting);
@@ -287,6 +292,9 @@ void              nm_setting_802_1x_clear_phase2_altsubject_matches         (NMS
 NM_AVAILABLE_IN_1_2
 const char *      nm_setting_802_1x_get_phase2_domain_suffix_match          (NMSetting8021x *setting);
 
+NM_AVAILABLE_IN_1_24
+const char *      nm_setting_802_1x_get_phase2_domain_match                 (NMSetting8021x *setting);
+
 NMSetting8021xCKScheme nm_setting_802_1x_get_phase2_client_cert_scheme   (NMSetting8021x *setting);
 GBytes *               nm_setting_802_1x_get_phase2_client_cert_blob     (NMSetting8021x *setting);
 const char *           nm_setting_802_1x_get_phase2_client_cert_path     (NMSetting8021x *setting);
diff --git a/libnm-core/nm-setting-adsl.c b/libnm-core/nm-setting-adsl.c
index e9b86669..c952353d 100644
--- a/libnm-core/nm-setting-adsl.c
+++ b/libnm-core/nm-setting-adsl.c
@@ -158,7 +158,8 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		                     _("property is missing"));
 		g_prefix_error (error, "%s.%s: ", NM_SETTING_ADSL_SETTING_NAME, NM_SETTING_ADSL_USERNAME);
 		return FALSE;
-	} else if (!strlen (priv->username)) {
+	}
+	if (!priv->username[0]) {
 		g_set_error_literal (error,
 		                     NM_CONNECTION_ERROR,
 		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -167,10 +168,9 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (   !priv->protocol
-	    || (   strcmp (priv->protocol, NM_SETTING_ADSL_PROTOCOL_PPPOA)
-	        && strcmp (priv->protocol, NM_SETTING_ADSL_PROTOCOL_PPPOE)
-	        && strcmp (priv->protocol, NM_SETTING_ADSL_PROTOCOL_IPOATM))){
+	if (!NM_IN_STRSET (priv->protocol, NM_SETTING_ADSL_PROTOCOL_PPPOA,
+	                                   NM_SETTING_ADSL_PROTOCOL_PPPOE,
+	                                   NM_SETTING_ADSL_PROTOCOL_IPOATM)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -180,9 +180,9 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (   priv->encapsulation
-	    && (   strcmp (priv->encapsulation, NM_SETTING_ADSL_ENCAPSULATION_VCMUX)
-	        && strcmp (priv->encapsulation, NM_SETTING_ADSL_ENCAPSULATION_LLC) )) {
+	if (!NM_IN_STRSET (priv->encapsulation, NULL,
+	                                        NM_SETTING_ADSL_ENCAPSULATION_VCMUX,
+	                                        NM_SETTING_ADSL_ENCAPSULATION_LLC)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
diff --git a/libnm-core/nm-setting-bluetooth.c b/libnm-core/nm-setting-bluetooth.c
index f175e5c0..14a1317b 100644
--- a/libnm-core/nm-setting-bluetooth.c
+++ b/libnm-core/nm-setting-bluetooth.c
@@ -295,7 +295,7 @@ nm_setting_bluetooth_class_init (NMSettingBluetoothClass *klass)
 	                         G_PARAM_READWRITE |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
-	_nm_properties_override_gobj (properties_override, obj_properties[PROP_BDADDR], &nm_sett_info_propert_type_mac_addrees);
+	_nm_properties_override_gobj (properties_override, obj_properties[PROP_BDADDR], &nm_sett_info_propert_type_mac_address);
 
 	/**
 	 * NMSettingBluetooth:type:
diff --git a/libnm-core/nm-setting-bond.c b/libnm-core/nm-setting-bond.c
index f3ae9ef5..8986e434 100644
--- a/libnm-core/nm-setting-bond.c
+++ b/libnm-core/nm-setting-bond.c
@@ -44,54 +44,285 @@ G_DEFINE_TYPE (NMSettingBond, nm_setting_bond, NM_TYPE_SETTING)
 
 /*****************************************************************************/
 
+static const char *const valid_options_lst[] = {
+	/* mode must be the first element. nm-device-bond.c relies on that. */
+	NM_SETTING_BOND_OPTION_MODE,
+	NM_SETTING_BOND_OPTION_MIIMON,
+	NM_SETTING_BOND_OPTION_DOWNDELAY,
+	NM_SETTING_BOND_OPTION_UPDELAY,
+	NM_SETTING_BOND_OPTION_ARP_INTERVAL,
+	NM_SETTING_BOND_OPTION_ARP_IP_TARGET,
+	NM_SETTING_BOND_OPTION_ARP_VALIDATE,
+	NM_SETTING_BOND_OPTION_PRIMARY,
+	NM_SETTING_BOND_OPTION_PRIMARY_RESELECT,
+	NM_SETTING_BOND_OPTION_FAIL_OVER_MAC,
+	NM_SETTING_BOND_OPTION_USE_CARRIER,
+	NM_SETTING_BOND_OPTION_AD_SELECT,
+	NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY,
+	NM_SETTING_BOND_OPTION_RESEND_IGMP,
+	NM_SETTING_BOND_OPTION_LACP_RATE,
+	NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,
+	NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO,
+	NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,
+	NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY,
+	NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE,
+	NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS,
+	NM_SETTING_BOND_OPTION_MIN_LINKS,
+	NM_SETTING_BOND_OPTION_NUM_GRAT_ARP,
+	NM_SETTING_BOND_OPTION_NUM_UNSOL_NA,
+	NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE,
+	NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,
+	NM_SETTING_BOND_OPTION_LP_INTERVAL,
+	NULL,
+};
+
 typedef struct {
-	const char *opt;
 	const char *val;
-	guint opt_type;
+	NMBondOptionType opt_type;
 	guint min;
 	guint max;
-	char *list[10];
-} BondDefault;
-
-static const BondDefault defaults[] = {
-	{ NM_SETTING_BOND_OPTION_MODE,             "balance-rr", NM_BOND_OPTION_TYPE_BOTH, 0, 6,
-	  { "balance-rr", "active-backup", "balance-xor", "broadcast", "802.3ad", "balance-tlb", "balance-alb", NULL } },
-	{ NM_SETTING_BOND_OPTION_MIIMON,           "100",        NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT },
-	{ NM_SETTING_BOND_OPTION_DOWNDELAY,        "0",          NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT },
-	{ NM_SETTING_BOND_OPTION_UPDELAY,          "0",          NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT },
-	{ NM_SETTING_BOND_OPTION_ARP_INTERVAL,     "0",          NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT },
-	{ NM_SETTING_BOND_OPTION_ARP_IP_TARGET,    "",           NM_BOND_OPTION_TYPE_IP },
-	{ NM_SETTING_BOND_OPTION_ARP_VALIDATE,     "none",       NM_BOND_OPTION_TYPE_BOTH, 0, 6,
-	  { "none", "active", "backup", "all", "filter", "filter_active", "filter_backup", NULL } },
-	{ NM_SETTING_BOND_OPTION_PRIMARY,          "",           NM_BOND_OPTION_TYPE_IFNAME },
-	{ NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, "always",     NM_BOND_OPTION_TYPE_BOTH, 0, 2,
-	  { "always", "better", "failure", NULL } },
-	{ NM_SETTING_BOND_OPTION_FAIL_OVER_MAC,    "none",       NM_BOND_OPTION_TYPE_BOTH, 0, 2,
-	  { "none", "active", "follow", NULL } },
-	{ NM_SETTING_BOND_OPTION_USE_CARRIER,      "1",          NM_BOND_OPTION_TYPE_INT, 0, 1 },
-	{ NM_SETTING_BOND_OPTION_AD_SELECT,        "stable",     NM_BOND_OPTION_TYPE_BOTH, 0, 2,
-	  { "stable", "bandwidth", "count", NULL } },
-	{ NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, "layer2",     NM_BOND_OPTION_TYPE_BOTH, 0, 4,
-	  { "layer2", "layer3+4", "layer2+3", "encap2+3", "encap3+4", NULL } },
-	{ NM_SETTING_BOND_OPTION_RESEND_IGMP,      "1",          NM_BOND_OPTION_TYPE_INT, 0, 255 },
-	{ NM_SETTING_BOND_OPTION_LACP_RATE,        "slow",       NM_BOND_OPTION_TYPE_BOTH, 0, 1,
-	  { "slow", "fast", NULL } },
-	{ NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,     "",           NM_BOND_OPTION_TYPE_IFNAME },
-	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO,"65535",      NM_BOND_OPTION_TYPE_INT, 1, 65535 },
-	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,  NULL,         NM_BOND_OPTION_TYPE_MAC },
-	{ NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY, "0",          NM_BOND_OPTION_TYPE_INT, 0, 1023},
-	{ NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE,"0",          NM_BOND_OPTION_TYPE_INT, 0, 1},
-	{ NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS,  "any",        NM_BOND_OPTION_TYPE_BOTH, 0, 1, {"any", "all"}},
-	{ NM_SETTING_BOND_OPTION_MIN_LINKS,        "0",          NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT },
-	{ NM_SETTING_BOND_OPTION_NUM_GRAT_ARP,     "1",          NM_BOND_OPTION_TYPE_INT, 0, 255 },
-	{ NM_SETTING_BOND_OPTION_NUM_UNSOL_NA,     "1",          NM_BOND_OPTION_TYPE_INT, 0, 255 },
-	{ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE,"1",          NM_BOND_OPTION_TYPE_INT, 0, 65535 },
-	{ NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,   "1",          NM_BOND_OPTION_TYPE_INT, 0, 1 },
-	{ NM_SETTING_BOND_OPTION_LP_INTERVAL,      "1",          NM_BOND_OPTION_TYPE_INT, 1, G_MAXINT },
-};
+	const char *const*list;
+} OptionMeta;
+
+static gboolean
+_nm_assert_bond_meta (const OptionMeta *option_meta)
+{
+	nm_assert (option_meta);
+
+	switch (option_meta->opt_type) {
+	case NM_BOND_OPTION_TYPE_BOTH:
+		nm_assert (option_meta->val);
+		nm_assert (option_meta->list);
+		nm_assert (option_meta->list[0]);
+		nm_assert (option_meta->min == 0);
+		nm_assert (option_meta->max == NM_PTRARRAY_LEN (option_meta->list) - 1);
+		nm_assert (g_strv_contains (option_meta->list, option_meta->val));
+		return TRUE;
+	case NM_BOND_OPTION_TYPE_INT:
+		nm_assert (option_meta->val);
+		nm_assert (!option_meta->list);
+		nm_assert (option_meta->min < option_meta->max);
+		nm_assert (NM_STRCHAR_ALL (option_meta->val, ch, g_ascii_isdigit (ch)));
+		nm_assert (NM_STRCHAR_ALL (option_meta->val, ch, g_ascii_isdigit (ch)));
+		nm_assert (({
+		              _nm_utils_ascii_str_to_uint64 (option_meta->val, 10, option_meta->min, option_meta->max, 0);
+		              errno == 0;
+		            }));
+		return TRUE;
+	case NM_BOND_OPTION_TYPE_IP:
+	case NM_BOND_OPTION_TYPE_IFNAME:
+		nm_assert (option_meta->val);
+		/* fall-through */
+	case NM_BOND_OPTION_TYPE_MAC:
+		nm_assert (!option_meta->list);
+		nm_assert (option_meta->min == 0);
+		nm_assert (option_meta->max == 0);
+		return TRUE;
+	}
+
+	nm_assert_not_reached ();
+	return FALSE;
+}
+
+static char const *const _option_default_strv_ad_select[]        = NM_MAKE_STRV ("stable", "bandwidth", "count");
+static char const *const _option_default_strv_arp_all_targets[]  = NM_MAKE_STRV ("any", "all");
+static char const *const _option_default_strv_arp_validate[]     = NM_MAKE_STRV ("none", "active", "backup", "all", "filter", "filter_active", "filter_backup");
+static char const *const _option_default_strv_fail_over_mac[]    = NM_MAKE_STRV ("none", "active", "follow");
+static char const *const _option_default_strv_lacp_rate[]        = NM_MAKE_STRV ("slow", "fast");
+static char const *const _option_default_strv_mode[]             = NM_MAKE_STRV ("balance-rr", "active-backup", "balance-xor", "broadcast", "802.3ad", "balance-tlb", "balance-alb");
+static char const *const _option_default_strv_primary_reselect[] = NM_MAKE_STRV ("always", "better", "failure");
+static char const *const _option_default_strv_xmit_hash_policy[] = NM_MAKE_STRV ("layer2", "layer3+4", "layer2+3", "encap2+3", "encap3+4");
+
+static
+NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE (
+	_get_option_meta,
+	OptionMeta,
+	{
+		G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (LIST) == G_N_ELEMENTS (valid_options_lst) - 1);
+
+		if (NM_MORE_ASSERT_ONCE (5)) {
+			int i;
+
+			nm_assert (G_N_ELEMENTS (LIST) == NM_PTRARRAY_LEN (valid_options_lst));
+			for (i = 0; i < G_N_ELEMENTS (LIST); i++)
+				_nm_assert_bond_meta (&LIST[i].value);
+			nm_assert (nm_streq (valid_options_lst[0], NM_SETTING_BOND_OPTION_MODE));
+		}
+	},
+	{ return NULL; },
+	{ NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,      { "",           NM_BOND_OPTION_TYPE_IFNAME                                                   } },
+	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, { "65535",      NM_BOND_OPTION_TYPE_INT,   1, 65535                                          } },
+	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,   { NULL,         NM_BOND_OPTION_TYPE_MAC                                                      } },
+	{ NM_SETTING_BOND_OPTION_AD_SELECT,         { "stable",     NM_BOND_OPTION_TYPE_BOTH,  0, 2,       _option_default_strv_ad_select        } },
+	{ NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY,  { "0",          NM_BOND_OPTION_TYPE_INT,   0, 1023                                           } },
+	{ NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE, { "0",          NM_BOND_OPTION_TYPE_INT,   0, 1                                              } },
+	{ NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS,   { "any",        NM_BOND_OPTION_TYPE_BOTH,  0, 1,       _option_default_strv_arp_all_targets  } },
+	{ NM_SETTING_BOND_OPTION_ARP_INTERVAL,      { "0",          NM_BOND_OPTION_TYPE_INT,   0, G_MAXINT                                       } },
+	{ NM_SETTING_BOND_OPTION_ARP_IP_TARGET,     { "",           NM_BOND_OPTION_TYPE_IP                                                       } },
+	{ NM_SETTING_BOND_OPTION_ARP_VALIDATE,      { "none",       NM_BOND_OPTION_TYPE_BOTH,  0, 6,       _option_default_strv_arp_validate     } },
+	{ NM_SETTING_BOND_OPTION_DOWNDELAY,         { "0",          NM_BOND_OPTION_TYPE_INT,   0, G_MAXINT                                       } },
+	{ NM_SETTING_BOND_OPTION_FAIL_OVER_MAC,     { "none",       NM_BOND_OPTION_TYPE_BOTH,  0, 2,       _option_default_strv_fail_over_mac    } },
+	{ NM_SETTING_BOND_OPTION_LACP_RATE,         { "slow",       NM_BOND_OPTION_TYPE_BOTH,  0, 1,       _option_default_strv_lacp_rate        } },
+	{ NM_SETTING_BOND_OPTION_LP_INTERVAL,       { "1",          NM_BOND_OPTION_TYPE_INT,   1, G_MAXINT                                       } },
+	{ NM_SETTING_BOND_OPTION_MIIMON,            { "100",        NM_BOND_OPTION_TYPE_INT,   0, G_MAXINT                                       } },
+	{ NM_SETTING_BOND_OPTION_MIN_LINKS,         { "0",          NM_BOND_OPTION_TYPE_INT,   0, G_MAXINT                                       } },
+	{ NM_SETTING_BOND_OPTION_MODE,              { "balance-rr", NM_BOND_OPTION_TYPE_BOTH,  0, 6,       _option_default_strv_mode             } },
+	{ NM_SETTING_BOND_OPTION_NUM_GRAT_ARP,      { "1",          NM_BOND_OPTION_TYPE_INT,   0, 255                                            } },
+	{ NM_SETTING_BOND_OPTION_NUM_UNSOL_NA,      { "1",          NM_BOND_OPTION_TYPE_INT,   0, 255                                            } },
+	{ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, { "1",          NM_BOND_OPTION_TYPE_INT,   0, 65535                                          } },
+	{ NM_SETTING_BOND_OPTION_PRIMARY,           { "",           NM_BOND_OPTION_TYPE_IFNAME                                                   } },
+	{ NM_SETTING_BOND_OPTION_PRIMARY_RESELECT,  { "always",     NM_BOND_OPTION_TYPE_BOTH,  0, 2,       _option_default_strv_primary_reselect } },
+	{ NM_SETTING_BOND_OPTION_RESEND_IGMP,       { "1",          NM_BOND_OPTION_TYPE_INT,   0, 255                                            } },
+	{ NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,    { "1",          NM_BOND_OPTION_TYPE_INT,   0, 1                                              } },
+	{ NM_SETTING_BOND_OPTION_UPDELAY,           { "0",          NM_BOND_OPTION_TYPE_INT,   0, G_MAXINT                                       } },
+	{ NM_SETTING_BOND_OPTION_USE_CARRIER,       { "1",          NM_BOND_OPTION_TYPE_INT,   0, 1                                              } },
+	{ NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY,  { "layer2",     NM_BOND_OPTION_TYPE_BOTH,  0, 4,       _option_default_strv_xmit_hash_policy } },
+);
 
 /*****************************************************************************/
 
+#define BIT(x) (((guint32) 1) << (x))
+
+static
+NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
+	_bond_option_unsupp_mode,
+	guint32,
+	{ ; },
+	{ return 0; },
+	{ NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,      ~(BIT (NM_BOND_MODE_ACTIVEBACKUP) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
+	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, ~(BIT (NM_BOND_MODE_8023AD)) },
+	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,   ~(BIT (NM_BOND_MODE_8023AD)) },
+	{ NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY,  ~(BIT (NM_BOND_MODE_8023AD)) },
+	{ NM_SETTING_BOND_OPTION_ARP_INTERVAL,       (BIT (NM_BOND_MODE_8023AD)       | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
+	{ NM_SETTING_BOND_OPTION_ARP_IP_TARGET,      (BIT (NM_BOND_MODE_8023AD)       | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
+	{ NM_SETTING_BOND_OPTION_ARP_VALIDATE,       (BIT (NM_BOND_MODE_8023AD)       | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
+	{ NM_SETTING_BOND_OPTION_LACP_RATE,         ~(BIT (NM_BOND_MODE_8023AD)) },
+	{ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, ~(BIT (NM_BOND_MODE_ROUNDROBIN)) },
+	{ NM_SETTING_BOND_OPTION_PRIMARY,           ~(BIT (NM_BOND_MODE_ACTIVEBACKUP) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
+	{ NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,    ~(BIT (NM_BOND_MODE_TLB)) },
+)
+
+gboolean
+_nm_setting_bond_option_supported (const char *option, NMBondMode mode)
+{
+	nm_assert (option);
+	nm_assert (mode != NM_BOND_MODE_UNKNOWN);
+	nm_assert (_NM_INT_NOT_NEGATIVE (mode) && mode < 32);
+
+	return !NM_FLAGS_ANY (_bond_option_unsupp_mode (option), BIT (mode));
+}
+
+static const char*
+_bond_get_option (NMSettingBond *self,
+                  const char *option)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BOND (self), NULL);
+	g_return_val_if_fail (option, NULL);
+
+	return g_hash_table_lookup (NM_SETTING_BOND_GET_PRIVATE (self)->options, option);
+}
+
+static const char*
+_bond_get_option_default (NMSettingBond *self,
+                          const char *option)
+{
+	const OptionMeta *option_meta;
+
+	g_return_val_if_fail (NM_IS_SETTING_BOND (self), NULL);
+
+	option_meta = _get_option_meta (option);
+
+	g_return_val_if_fail (option_meta, NULL);
+
+	return option_meta->val;
+}
+
+static const char*
+_bond_get_option_or_default (NMSettingBond *self,
+                             const char *option)
+{
+	return    _bond_get_option (self, option)
+	       ?: _bond_get_option_default (self, option);
+}
+
+static const char*
+_bond_get_option_normalized (NMSettingBond* self,
+                             const char* option,
+                             gboolean get_default_only)
+{
+	const char *arp_interval_str;
+	const char *mode_str;
+	gint64 arp_interval;
+	NMBondMode mode;
+	const char *value = NULL;
+
+	g_return_val_if_fail (NM_IS_SETTING_BOND (self), NULL);
+	g_return_val_if_fail (option, NULL);
+
+	mode_str = _bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_MODE);
+	mode = _nm_setting_bond_mode_from_string (mode_str);
+	g_return_val_if_fail (mode != NM_BOND_MODE_UNKNOWN, NULL);
+
+	if (!_nm_setting_bond_option_supported (option, mode))
+		return NULL;
+
+	/* Apply custom NetworkManager policies here */
+	if (!get_default_only) {
+		if (NM_IN_STRSET (option,
+		                  NM_SETTING_BOND_OPTION_UPDELAY,
+		                  NM_SETTING_BOND_OPTION_DOWNDELAY,
+		                  NM_SETTING_BOND_OPTION_MIIMON)) {
+			/* if arp_interval is explicitly set and miimon is not, then disable miimon
+			 * (and related updelay and downdelay) as recommended by the kernel docs */
+			arp_interval_str = _bond_get_option (self, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
+			arp_interval = _nm_utils_ascii_str_to_int64 (arp_interval_str, 10, 0, G_MAXINT, 0);
+
+			if (!arp_interval || _bond_get_option (self, NM_SETTING_BOND_OPTION_MIIMON)) {
+				value = _bond_get_option (self, option);
+			} else {
+				return NULL;
+			}
+		} else if (NM_IN_STRSET (option,
+		                         NM_SETTING_BOND_OPTION_NUM_GRAT_ARP,
+		                         NM_SETTING_BOND_OPTION_NUM_UNSOL_NA)) {
+			/* just get one of the 2, at kernel level they're the same bond option */
+			value = _bond_get_option (self, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP);
+			if (!value) {
+				value = _bond_get_option (self, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA);
+			}
+		} else {
+			value = _bond_get_option (self, option);
+		}
+	}
+
+	if (!value) {
+		/* Apply rules that change the default value of an option */
+		if (nm_streq (option, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM)) {
+			/* The default value depends on the current mode */
+			if (NM_IN_STRSET (mode_str, "4", "802.3ad"))
+				return "00:00:00:00:00:00";
+			else
+				return "";
+		} else {
+			return _bond_get_option_or_default (self, option);
+		}
+	}
+
+	return value;
+}
+
+const char*
+nm_setting_bond_get_option_or_default (NMSettingBond *self,
+                                       const char *option)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BOND (self), NULL);
+	g_return_val_if_fail (option, NULL);
+
+	return _bond_get_option_normalized (self,
+	                                    option,
+	                                    FALSE);
+}
+
 static int
 _atoi (const char *value)
 {
@@ -120,6 +351,26 @@ nm_setting_bond_get_num_options (NMSettingBond *setting)
 	return g_hash_table_size (NM_SETTING_BOND_GET_PRIVATE (setting)->options);
 }
 
+static int
+_get_option_sort (gconstpointer p_a, gconstpointer p_b, gpointer _unused)
+{
+	const char *a = *((const char *const*) p_a);
+	const char *b = *((const char *const*) p_b);
+
+	NM_CMP_DIRECT (nm_streq (b, NM_SETTING_BOND_OPTION_MODE),
+	               nm_streq (a, NM_SETTING_BOND_OPTION_MODE));
+	NM_CMP_DIRECT_STRCMP (a, b);
+	nm_assert_not_reached ();
+	return 0;
+}
+
+static void
+_ensure_options_idx_cache (NMSettingBondPrivate *priv)
+{
+	if (!G_UNLIKELY (priv->options_idx_cache))
+		priv->options_idx_cache = nm_utils_named_values_from_str_dict_with_sort (priv->options, NULL, _get_option_sort, NULL);
+}
+
 /**
  * nm_setting_bond_get_option:
  * @setting: the #NMSettingBond
@@ -158,8 +409,7 @@ nm_setting_bond_get_option (NMSettingBond *setting,
 	if (idx >= len)
 		return FALSE;
 
-	if (!G_UNLIKELY (priv->options_idx_cache))
-		priv->options_idx_cache = nm_utils_named_values_from_str_dict (priv->options, NULL);
+	_ensure_options_idx_cache (priv);
 
 	NM_SET_OUT (out_name, priv->options_idx_cache[idx].name);
 	NM_SET_OUT (out_value, priv->options_idx_cache[idx].value_str);
@@ -167,14 +417,14 @@ nm_setting_bond_get_option (NMSettingBond *setting,
 }
 
 static gboolean
-validate_int (const char *name, const char *value, const BondDefault *def)
+validate_int (const char *name, const char *value, const OptionMeta *option_meta)
 {
 	guint64 num;
 
 	if (!NM_STRCHAR_ALL (value, ch, g_ascii_isdigit (ch)))
 		return FALSE;
 
-	num = _nm_utils_ascii_str_to_uint64 (value, 10, def->min, def->max, G_MAXUINT64);
+	num = _nm_utils_ascii_str_to_uint64 (value, 10, option_meta->min, option_meta->max, G_MAXUINT64);
 	if (   num == G_MAXUINT64
 	    && errno != 0)
 		return FALSE;
@@ -183,17 +433,17 @@ validate_int (const char *name, const char *value, const BondDefault *def)
 }
 
 static gboolean
-validate_list (const char *name, const char *value, const BondDefault *def)
+validate_list (const char *name, const char *value, const OptionMeta *option_meta)
 {
-	guint i;
+	int i;
+
+	nm_assert (option_meta->list);
 
-	for (i = 0; i < G_N_ELEMENTS (def->list) && def->list[i]; i++) {
-		if (g_strcmp0 (def->list[i], value) == 0)
+	for (i = 0; option_meta->list[i]; i++) {
+		if (nm_streq (option_meta->list[i], value))
 			return TRUE;
 	}
-
-	/* empty validation list means all values pass */
-	return def->list[0] == NULL ? TRUE : FALSE;
+	return FALSE;
 }
 
 static gboolean
@@ -232,9 +482,6 @@ validate_ip (const char *name, const char *value)
 static gboolean
 validate_ifname (const char *name, const char *value)
 {
-	if (!value || !value[0])
-		return FALSE;
-
 	return nm_utils_ifname_valid_kernel (value, NULL);
 }
 
@@ -253,33 +500,30 @@ gboolean
 nm_setting_bond_validate_option (const char *name,
                                  const char *value)
 {
-	guint i;
+	const OptionMeta *option_meta;
 
-	if (!name || !name[0])
+	option_meta = _get_option_meta (name);
+	if (!option_meta)
 		return FALSE;
 
-	for (i = 0; i < G_N_ELEMENTS (defaults); i++) {
-		if (g_strcmp0 (defaults[i].opt, name) == 0) {
-			if (value == NULL)
-				return TRUE;
-			switch (defaults[i].opt_type) {
-			case NM_BOND_OPTION_TYPE_INT:
-				return validate_int (name, value, &defaults[i]);
-			case NM_BOND_OPTION_TYPE_STRING:
-				return validate_list (name, value, &defaults[i]);
-			case NM_BOND_OPTION_TYPE_BOTH:
-				return (   validate_int (name, value, &defaults[i])
-				        || validate_list (name, value, &defaults[i]));
-			case NM_BOND_OPTION_TYPE_IP:
-				return validate_ip (name, value);
-			case NM_BOND_OPTION_TYPE_MAC:
-				return nm_utils_hwaddr_valid (value, ETH_ALEN);
-			case NM_BOND_OPTION_TYPE_IFNAME:
-				return validate_ifname (name, value);
-			}
-			return FALSE;
-		}
+	if (!value)
+		return TRUE;
+
+	switch (option_meta->opt_type) {
+	case NM_BOND_OPTION_TYPE_INT:
+		return validate_int (name, value, option_meta);
+	case NM_BOND_OPTION_TYPE_BOTH:
+		return (   validate_int (name, value, option_meta)
+		        || validate_list (name, value, option_meta));
+	case NM_BOND_OPTION_TYPE_IP:
+		return validate_ip (name, value);
+	case NM_BOND_OPTION_TYPE_MAC:
+		return nm_utils_hwaddr_valid (value, ETH_ALEN);
+	case NM_BOND_OPTION_TYPE_IFNAME:
+		return validate_ifname (name, value);
 	}
+
+	nm_assert_not_reached ();
 	return FALSE;
 }
 
@@ -303,7 +547,7 @@ nm_setting_bond_get_option_by_name (NMSettingBond *setting,
 	if (!nm_setting_bond_validate_option (name, NULL))
 		return NULL;
 
-	return g_hash_table_lookup (NM_SETTING_BOND_GET_PRIVATE (setting)->options, name);
+	return _bond_get_option (setting, name);
 }
 
 /**
@@ -340,17 +584,6 @@ nm_setting_bond_add_option (NMSettingBond *setting,
 	nm_clear_g_free (&priv->options_idx_cache);
 	g_hash_table_insert (priv->options, g_strdup (name), g_strdup (value));
 
-	if (   !strcmp (name, NM_SETTING_BOND_OPTION_MIIMON)
-	    && strcmp (value, "0") != 0) {
-		g_hash_table_remove (priv->options, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-		g_hash_table_remove (priv->options, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
-	} else if (   !strcmp (name, NM_SETTING_BOND_OPTION_ARP_INTERVAL)
-	           && strcmp (value, "0") != 0) {
-		g_hash_table_remove (priv->options, NM_SETTING_BOND_OPTION_MIIMON);
-		g_hash_table_remove (priv->options, NM_SETTING_BOND_OPTION_DOWNDELAY);
-		g_hash_table_remove (priv->options, NM_SETTING_BOND_OPTION_UPDELAY);
-	}
-
 	_notify (setting, PROP_OPTIONS);
 
 	return TRUE;
@@ -401,16 +634,7 @@ nm_setting_bond_remove_option (NMSettingBond *setting,
 const char **
 nm_setting_bond_get_valid_options  (NMSettingBond *setting)
 {
-	static const char *array[G_N_ELEMENTS (defaults) + 1] = { NULL };
-	int i;
-
-	/* initialize the array once */
-	if (G_UNLIKELY (array[0] == NULL)) {
-		for (i = 0; i < G_N_ELEMENTS (defaults); i++)
-			array[i] = defaults[i].opt;
-		array[i] = NULL;
-	}
-	return array;
+	return (const char **) valid_options_lst;
 }
 
 /**
@@ -424,28 +648,38 @@ nm_setting_bond_get_valid_options  (NMSettingBond *setting)
 const char *
 nm_setting_bond_get_option_default (NMSettingBond *setting, const char *name)
 {
-	const char *mode;
-	guint i;
-
 	g_return_val_if_fail (NM_IS_SETTING_BOND (setting), NULL);
-	g_return_val_if_fail (nm_setting_bond_validate_option (name, NULL), NULL);
-
-	if (nm_streq (name, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM)) {
-		/* The default value depends on the current mode */
-		mode = nm_setting_bond_get_option_by_name (setting, NM_SETTING_BOND_OPTION_MODE);
-		if (   nm_streq0 (mode, "4")
-		    || nm_streq0 (mode, "802.3ad"))
-			return "00:00:00:00:00:00";
-		else
-			return "";
-	}
 
-	for (i = 0; i < G_N_ELEMENTS (defaults); i++) {
-		if (g_strcmp0 (defaults[i].opt, name) == 0)
-			return defaults[i].val;
+	if (!name) {
+		return NULL;
 	}
-	/* Any option that passes nm_setting_bond_validate_option() should also be found in defaults */
-	g_assert_not_reached ();
+
+	return _bond_get_option_normalized (setting,
+	                                    name,
+	                                    TRUE);
+}
+
+/**
+ * nm_setting_bond_get_option_normalized:
+ * @setting: the #NMSettingBond
+ * @name: the name of the option
+ *
+ * Since: 1.24
+ *
+ * Returns: the value of the bond option after normalization, which is what NetworkManager
+ *   will actually apply when activating the connection. %NULL if the option won't be applied
+ *   to the connection.
+ **/
+const char *
+nm_setting_bond_get_option_normalized (NMSettingBond *setting,
+                                       const char *name)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BOND (setting), NULL);
+	g_return_val_if_fail (name, NULL);
+
+	return _bond_get_option_normalized (setting,
+	                                    name,
+	                                    FALSE);
 }
 
 /**
@@ -458,131 +692,97 @@ nm_setting_bond_get_option_default (NMSettingBond *setting, const char *name)
 NMBondOptionType
 _nm_setting_bond_get_option_type (NMSettingBond *setting, const char *name)
 {
-	guint i;
+	const OptionMeta *option_meta;
 
 	g_return_val_if_fail (NM_IS_SETTING_BOND (setting), NM_BOND_OPTION_TYPE_INT);
-	g_return_val_if_fail (nm_setting_bond_validate_option (name, NULL), NM_BOND_OPTION_TYPE_INT);
 
-	for (i = 0; i < G_N_ELEMENTS (defaults); i++) {
-		if (nm_streq0 (defaults[i].opt, name))
-			return defaults[i].opt_type;
-	}
-	/* Any option that passes nm_setting_bond_validate_option() should also be found in defaults */
-	g_assert_not_reached ();
-}
+	option_meta = _get_option_meta (name);
 
-NMBondMode
-_nm_setting_bond_mode_from_string (const char *str)
-{
-	g_return_val_if_fail (str, NM_BOND_MODE_UNKNOWN);
+	g_return_val_if_fail (option_meta, NM_BOND_OPTION_TYPE_INT);
 
-	if (nm_streq (str, "balance-rr"))
-		return NM_BOND_MODE_ROUNDROBIN;
-	if (nm_streq (str, "active-backup"))
-		return NM_BOND_MODE_ACTIVEBACKUP;
-	if (nm_streq (str, "balance-xor"))
-		return NM_BOND_MODE_XOR;
-	if (nm_streq (str, "broadcast"))
-		return NM_BOND_MODE_BROADCAST;
-	if (nm_streq (str, "802.3ad"))
-		return NM_BOND_MODE_8023AD;
-	if (nm_streq (str, "balance-tlb"))
-		return NM_BOND_MODE_TLB;
-	if (nm_streq (str, "balance-alb"))
-		return NM_BOND_MODE_ALB;
-
-	return NM_BOND_MODE_UNKNOWN;
+	return option_meta->opt_type;
 }
 
-/*****************************************************************************/
-
-#define BIT(x) (1 << (x))
-
-static const struct {
-	const char *option;
-	NMBondMode unsupp_modes;
-} bond_unsupp_modes[] = {
-	{ NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, ~(BIT (NM_BOND_MODE_ROUNDROBIN)) },
-	{ NM_SETTING_BOND_OPTION_ARP_VALIDATE,      BIT (NM_BOND_MODE_8023AD) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB) },
-	{ NM_SETTING_BOND_OPTION_ARP_INTERVAL,      BIT (NM_BOND_MODE_8023AD) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB) },
-	{ NM_SETTING_BOND_OPTION_ARP_IP_TARGET,     BIT (NM_BOND_MODE_8023AD) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB) },
-	{ NM_SETTING_BOND_OPTION_LACP_RATE,         ~(BIT (NM_BOND_MODE_8023AD)) },
-	{ NM_SETTING_BOND_OPTION_PRIMARY,           ~(BIT (NM_BOND_MODE_ACTIVEBACKUP) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
-	{ NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,      ~(BIT (NM_BOND_MODE_ACTIVEBACKUP) | BIT (NM_BOND_MODE_TLB) | BIT (NM_BOND_MODE_ALB)) },
-	{ NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,    ~(BIT (NM_BOND_MODE_TLB)) },
-	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, ~(BIT (NM_BOND_MODE_8023AD)) },
-	{ NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM,   ~(BIT (NM_BOND_MODE_8023AD)) },
-	{ NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY,  ~(BIT (NM_BOND_MODE_8023AD)) },
-};
-
-gboolean
-_nm_setting_bond_option_supported (const char *option, NMBondMode mode)
-{
-	guint i;
-
-	for (i = 0; i < G_N_ELEMENTS (bond_unsupp_modes); i++) {
-		if (nm_streq (option, bond_unsupp_modes[i].option))
-			return !NM_FLAGS_ANY (bond_unsupp_modes[i].unsupp_modes, BIT (mode));
-	}
+NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
+	_nm_setting_bond_mode_from_string,
+	NMBondMode,
+	{ g_return_val_if_fail (name, NM_BOND_MODE_UNKNOWN); },
+	{ return NM_BOND_MODE_UNKNOWN; },
+	{ "0",             NM_BOND_MODE_ROUNDROBIN   },
+	{ "1",             NM_BOND_MODE_ACTIVEBACKUP },
+	{ "2",             NM_BOND_MODE_XOR          },
+	{ "3",             NM_BOND_MODE_BROADCAST    },
+	{ "4",             NM_BOND_MODE_8023AD       },
+	{ "5",             NM_BOND_MODE_TLB          },
+	{ "6",             NM_BOND_MODE_ALB          },
+	{ "802.3ad",       NM_BOND_MODE_8023AD       },
+	{ "active-backup", NM_BOND_MODE_ACTIVEBACKUP },
+	{ "balance-alb",   NM_BOND_MODE_ALB          },
+	{ "balance-rr",    NM_BOND_MODE_ROUNDROBIN   },
+	{ "balance-tlb",   NM_BOND_MODE_TLB          },
+	{ "balance-xor",   NM_BOND_MODE_XOR          },
+	{ "broadcast",     NM_BOND_MODE_BROADCAST    },
+);
 
-	return TRUE;
-}
+/*****************************************************************************/
 
 static gboolean
 verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
+	NMSettingBond *self = NM_SETTING_BOND (setting);
 	NMSettingBondPrivate *priv = NM_SETTING_BOND_GET_PRIVATE (setting);
-	GHashTableIter iter;
-	const char *key, *value;
-	int mode, miimon = 0, arp_interval = 0;
-	int num_grat_arp = -1, num_unsol_na = -1;
-	const char *mode_orig, *mode_new;
+	int miimon;
+	int arp_interval;
+	int num_grat_arp;
+	int num_unsol_na;
+	const char *mode_str;
 	const char *arp_ip_target = NULL;
 	const char *lacp_rate;
 	const char *primary;
 	NMBondMode bond_mode;
+	guint i;
+	const NMUtilsNamedValue *n;
 
-	g_hash_table_iter_init (&iter, priv->options);
-	while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) {
-		if (!value[0] || !nm_setting_bond_validate_option (key, value)) {
-			g_set_error (error,
-			             NM_CONNECTION_ERROR,
-			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			             _("invalid option '%s' or its value '%s'"),
-			             key, value);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
-			return FALSE;
+	_ensure_options_idx_cache (priv);
+
+	if (priv->options_idx_cache) {
+		for (i = 0; priv->options_idx_cache[i].name; i++) {
+			n = &priv->options_idx_cache[i];
+
+			if (   !n->value_str
+			    || !nm_setting_bond_validate_option (n->name, n->value_str)) {
+				g_set_error (error,
+				             NM_CONNECTION_ERROR,
+				             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				             _("invalid option '%s' or its value '%s'"),
+				             n->name, n->value_str);
+				g_prefix_error (error,
+				                "%s.%s: ",
+				                NM_SETTING_BOND_SETTING_NAME,
+				                NM_SETTING_BOND_OPTIONS);
+				return FALSE;
+			}
 		}
 	}
 
-	value = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_MIIMON);
-	if (value)
-		miimon = atoi (value);
-	value = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-	if (value)
-		arp_interval = atoi (value);
-	value = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP);
-	if (value)
-		num_grat_arp = atoi (value);
-	value = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA);
-	if (value)
-		num_unsol_na = atoi (value);
-
-	/* Can only set one of miimon and arp_interval */
-	if (miimon > 0 && arp_interval > 0) {
-		g_set_error (error,
-		             NM_CONNECTION_ERROR,
-		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-		             _("only one of '%s' and '%s' can be set"),
-		             NM_SETTING_BOND_OPTION_MIIMON,
-		             NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-		g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
-		return FALSE;
-	}
+	miimon = _atoi (_bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_MIIMON));
+	arp_interval = _atoi (_bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_ARP_INTERVAL));
+	num_grat_arp = _atoi (_bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP));
+	num_unsol_na = _atoi (_bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA));
+
+	/* Option restrictions:
+	 *
+	 * arp_interval conflicts [ alb, tlb ]
+	 * arp_interval needs arp_ip_target
+	 * arp_validate does not work with [ BOND_MODE_8023AD, BOND_MODE_TLB, BOND_MODE_ALB ]
+	 * downdelay needs miimon
+	 * updelay needs miimon
+	 * primary needs [ active-backup, tlb, alb ]
+	 */
 
 	/* Verify bond mode */
-	mode_orig = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_MODE);
-	if (!mode_orig) {
+	mode_str = _bond_get_option (self, NM_SETTING_BOND_OPTION_MODE);
+	if (!mode_str) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -591,34 +791,42 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
 		return FALSE;
 	}
-	mode = nm_utils_bond_mode_string_to_int (mode_orig);
-	if (mode == -1) {
+	bond_mode = _nm_setting_bond_mode_from_string (mode_str);
+	if (bond_mode == NM_BOND_MODE_UNKNOWN) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 		             _("'%s' is not a valid value for '%s'"),
-		             value, NM_SETTING_BOND_OPTION_MODE);
-		g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+		             mode_str,
+		             NM_SETTING_BOND_OPTION_MODE);
+		g_prefix_error (error,
+		                "%s.%s: ",
+		                NM_SETTING_BOND_SETTING_NAME,
+		                NM_SETTING_BOND_OPTIONS);
 		return FALSE;
 	}
-	mode_new = nm_utils_bond_mode_int_to_string (mode);
 
 	/* Make sure mode is compatible with other settings */
-	if (   strcmp (mode_new, "balance-alb") == 0
-	    || strcmp (mode_new, "balance-tlb") == 0) {
+	if (NM_IN_SET (bond_mode, NM_BOND_MODE_TLB,
+	                          NM_BOND_MODE_ALB)) {
 		if (arp_interval > 0) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s=%s' is incompatible with '%s > 0'"),
-			             NM_SETTING_BOND_OPTION_MODE, mode_new, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			             NM_SETTING_BOND_OPTION_MODE,
+			             mode_str,
+			             NM_SETTING_BOND_OPTION_ARP_INTERVAL);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			return FALSE;
 		}
 	}
 
-	primary = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_PRIMARY);
-	if (strcmp (mode_new, "active-backup") == 0) {
+	primary = _bond_get_option (self, NM_SETTING_BOND_OPTION_PRIMARY);
+	if (bond_mode == NM_BOND_MODE_ACTIVEBACKUP) {
 		GError *tmp_error = NULL;
 
 		if (primary && !nm_utils_ifname_valid_kernel (primary, &tmp_error)) {
@@ -627,58 +835,64 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' is not valid for the '%s' option: %s"),
 			             primary, NM_SETTING_BOND_OPTION_PRIMARY, tmp_error->message);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			g_error_free (tmp_error);
 			return FALSE;
 		}
-	} else {
-		if (primary) {
-			g_set_error (error,
-			             NM_CONNECTION_ERROR,
-			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			             _("'%s' option is only valid for '%s=%s'"),
-			             NM_SETTING_BOND_OPTION_PRIMARY,
-			             NM_SETTING_BOND_OPTION_MODE, "active-backup");
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
-			return FALSE;
-		}
+	} else if (primary) {
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             _("'%s' option is only valid for '%s=%s'"),
+		             NM_SETTING_BOND_OPTION_PRIMARY,
+		             NM_SETTING_BOND_OPTION_MODE, "active-backup");
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+		return FALSE;
 	}
 
-	if (connection && nm_connection_get_setting_infiniband (connection)) {
-		if (strcmp (mode_new, "active-backup") != 0) {
+	if (   connection
+	    && nm_connection_get_setting_infiniband (connection)) {
+		if (bond_mode != NM_BOND_MODE_ACTIVEBACKUP) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s=%s' is not a valid configuration for '%s'"),
-			             NM_SETTING_BOND_OPTION_MODE, mode_new, NM_SETTING_INFINIBAND_SETTING_NAME);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			             NM_SETTING_BOND_OPTION_MODE, mode_str, NM_SETTING_INFINIBAND_SETTING_NAME);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
 			return FALSE;
 		}
 	}
 
 	if (miimon == 0) {
-		gpointer delayopt;
-
 		/* updelay and downdelay need miimon to be enabled to be valid */
-		delayopt = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_UPDELAY);
-		if (delayopt && _atoi (delayopt) > 0) {
+		if (_atoi (_bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_UPDELAY))) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' option requires '%s' option to be enabled"),
 			             NM_SETTING_BOND_OPTION_UPDELAY, NM_SETTING_BOND_OPTION_MIIMON);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			return FALSE;
 		}
 
-		delayopt = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_DOWNDELAY);
-		if (delayopt && _atoi (delayopt) > 0) {
+		if (_atoi (_bond_get_option_or_default (self, NM_SETTING_BOND_OPTION_DOWNDELAY))) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' option requires '%s' option to be enabled"),
 			             NM_SETTING_BOND_OPTION_DOWNDELAY, NM_SETTING_BOND_OPTION_MIIMON);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			return FALSE;
 		}
 	}
@@ -686,19 +900,22 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	/* arp_ip_target can only be used with arp_interval, and must
 	 * contain a comma-separated list of IPv4 addresses.
 	 */
-	arp_ip_target = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
+	arp_ip_target = _bond_get_option (self, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
 	if (arp_interval > 0) {
 		char **addrs;
 		guint32 addr;
-		int i;
 
 		if (!arp_ip_target) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' option requires '%s' option to be set"),
-			             NM_SETTING_BOND_OPTION_ARP_INTERVAL, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			             NM_SETTING_BOND_OPTION_ARP_INTERVAL,
+			             NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			return FALSE;
 		}
 
@@ -709,7 +926,9 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' option is empty"),
 			             NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			g_prefix_error (error, "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			g_strfreev (addrs);
 			return FALSE;
 		}
@@ -720,8 +939,12 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 				             NM_CONNECTION_ERROR,
 				             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 				             _("'%s' is not a valid IPv4 address for '%s' option"),
-				             NM_SETTING_BOND_OPTION_ARP_IP_TARGET, addrs[i]);
-				g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+				             NM_SETTING_BOND_OPTION_ARP_IP_TARGET,
+				             addrs[i]);
+				g_prefix_error (error,
+				                "%s.%s: ",
+				                NM_SETTING_BOND_SETTING_NAME,
+				                NM_SETTING_BOND_OPTIONS);
 				g_strfreev (addrs);
 				return FALSE;
 			}
@@ -733,17 +956,19 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' option requires '%s' option to be set"),
-			             NM_SETTING_BOND_OPTION_ARP_IP_TARGET, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			             NM_SETTING_BOND_OPTION_ARP_IP_TARGET,
+			             NM_SETTING_BOND_OPTION_ARP_INTERVAL);
+			g_prefix_error (error, "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			return FALSE;
 		}
 	}
 
-	lacp_rate = g_hash_table_lookup (priv->options, NM_SETTING_BOND_OPTION_LACP_RATE);
+	lacp_rate = _bond_get_option (self, NM_SETTING_BOND_OPTION_LACP_RATE);
 	if (   lacp_rate
-	    && g_strcmp0 (mode_new, "802.3ad")
-	    && strcmp (lacp_rate, "slow") != 0
-	    && strcmp (lacp_rate, "0") != 0) {
+	    && bond_mode != NM_BOND_MODE_8023AD
+	    && !NM_IN_STRSET (lacp_rate, "0", "slow")) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -753,8 +978,9 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (   (num_grat_arp != -1 && num_unsol_na != -1)
-	    && (num_grat_arp != num_unsol_na)) {
+	if (   _bond_get_option (self, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP)
+	    && _bond_get_option (self, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA)
+	    && num_grat_arp != num_unsol_na) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -770,7 +996,14 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 
 	/* *** errors above here should be always fatal, below NORMALIZABLE_ERROR *** */
 
-	if (g_strcmp0 (mode_orig, mode_new) != 0) {
+	if (!NM_IN_STRSET (mode_str,
+	                   "802.3ad",
+	                   "active-backup",
+	                   "balance-rr",
+	                   "balance-alb",
+	                   "balance-tlb",
+	                   "balance-xor",
+	                   "broadcast")) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -781,18 +1014,18 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	}
 
 	/* normalize unsupported options for the current mode */
-	bond_mode = _nm_setting_bond_mode_from_string (mode_new);
-	g_hash_table_iter_init (&iter, priv->options);
-	while (g_hash_table_iter_next (&iter, (gpointer) &key, NULL)) {
-		if (nm_streq (key, "mode"))
-			continue;
-		if (!_nm_setting_bond_option_supported (key, bond_mode)) {
+	for (i = 0; priv->options_idx_cache[i].name; i++) {
+		n = &priv->options_idx_cache[i];
+		if (!_nm_setting_bond_option_supported (n->name, bond_mode)) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("'%s' option is not valid with mode '%s'"),
-			             key, mode_new);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+			             n->name, mode_str);
+			g_prefix_error (error,
+			                "%s.%s: ",
+			                NM_SETTING_BOND_SETTING_NAME,
+			                NM_SETTING_BOND_OPTIONS);
 			return NM_SETTING_VERIFY_NORMALIZABLE;
 		}
 	}
@@ -807,9 +1040,8 @@ options_equal_asym (NMSettingBond *s_bond,
                     NMSettingBond *s_bond2,
                     NMSettingCompareFlags flags)
 {
-	GHashTable *options2 = NM_SETTING_BOND_GET_PRIVATE (s_bond2)->options;
 	GHashTableIter iter;
-	const char *key, *value, *value2;
+	const char *key, *value;
 
 	g_hash_table_iter_init (&iter, NM_SETTING_BOND_GET_PRIVATE (s_bond)->options);
 	while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) {
@@ -824,18 +1056,7 @@ options_equal_asym (NMSettingBond *s_bond,
 				continue;
 		}
 
-		value2 = g_hash_table_lookup (options2, key);
-
-		if (!value2) {
-			if (nm_streq (key, "num_grat_arp"))
-				value2 = g_hash_table_lookup (options2, "num_unsol_na");
-			else if (nm_streq (key, "num_unsol_na"))
-				value2 = g_hash_table_lookup (options2, "num_grat_arp");
-		}
-
-		if (!value2)
-			value2 = nm_setting_bond_get_option_default (s_bond2, key);
-		if (!nm_streq (value, value2))
+		if (!nm_streq0 (value, _bond_get_option (s_bond2, key)))
 			return FALSE;
 	}
 
diff --git a/libnm-core/nm-setting-bond.h b/libnm-core/nm-setting-bond.h
index c17babdc..fc2a3735 100644
--- a/libnm-core/nm-setting-bond.h
+++ b/libnm-core/nm-setting-bond.h
@@ -94,6 +94,10 @@ const char **nm_setting_bond_get_valid_options  (NMSettingBond *setting);
 const char * nm_setting_bond_get_option_default (NMSettingBond *setting,
                                                  const char *name);
 
+NM_AVAILABLE_IN_1_24
+const char * nm_setting_bond_get_option_normalized (NMSettingBond *setting,
+                                                    const char *name);
+
 G_END_DECLS
 
 #endif /* __NM_SETTING_BOND_H__ */
diff --git a/libnm-core/nm-setting-bridge-port.c b/libnm-core/nm-setting-bridge-port.c
index 152ba944..5fcf1e60 100644
--- a/libnm-core/nm-setting-bridge-port.c
+++ b/libnm-core/nm-setting-bridge-port.c
@@ -34,10 +34,10 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingBridgePort,
 );
 
 typedef struct {
+	GPtrArray *vlans;
 	guint16 priority;
 	guint16 path_cost;
-	gboolean hairpin_mode;
-	GPtrArray *vlans;
+	bool hairpin_mode:1;
 } NMSettingBridgePortPrivate;
 
 G_DEFINE_TYPE (NMSettingBridgePort, nm_setting_bridge_port, NM_TYPE_SETTING)
@@ -439,6 +439,9 @@ nm_setting_bridge_port_init (NMSettingBridgePort *setting)
 	NMSettingBridgePortPrivate *priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting);
 
 	priv->vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref);
+
+	priv->priority = NM_BR_PORT_DEF_PRIORITY;
+	priv->path_cost = NM_BR_PORT_DEF_PATH_COST;
 }
 
 /**
@@ -497,7 +500,6 @@ nm_setting_bridge_port_class_init (NMSettingBridgePortClass *klass)
 	    g_param_spec_uint (NM_SETTING_BRIDGE_PORT_PRIORITY, "", "",
 	                       0, NM_BR_PORT_MAX_PRIORITY, NM_BR_PORT_DEF_PRIORITY,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -517,9 +519,8 @@ nm_setting_bridge_port_class_init (NMSettingBridgePortClass *klass)
 	 */
 	obj_properties[PROP_PATH_COST] =
 	    g_param_spec_uint (NM_SETTING_BRIDGE_PORT_PATH_COST, "", "",
-	                       0, NM_BR_PORT_MAX_PATH_COST, 100,
+	                       0, NM_BR_PORT_MAX_PATH_COST, NM_BR_PORT_DEF_PATH_COST,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	/**
diff --git a/libnm-core/nm-setting-bridge.c b/libnm-core/nm-setting-bridge.c
index 585f9ced..d8000221 100644
--- a/libnm-core/nm-setting-bridge.c
+++ b/libnm-core/nm-setting-bridge.c
@@ -22,6 +22,18 @@
  * necessary for bridging connections.
  **/
 
+#define BRIDGE_AGEING_TIME_DEFAULT                300
+#define BRIDGE_FORWARD_DELAY_DEFAULT              15
+#define BRIDGE_HELLO_TIME_DEFAULT                 2
+#define BRIDGE_MAX_AGE_DEFAULT                    20
+#define BRIDGE_MULTICAST_QUERIER_DEFAULT          FALSE
+#define BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEFAULT FALSE
+#define BRIDGE_MULTICAST_SNOOPING_DEFAULT         TRUE
+#define BRIDGE_PRIORITY_DEFAULT                   0x8000
+#define BRIDGE_STP_DEFAULT                        TRUE
+#define BRIDGE_VLAN_DEFAULT_PVID_DEFAULT          1
+#define BRIDGE_VLAN_STATS_ENABLED_DEFAULT         FALSE
+
 /*****************************************************************************/
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMSettingBridge,
@@ -32,31 +44,57 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingBridge,
 	PROP_HELLO_TIME,
 	PROP_MAX_AGE,
 	PROP_AGEING_TIME,
+	PROP_GROUP_ADDRESS,
 	PROP_GROUP_FORWARD_MASK,
+	PROP_MULTICAST_ROUTER,
+	PROP_MULTICAST_QUERIER,
+	PROP_MULTICAST_QUERY_USE_IFADDR,
 	PROP_MULTICAST_SNOOPING,
 	PROP_VLAN_FILTERING,
 	PROP_VLAN_DEFAULT_PVID,
+	PROP_VLAN_PROTOCOL,
+	PROP_VLAN_STATS_ENABLED,
 	PROP_VLANS,
 );
 
 typedef struct {
+	GPtrArray *vlans;
 	char *   mac_address;
-	gboolean stp;
+	char *   multicast_router;
+	char *   group_address;
+	char *   vlan_protocol;
+	guint32  ageing_time;
 	guint16  priority;
 	guint16  forward_delay;
 	guint16  hello_time;
 	guint16  max_age;
-	guint32  ageing_time;
-	guint16  group_forward_mask;
-	gboolean multicast_snooping;
-	gboolean vlan_filtering;
 	guint16  vlan_default_pvid;
-	GPtrArray *vlans;
+	guint16  group_forward_mask;
+	bool multicast_snooping:1;
+	bool vlan_filtering:1;
+	bool stp:1;
+	bool vlan_stats_enabled:1;
+	bool multicast_query_use_ifaddr:1;
+	bool multicast_querier:1;
 } NMSettingBridgePrivate;
 
+/**
+ * NMSettingBridge:
+ *
+ * Bridging Settings
+ */
+struct _NMSettingBridge {
+	NMSetting parent;
+	NMSettingBridgePrivate _priv;
+};
+
+struct _NMSettingBridgeClass {
+	NMSettingClass parent;
+};
+
 G_DEFINE_TYPE (NMSettingBridge, nm_setting_bridge, NM_TYPE_SETTING)
 
-#define NM_SETTING_BRIDGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_SETTING_BRIDGE, NMSettingBridgePrivate))
+#define NM_SETTING_BRIDGE_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSettingBridge, NM_IS_SETTING_BRIDGE, NMSetting)
 
 /*****************************************************************************/
 
@@ -876,6 +914,102 @@ nm_setting_bridge_clear_vlans (NMSettingBridge *setting)
 	}
 }
 
+/**
+ * nm_setting_bridge_get_group_address:
+ * @setting: the #NMSettingBridge
+ *
+ * Returns: the #NMSettingBridge:group-address property of the setting
+ *
+ * Since 1.24
+ **/
+const char *
+nm_setting_bridge_get_group_address (const NMSettingBridge *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), NULL);
+
+	return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->group_address;
+}
+
+/**
+ * nm_setting_bridge_get_vlan_protocol:
+ * @setting: the #NMSettingBridge
+ *
+ * Returns: the #NMSettingBridge:vlan-protocol property of the setting
+ *
+ * Since 1.24
+ **/
+const char *
+nm_setting_bridge_get_vlan_protocol (const NMSettingBridge *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), NULL);
+
+	return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->vlan_protocol;
+}
+
+/**
+ * nm_setting_bridge_get_vlan_stats_enabled:
+ * @setting: the #NMSettingBridge
+ *
+ * Returns: the #NMSettingBridge:vlan-stats-enabled property of the setting
+ *
+ * Since 1.24
+ **/
+gboolean
+nm_setting_bridge_get_vlan_stats_enabled (const NMSettingBridge *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), FALSE);
+
+	return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->vlan_stats_enabled;
+}
+
+/**
+ * nm_setting_bridge_get_multicast_router:
+ * @setting: the #NMSettingBridge
+ *
+ * Returns: the #NMSettingBridge:multicast-router property of the setting
+ *
+ * Since 1.24
+ **/
+const char *
+nm_setting_bridge_get_multicast_router (const NMSettingBridge *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), NULL);
+
+	return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_router;
+}
+
+/**
+ * nm_setting_bridge_get_multicast_query_use_ifaddr:
+ * @setting: the #NMSettingBridge
+ *
+ * Returns: the #NMSettingBridge:multicast-query-use-ifaddr property of the setting
+ *
+ * Since 1.24
+ **/
+gboolean
+nm_setting_bridge_get_multicast_query_use_ifaddr (const NMSettingBridge *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), FALSE);
+
+	return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_query_use_ifaddr;
+}
+
+/**
+ * nm_setting_bridge_get_multicast_querier:
+ * @setting: the #NMSettingBridge
+ *
+ * Returns: the #NMSettingBridge:multicast-querier property of the setting
+ *
+ * Since 1.24
+ **/
+gboolean
+nm_setting_bridge_get_multicast_querier (const NMSettingBridge *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_BRIDGE (setting), FALSE);
+
+	return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_querier;
+}
+
 /*****************************************************************************/
 
 static gboolean
@@ -966,6 +1100,55 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	                                        NM_SETTING_BRIDGE_VLANS))
 		return FALSE;
 
+	if (   priv->group_address
+	    && !_nm_utils_hwaddr_link_local_valid (priv->group_address)) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("is not a valid link local MAC address"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_BRIDGE_SETTING_NAME, NM_SETTING_BRIDGE_GROUP_ADDRESS);
+		return FALSE;
+	}
+
+	if (   priv->vlan_protocol
+	    && !NM_IN_STRSET (priv->vlan_protocol,
+	                      "802.1Q",
+	                      "802.1ad")) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("is not a valid VLAN filtering protocol"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_BRIDGE_SETTING_NAME, NM_SETTING_BRIDGE_VLAN_PROTOCOL);
+		return FALSE;
+	}
+
+	if (priv->multicast_router) {
+		if (!NM_IN_STRSET (priv->multicast_router,
+		                   "auto",
+		                   "enabled",
+		                   "disabled")) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("is not a valid option"));
+			g_prefix_error (error, "%s.%s: ", NM_SETTING_BRIDGE_SETTING_NAME, NM_SETTING_BRIDGE_MULTICAST_ROUTER);
+			return FALSE;
+		}
+
+		if (   NM_IN_STRSET (priv->multicast_router,
+		                     "auto",
+		                     "enabled")
+		    && !priv->multicast_snooping) {
+			g_set_error (error,
+			             NM_CONNECTION_ERROR,
+			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("'%s' option requires '%s' option to be enabled"),
+			             NM_SETTING_BRIDGE_MULTICAST_ROUTER, NM_SETTING_BRIDGE_MULTICAST_SNOOPING);
+			g_prefix_error (error, "%s.%s: ", NM_SETTING_BRIDGE_SETTING_NAME, NM_SETTING_BRIDGE_MULTICAST_ROUTER);
+			return FALSE;
+		}
+	}
+
 	/* Failures from here on are NORMALIZABLE... */
 
 	if (!_nm_utils_bridge_vlan_verify_list (priv->vlans,
@@ -1046,18 +1229,36 @@ get_property (GObject *object, guint prop_id,
 	case PROP_AGEING_TIME:
 		g_value_set_uint (value, priv->ageing_time);
 		break;
+	case PROP_GROUP_ADDRESS:
+		g_value_set_string (value, priv->group_address);
+		break;
 	case PROP_GROUP_FORWARD_MASK:
 		g_value_set_uint (value, priv->group_forward_mask);
 		break;
 	case PROP_MULTICAST_SNOOPING:
 		g_value_set_boolean (value, priv->multicast_snooping);
 		break;
+	case PROP_MULTICAST_ROUTER:
+		g_value_set_string (value, priv->multicast_router);
+		break;
+	case PROP_MULTICAST_QUERIER:
+		g_value_set_boolean (value, priv->multicast_querier);
+		break;
+	case PROP_MULTICAST_QUERY_USE_IFADDR:
+		g_value_set_boolean (value, priv->multicast_query_use_ifaddr);
+		break;
 	case PROP_VLAN_FILTERING:
 		g_value_set_boolean (value, priv->vlan_filtering);
 		break;
 	case PROP_VLAN_DEFAULT_PVID:
 		g_value_set_uint (value, priv->vlan_default_pvid);
 		break;
+	case PROP_VLAN_PROTOCOL:
+		g_value_set_string (value, priv->vlan_protocol);
+		break;
+	case PROP_VLAN_STATS_ENABLED:
+		g_value_set_boolean (value, priv->vlan_stats_enabled);
+		break;
 	case PROP_VLANS:
 		g_value_take_boxed (value, _nm_utils_copy_array (priv->vlans,
 		                                                 (NMUtilsCopyFunc) nm_bridge_vlan_ref,
@@ -1099,18 +1300,40 @@ set_property (GObject *object, guint prop_id,
 	case PROP_AGEING_TIME:
 		priv->ageing_time = g_value_get_uint (value);
 		break;
+	case PROP_GROUP_ADDRESS:
+		g_free (priv->group_address);
+		priv->group_address = _nm_utils_hwaddr_canonical_or_invalid (g_value_get_string (value),
+		                                                             ETH_ALEN);
+		break;
 	case PROP_GROUP_FORWARD_MASK:
 		priv->group_forward_mask = (guint16) g_value_get_uint (value);
 		break;
 	case PROP_MULTICAST_SNOOPING:
 		priv->multicast_snooping = g_value_get_boolean (value);
 		break;
+	case PROP_MULTICAST_ROUTER:
+		g_free (priv->multicast_router);
+		priv->multicast_router = g_value_dup_string (value);
+		break;
+	case PROP_MULTICAST_QUERIER:
+		priv->multicast_querier = g_value_get_boolean (value);
+		break;
+	case PROP_MULTICAST_QUERY_USE_IFADDR:
+		priv->multicast_query_use_ifaddr = g_value_get_boolean (value);
+		break;
 	case PROP_VLAN_FILTERING:
 		priv->vlan_filtering = g_value_get_boolean (value);
 		break;
 	case PROP_VLAN_DEFAULT_PVID:
 		priv->vlan_default_pvid = g_value_get_uint (value);
 		break;
+	case PROP_VLAN_PROTOCOL:
+		g_free (priv->vlan_protocol);
+		priv->vlan_protocol = g_value_dup_string (value);
+		break;
+	case PROP_VLAN_STATS_ENABLED:
+		priv->vlan_stats_enabled = g_value_get_boolean (value);
+		break;
 	case PROP_VLANS:
 		g_ptr_array_unref (priv->vlans);
 		priv->vlans = _nm_utils_copy_array (g_value_get_boxed (value),
@@ -1131,6 +1354,18 @@ nm_setting_bridge_init (NMSettingBridge *setting)
 	NMSettingBridgePrivate *priv = NM_SETTING_BRIDGE_GET_PRIVATE (setting);
 
 	priv->vlans = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_bridge_vlan_unref);
+
+	priv->ageing_time                = BRIDGE_AGEING_TIME_DEFAULT;
+	priv->forward_delay              = BRIDGE_FORWARD_DELAY_DEFAULT;
+	priv->hello_time                 = BRIDGE_HELLO_TIME_DEFAULT;
+	priv->max_age                    = BRIDGE_MAX_AGE_DEFAULT;
+	priv->multicast_snooping         = BRIDGE_MULTICAST_SNOOPING_DEFAULT;
+	priv->priority                   = BRIDGE_PRIORITY_DEFAULT;
+	priv->stp                        = BRIDGE_STP_DEFAULT;
+	priv->vlan_default_pvid          = BRIDGE_VLAN_DEFAULT_PVID_DEFAULT;
+	priv->vlan_stats_enabled         = BRIDGE_VLAN_STATS_ENABLED_DEFAULT;
+	priv->multicast_query_use_ifaddr = BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEFAULT;
+	priv->multicast_querier          = BRIDGE_MULTICAST_QUERIER_DEFAULT;
 }
 
 /**
@@ -1152,6 +1387,9 @@ finalize (GObject *object)
 	NMSettingBridgePrivate *priv = NM_SETTING_BRIDGE_GET_PRIVATE (object);
 
 	g_free (priv->mac_address);
+	g_free (priv->multicast_router);
+	g_free (priv->group_address);
+	g_free (priv->vlan_protocol);
 	g_ptr_array_unref (priv->vlans);
 
 	G_OBJECT_CLASS (nm_setting_bridge_parent_class)->finalize (object);
@@ -1164,8 +1402,6 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	NMSettingClass *setting_class = NM_SETTING_CLASS (klass);
 	GArray *properties_override = _nm_sett_info_property_override_create_array ();
 
-	g_type_class_add_private (klass, sizeof (NMSettingBridgePrivate));
-
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 	object_class->finalize     = finalize;
@@ -1209,7 +1445,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	                         G_PARAM_READWRITE |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
-	_nm_properties_override_gobj (properties_override, obj_properties[PROP_MAC_ADDRESS], &nm_sett_info_propert_type_mac_addrees);
+	_nm_properties_override_gobj (properties_override, obj_properties[PROP_MAC_ADDRESS], &nm_sett_info_propert_type_mac_address);
 
 	/**
 	 * NMSettingBridge:stp:
@@ -1225,9 +1461,8 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	 */
 	obj_properties[PROP_STP] =
 	    g_param_spec_boolean (NM_SETTING_BRIDGE_STP, "", "",
-	                          TRUE,
+	                          BRIDGE_STP_DEFAULT,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_INFERRABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
@@ -1248,9 +1483,8 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	 */
 	obj_properties[PROP_PRIORITY] =
 	    g_param_spec_uint (NM_SETTING_BRIDGE_PRIORITY, "", "",
-	                       0, G_MAXUINT16, 0x8000,
+	                       0, G_MAXUINT16, BRIDGE_PRIORITY_DEFAULT,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1269,9 +1503,8 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	 */
 	obj_properties[PROP_FORWARD_DELAY] =
 	    g_param_spec_uint (NM_SETTING_BRIDGE_FORWARD_DELAY, "", "",
-	                       0, NM_BR_MAX_FORWARD_DELAY, 15,
+	                       0, NM_BR_MAX_FORWARD_DELAY, BRIDGE_FORWARD_DELAY_DEFAULT,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1290,9 +1523,8 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	 */
 	obj_properties[PROP_HELLO_TIME] =
 	    g_param_spec_uint (NM_SETTING_BRIDGE_HELLO_TIME, "", "",
-	                       0, NM_BR_MAX_HELLO_TIME, 2,
+	                       0, NM_BR_MAX_HELLO_TIME, BRIDGE_HELLO_TIME_DEFAULT,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1311,9 +1543,8 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	 */
 	obj_properties[PROP_MAX_AGE] =
 	    g_param_spec_uint (NM_SETTING_BRIDGE_MAX_AGE, "", "",
-	                       0, NM_BR_MAX_MAX_AGE, 20,
+	                       0, NM_BR_MAX_MAX_AGE, BRIDGE_MAX_AGE_DEFAULT,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1332,9 +1563,8 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	 */
 	obj_properties[PROP_AGEING_TIME] =
 	    g_param_spec_uint (NM_SETTING_BRIDGE_AGEING_TIME, "", "",
-	                       NM_BR_MIN_AGEING_TIME, NM_BR_MAX_AGEING_TIME, 300,
+	                       NM_BR_MIN_AGEING_TIME, NM_BR_MAX_AGEING_TIME, BRIDGE_AGEING_TIME_DEFAULT,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1354,7 +1584,6 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	    g_param_spec_uint (NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, "", "",
 	                       0, 0xFFFF, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1378,9 +1607,8 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	 */
 	obj_properties[PROP_MULTICAST_SNOOPING] =
 	    g_param_spec_boolean (NM_SETTING_BRIDGE_MULTICAST_SNOOPING, "", "",
-	                          TRUE,
+	                          BRIDGE_MULTICAST_SNOOPING_DEFAULT,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_INFERRABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
@@ -1403,7 +1631,6 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	    g_param_spec_boolean (NM_SETTING_BRIDGE_VLAN_FILTERING, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_INFERRABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
@@ -1425,9 +1652,8 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	 */
 	obj_properties[PROP_VLAN_DEFAULT_PVID] =
 	    g_param_spec_uint (NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID, "", "",
-	                       0, NM_BRIDGE_VLAN_VID_MAX, 1,
+	                       0, NM_BRIDGE_VLAN_VID_MAX, BRIDGE_VLAN_DEFAULT_PVID_DEFAULT,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1473,6 +1699,140 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *klass)
 	 */
 	_nm_properties_override_dbus (properties_override, "interface-name", &nm_sett_info_propert_type_deprecated_interface_name);
 
+	/**
+	 * NMSettingBridge:group-address:
+	 *
+	 * If specified, The MAC address of the multicast group this bridge uses for STP.
+	 *
+	 * The address must be a link-local address in standard Ethernet MAC address format,
+	 * ie an address of the form 01:80:C2:00:00:0X, with X in [0, 4..F].
+	 * If not specified the default value is 01:80:C2:00:00:00.
+	 *
+	 * Since: 1.24
+	 **/
+	/* ---ifcfg-rh---
+	 * property: group-address
+	 * variable: BRIDGING_OPTS: group_address=
+	 * description: STP group address.
+	 * example: BRIDGING_OPTS="group_address=01:80:C2:00:00:0A"
+	 * ---end---
+	 */
+	obj_properties[PROP_GROUP_ADDRESS] =
+	    g_param_spec_string (NM_SETTING_BRIDGE_GROUP_ADDRESS, "", "",
+	                         NULL,
+	                         G_PARAM_READWRITE |
+	                         NM_SETTING_PARAM_INFERRABLE |
+	                         G_PARAM_STATIC_STRINGS);
+	_nm_properties_override_gobj (properties_override, obj_properties[PROP_GROUP_ADDRESS], &nm_sett_info_propert_type_mac_address);
+
+	/**
+	 * NMSettingBridge:vlan-protocol:
+	 *
+	 * If specified, the protocol used for VLAN filtering.
+	 *
+	 * Supported values are: '802.1Q', '802.1ad'.
+	 * If not specified the default value is '802.1Q'.
+	 *
+	 * Since: 1.24
+	 **/
+	/* ---ifcfg-rh---
+	 * property: vlan-protocol
+	 * variable: BRIDGING_OPTS: vlan_protocol=
+	 * description: VLAN filtering protocol.
+	 * example: BRIDGING_OPTS="vlan_protocol=802.1Q"
+	 * ---end---
+	 */
+	obj_properties[PROP_VLAN_PROTOCOL] =
+	    g_param_spec_string (NM_SETTING_BRIDGE_VLAN_PROTOCOL, "", "",
+	                         NULL,
+	                         G_PARAM_READWRITE |
+	                         NM_SETTING_PARAM_INFERRABLE |
+	                         G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingBridge:vlan-stats-enabled:
+	 *
+	 * Controls whether per-VLAN stats accounting is enabled.
+	 **/
+	/* ---ifcfg-rh---
+	 * property: vlan-stats-enabled
+	 * variable: BRIDGING_OPTS: vlan_stats_enabled=
+	 * default: 0
+	 * example: BRIDGING_OPTS="vlan_stats_enabled=1"
+	 * ---end---
+	 */
+	obj_properties[PROP_VLAN_STATS_ENABLED] =
+	    g_param_spec_boolean (NM_SETTING_BRIDGE_VLAN_STATS_ENABLED, "", "",
+	                          BRIDGE_VLAN_STATS_ENABLED_DEFAULT,
+	                          G_PARAM_READWRITE |
+	                          NM_SETTING_PARAM_INFERRABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingBridge:multicast-router:
+	 *
+	 * Sets bridge's multicast router.
+	 * multicast-snooping must be enabled for this option to work.
+	 *
+	 * Supported values are: 'auto', 'disabled', 'enabled'.
+	 * If not specified the default value is 'auto'.
+	 **/
+	/* ---ifcfg-rh---
+	 * property: multicast-router
+	 * variable: BRIDGING_OPTS: multicast_router=
+	 * values: auto, enabled, disabled
+	 * default: auto
+	 * example: BRIDGING_OPTS="multicast_router=enabled"
+	 * ---end---
+	 */
+	obj_properties[PROP_MULTICAST_ROUTER] =
+	    g_param_spec_string (NM_SETTING_BRIDGE_MULTICAST_ROUTER, "", "",
+	                         NULL,
+	                         G_PARAM_READWRITE |
+	                         NM_SETTING_PARAM_INFERRABLE |
+	                         G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingBridge:multicast-query-use-ifaddr:
+	 *
+	 * If enabled the bridge's own IP address is used as
+	 * the source address for IGMP queries otherwise
+	 * the default of 0.0.0.0 is used.
+	 **/
+	/* ---ifcfg-rh---
+	 * property: multicast-query-use-ifaddr
+	 * variable: BRIDGING_OPTS: multicast_query_use_ifaddr=
+	 * default: 0
+	 * example: BRIDGING_OPTS="multicast_query-use_ifaddr=1"
+	 * ---end---
+	 */
+	obj_properties[PROP_MULTICAST_QUERY_USE_IFADDR] =
+	    g_param_spec_boolean (NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR, "", "",
+	                          BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEFAULT,
+	                          G_PARAM_READWRITE |
+	                          NM_SETTING_PARAM_INFERRABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingBridge:multicast-querier:
+	 *
+	 * Enable or disable sending of multicast queries by the bridge.
+	 * If not specified the option is disabled.
+	 **/
+	/* ---ifcfg-rh---
+	 * property: multicast-querier
+	 * variable: BRIDGING_OPTS: multicast_querier=
+	 * default: 0
+	 * example: BRIDGING_OPTS="multicast_querier=1"
+	 * ---end---
+	 */
+	obj_properties[PROP_MULTICAST_QUERIER] =
+	    g_param_spec_boolean (NM_SETTING_BRIDGE_MULTICAST_QUERIER, "", "",
+	                          BRIDGE_MULTICAST_QUERIER_DEFAULT,
+	                          G_PARAM_READWRITE |
+	                          NM_SETTING_PARAM_INFERRABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	_nm_setting_class_commit_full (setting_class, NM_META_SETTING_TYPE_BRIDGE,
diff --git a/libnm-core/nm-setting-bridge.h b/libnm-core/nm-setting-bridge.h
index ee8b45f6..6bd5100f 100644
--- a/libnm-core/nm-setting-bridge.h
+++ b/libnm-core/nm-setting-bridge.h
@@ -23,37 +23,29 @@ G_BEGIN_DECLS
 
 #define NM_SETTING_BRIDGE_SETTING_NAME "bridge"
 
-#define NM_SETTING_BRIDGE_MAC_ADDRESS    "mac-address"
-#define NM_SETTING_BRIDGE_STP            "stp"
-#define NM_SETTING_BRIDGE_PRIORITY       "priority"
-#define NM_SETTING_BRIDGE_FORWARD_DELAY  "forward-delay"
-#define NM_SETTING_BRIDGE_HELLO_TIME     "hello-time"
-#define NM_SETTING_BRIDGE_MAX_AGE        "max-age"
-#define NM_SETTING_BRIDGE_AGEING_TIME    "ageing-time"
-#define NM_SETTING_BRIDGE_GROUP_FORWARD_MASK "group-forward-mask"
-#define NM_SETTING_BRIDGE_MULTICAST_SNOOPING "multicast-snooping"
-#define NM_SETTING_BRIDGE_VLAN_FILTERING     "vlan-filtering"
-#define NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID  "vlan-default-pvid"
-#define NM_SETTING_BRIDGE_VLANS              "vlans"
+#define NM_SETTING_BRIDGE_MAC_ADDRESS                     "mac-address"
+#define NM_SETTING_BRIDGE_STP                             "stp"
+#define NM_SETTING_BRIDGE_PRIORITY                        "priority"
+#define NM_SETTING_BRIDGE_FORWARD_DELAY                   "forward-delay"
+#define NM_SETTING_BRIDGE_HELLO_TIME                      "hello-time"
+#define NM_SETTING_BRIDGE_MAX_AGE                         "max-age"
+#define NM_SETTING_BRIDGE_AGEING_TIME                     "ageing-time"
+#define NM_SETTING_BRIDGE_GROUP_FORWARD_MASK              "group-forward-mask"
+#define NM_SETTING_BRIDGE_MULTICAST_SNOOPING              "multicast-snooping"
+#define NM_SETTING_BRIDGE_MULTICAST_ROUTER                "multicast-router"
+#define NM_SETTING_BRIDGE_MULTICAST_QUERIER               "multicast-querier"
+#define NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR      "multicast-query-use-ifaddr"
+#define NM_SETTING_BRIDGE_VLAN_FILTERING                  "vlan-filtering"
+#define NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID               "vlan-default-pvid"
+#define NM_SETTING_BRIDGE_VLANS                           "vlans"
+#define NM_SETTING_BRIDGE_GROUP_ADDRESS                   "group-address"
+#define NM_SETTING_BRIDGE_VLAN_PROTOCOL                   "vlan-protocol"
+#define NM_SETTING_BRIDGE_VLAN_STATS_ENABLED              "vlan-stats-enabled"
 
 #define NM_BRIDGE_VLAN_VID_MIN            1
 #define NM_BRIDGE_VLAN_VID_MAX            4094
 
-/**
- * NMSettingBridge:
- *
- * Bridging Settings
- */
-struct _NMSettingBridge {
-	NMSetting parent;
-};
-
-typedef struct {
-	NMSettingClass parent;
-
-	/*< private >*/
-	gpointer padding[4];
-} NMSettingBridgeClass;
+typedef struct _NMSettingBridgeClass NMSettingBridgeClass;
 
 typedef struct _NMBridgeVlan NMBridgeVlan;
 
@@ -130,6 +122,24 @@ char         * nm_bridge_vlan_to_str (const NMBridgeVlan *vlan, GError **error);
 NM_AVAILABLE_IN_1_18
 NMBridgeVlan * nm_bridge_vlan_from_str (const char *str, GError **error);
 
+NM_AVAILABLE_IN_1_24
+const char *   nm_setting_bridge_get_group_address (const NMSettingBridge *setting);
+
+NM_AVAILABLE_IN_1_24
+const char *   nm_setting_bridge_get_vlan_protocol (const NMSettingBridge *setting);
+
+NM_AVAILABLE_IN_1_24
+gboolean nm_setting_bridge_get_vlan_stats_enabled (const NMSettingBridge *setting);
+
+NM_AVAILABLE_IN_1_24
+const char *   nm_setting_bridge_get_multicast_router (const NMSettingBridge *setting);
+
+NM_AVAILABLE_IN_1_24
+gboolean nm_setting_bridge_get_multicast_query_use_ifaddr (const NMSettingBridge *setting);
+
+NM_AVAILABLE_IN_1_24
+gboolean nm_setting_bridge_get_multicast_querier (const NMSettingBridge *setting);
+
 G_END_DECLS
 
 #endif /* __NM_SETTING_BRIDGE_H__ */
diff --git a/libnm-core/nm-setting-cdma.c b/libnm-core/nm-setting-cdma.c
index 60be8c8f..e303ab8a 100644
--- a/libnm-core/nm-setting-cdma.c
+++ b/libnm-core/nm-setting-cdma.c
@@ -34,8 +34,8 @@ typedef struct {
 	char *number; /* For dialing, duh */
 	char *username;
 	char *password;
-	NMSettingSecretFlags password_flags;
 	guint32 mtu;
+	NMSettingSecretFlags password_flags;
 } NMSettingCdmaPrivate;
 
 G_DEFINE_TYPE (NMSettingCdma, nm_setting_cdma, NM_TYPE_SETTING)
@@ -350,7 +350,6 @@ nm_setting_cdma_class_init (NMSettingCdmaClass *klass)
 	    g_param_spec_uint (NM_SETTING_CDMA_MTU, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c
index 17058ea7..2e8fa37a 100644
--- a/libnm-core/nm-setting-connection.c
+++ b/libnm-core/nm-setting-connection.c
@@ -66,6 +66,8 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingConnection,
 );
 
 typedef struct {
+	GSList *permissions; /* list of Permission structs */
+	GSList *secondaries; /* secondary connections to activate with the base connection */
 	char *id;
 	char *uuid;
 	char *stable_id;
@@ -73,23 +75,21 @@ typedef struct {
 	char *type;
 	char *master;
 	char *slave_type;
-	NMSettingConnectionAutoconnectSlaves autoconnect_slaves;
-	GSList *permissions; /* list of Permission structs */
-	gboolean autoconnect;
+	char *zone;
+	guint64 timestamp;
 	int autoconnect_priority;
 	int autoconnect_retries;
 	int multi_connect;
-	guint64 timestamp;
-	gboolean read_only;
-	char *zone;
-	GSList *secondaries; /* secondary connections to activate with the base connection */
-	guint gateway_ping_timeout;
-	NMMetered metered;
-	NMSettingConnectionLldp lldp;
 	int auth_retries;
 	int mdns;
 	int llmnr;
 	int wait_device_timeout;
+	guint gateway_ping_timeout;
+	NMSettingConnectionAutoconnectSlaves autoconnect_slaves;
+	NMMetered metered;
+	NMSettingConnectionLldp lldp;
+	bool read_only:1;
+	bool autoconnect:1;
 } NMSettingConnectionPrivate;
 
 G_DEFINE_TYPE (NMSettingConnection, nm_setting_connection, NM_TYPE_SETTING)
@@ -1032,22 +1032,50 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 
 	if (priv->interface_name) {
 		GError *tmp_error = NULL;
-		gboolean valid_ifname = FALSE;
+		NMUtilsIfaceType iface_type;
 
-		/* do not perform a interface name length check for OVS connection types
-		 * as they don't have a corresponding kernel link that enforces the 15 bytes limit.
-		 * Here we're whitelisting the OVS interface type as well, even if most OVS
-		 * iface types do have the limit, to let the OVS specific nm-setting verify whether the iface name
-		 * is good or not according to the internal type (internal, patch, ...) */
 		if (NM_IN_STRSET (type,
 		                  NM_SETTING_OVS_BRIDGE_SETTING_NAME,
-		                  NM_SETTING_OVS_PORT_SETTING_NAME,
-		                  NM_SETTING_OVS_INTERFACE_SETTING_NAME))
-			valid_ifname = nm_utils_ifname_valid (priv->interface_name, NMU_IFACE_OVS, &tmp_error);
-		else
-			valid_ifname = nm_utils_ifname_valid (priv->interface_name, NMU_IFACE_KERNEL, &tmp_error);
+		                  NM_SETTING_OVS_PORT_SETTING_NAME))
+			iface_type = NMU_IFACE_OVS;
+		else if (nm_streq (type, NM_SETTING_OVS_INTERFACE_SETTING_NAME)) {
+			NMSettingOvsInterface *s_ovs_iface = NULL;
+			const char *ovs_iface_type;
+
+			if (connection)
+				s_ovs_iface = nm_connection_get_setting_ovs_interface (connection);
+			_nm_setting_ovs_interface_verify_interface_type (s_ovs_iface,
+			                                                 s_ovs_iface ? nm_setting_ovs_interface_get_interface_type (s_ovs_iface) : NULL,
+			                                                 connection,
+			                                                 FALSE,
+			                                                 NULL,
+			                                                 &ovs_iface_type,
+			                                                 NULL);
+			if (!ovs_iface_type) {
+				/* We cannot determine to OVS interface type. Consequently, we cannot
+				 * fully validate the interface name.
+				 *
+				 * If we have a connection (and we do a full validation anyway), skip the
+				 * check. The connection will fail validation when we validate the OVS setting.
+				 *
+				 * Otherwise, do the most basic validation.
+				 */
+				if (connection)
+					goto after_interface_name;
+				iface_type = NMU_IFACE_ANY;
+			} else if (NM_IN_STRSET (ovs_iface_type, "patch")) {
+				/* this interface type is internal to OVS. */
+				iface_type = NMU_IFACE_OVS;
+			} else {
+				/* This interface type also requires a netdev. We need to validate
+				 * for both OVS and KERNEL. */
+				nm_assert (NM_IN_STRSET (ovs_iface_type, "internal", "system", "dpdk"));
+				iface_type = NMU_IFACE_OVS_AND_KERNEL;
+			}
+		} else
+			iface_type = NMU_IFACE_KERNEL;
 
-		if (!valid_ifname) {
+		if (!nm_utils_ifname_valid (priv->interface_name, iface_type, &tmp_error)) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -1057,6 +1085,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 			return FALSE;
 		}
 	}
+after_interface_name:
 
 	is_slave = FALSE;
 	slave_setting_type = NULL;
@@ -1251,11 +1280,14 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 }
 
 static const char *
-find_virtual_interface_name (GVariant *connection_dict)
+find_virtual_interface_name (GVariant *connection_dict,
+                             GVariant **variant_to_free)
 {
 	GVariant *setting_dict;
 	const char *interface_name;
 
+	nm_assert (variant_to_free && !*variant_to_free);
+
 	setting_dict = g_variant_lookup_value (connection_dict, NM_SETTING_BOND_SETTING_NAME, NM_VARIANT_TYPE_SETTING);
 	if (!setting_dict)
 		setting_dict = g_variant_lookup_value (connection_dict, NM_SETTING_BRIDGE_SETTING_NAME, NM_VARIANT_TYPE_SETTING);
@@ -1267,40 +1299,16 @@ find_virtual_interface_name (GVariant *connection_dict)
 	if (!setting_dict)
 		return NULL;
 
+	*variant_to_free = setting_dict;
+
 	/* All of the deprecated virtual interface name properties were named "interface-name". */
 	if (!g_variant_lookup (setting_dict, "interface-name", "&s", &interface_name))
 		interface_name = NULL;
 
-	g_variant_unref (setting_dict);
 	return interface_name;
 }
 
 static gboolean
-nm_setting_connection_set_interface_name (NMSetting *setting,
-                                          GVariant *connection_dict,
-                                          const char *property,
-                                          GVariant *value,
-                                          NMSettingParseFlags parse_flags,
-                                          GError **error)
-{
-	const char *interface_name;
-
-	/* For compatibility reasons, if there is an invalid virtual interface name,
-	 * we need to make verification fail, even if that virtual name would be
-	 * overridden by a valid connection.interface-name.
-	 */
-	interface_name = find_virtual_interface_name (connection_dict);
-	if (!interface_name || nm_utils_ifname_valid_kernel (interface_name, NULL))
-		interface_name = g_variant_get_string (value, NULL);
-
-	g_object_set (G_OBJECT (setting),
-	              NM_SETTING_CONNECTION_INTERFACE_NAME, interface_name,
-	              NULL);
-
-	return TRUE;
-}
-
-static gboolean
 nm_setting_connection_no_interface_name (NMSetting *setting,
                                          GVariant *connection_dict,
                                          const char *property,
@@ -1308,8 +1316,9 @@ nm_setting_connection_no_interface_name (NMSetting *setting,
                                          GError **error)
 {
 	const char *virtual_interface_name;
+	gs_unref_variant GVariant *variant_to_free = NULL;
 
-	virtual_interface_name = find_virtual_interface_name (connection_dict);
+	virtual_interface_name = find_virtual_interface_name (connection_dict, &variant_to_free);
 	g_object_set (G_OBJECT (setting),
 	              NM_SETTING_CONNECTION_INTERFACE_NAME, virtual_interface_name,
 	              NULL);
@@ -1566,9 +1575,15 @@ nm_setting_connection_init (NMSettingConnection *setting)
 {
 	NMSettingConnectionPrivate *priv = NM_SETTING_CONNECTION_GET_PRIVATE (setting);
 
-	priv->mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
-	priv->llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
-	priv->wait_device_timeout = -1;
+	priv->auth_retries         = -1;
+	priv->autoconnect          = TRUE;
+	priv->autoconnect_priority = NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT;
+	priv->autoconnect_retries  = -1;
+	priv->autoconnect_slaves   = NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT;
+	priv->lldp                 = NM_SETTING_CONNECTION_LLDP_DEFAULT;
+	priv->llmnr                = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
+	priv->mdns                 = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+	priv->wait_device_timeout  = -1;
 }
 
 /**
@@ -1753,7 +1768,6 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	                              obj_properties[PROP_INTERFACE_NAME],
 	                              NM_SETT_INFO_PROPERT_TYPE (
 	                                  .dbus_type             = G_VARIANT_TYPE_STRING,
-	                                  .from_dbus_fcn         = nm_setting_connection_set_interface_name,
 	                                  .missing_from_dbus_fcn = nm_setting_connection_no_interface_name,
 	                              ));
 
@@ -1836,7 +1850,6 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	    g_param_spec_boolean (NM_SETTING_CONNECTION_AUTOCONNECT, "", "",
 	                          TRUE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_FUZZY_IGNORE |
 	                          G_PARAM_STATIC_STRINGS);
 
@@ -1863,7 +1876,6 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	                       NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MAX,
 	                       NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1889,7 +1901,6 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	     g_param_spec_int (NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES, "", "",
 	                       -1, G_MAXINT32, -1,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1906,7 +1917,7 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	 * variable: MULTI_CONNECT(+)
 	 * description: whether the profile can be active on multiple devices at a given
 	 *   moment. The values are numbers corresponding to #NMConnectionMultiConnect enum.
-	 * example: ZONE=3
+	 * example: MULTI_CONNECT=3
 	 * ---end---
 	 */
 	obj_properties[PROP_MULTI_CONNECT] =
@@ -1931,7 +1942,6 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	    g_param_spec_uint64 (NM_SETTING_CONNECTION_TIMESTAMP, "", "",
 	                         0, G_MAXUINT64, 0,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_FUZZY_IGNORE |
 	                         G_PARAM_STATIC_STRINGS);
 	_nm_properties_override_gobj (properties_override,
@@ -1952,7 +1962,6 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	    g_param_spec_boolean (NM_SETTING_CONNECTION_READ_ONLY, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_FUZZY_IGNORE |
 	                          G_PARAM_STATIC_STRINGS);
 
@@ -1979,7 +1988,6 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	    g_param_spec_string (NM_SETTING_CONNECTION_ZONE, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_FUZZY_IGNORE |
 	                         NM_SETTING_PARAM_REAPPLY_IMMEDIATELY |
 	                         G_PARAM_STATIC_STRINGS);
@@ -2059,7 +2067,6 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	                       NM_TYPE_SETTING_CONNECTION_AUTOCONNECT_SLAVES,
 	                       NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -2103,7 +2110,6 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	    g_param_spec_uint (NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, "", "",
 	                       0, 600, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -2153,7 +2159,6 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	                      G_MININT32, G_MAXINT32, NM_SETTING_CONNECTION_LLDP_DEFAULT,
 	                      NM_SETTING_PARAM_FUZZY_IGNORE |
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -2178,7 +2183,6 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	    g_param_spec_int (NM_SETTING_CONNECTION_AUTH_RETRIES, "", "",
 	                      -1, G_MAXINT32, -1,
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      NM_SETTING_PARAM_FUZZY_IGNORE |
 	                      G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-dcb.c b/libnm-core/nm-setting-dcb.c
index 3c12abdf..415eb37b 100644
--- a/libnm-core/nm-setting-dcb.c
+++ b/libnm-core/nm-setting-dcb.c
@@ -49,27 +49,21 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingDcb,
 );
 
 typedef struct {
-	NMSettingDcbFlags app_fcoe_flags;
-	int               app_fcoe_priority;
 	char *            app_fcoe_mode;
-
-	NMSettingDcbFlags app_iscsi_flags;
-	int               app_iscsi_priority;
-
-	NMSettingDcbFlags app_fip_flags;
-	int               app_fip_priority;
-
-	/* Priority Flow Control */
-	NMSettingDcbFlags pfc_flags;
 	guint             pfc[8];
-
-	/* Priority Groups */
-	NMSettingDcbFlags priority_group_flags;
 	guint             priority_group_id[8];
 	guint             priority_group_bandwidth[8];
 	guint             priority_bandwidth[8];
 	guint             priority_strict[8];
 	guint             priority_traffic_class[8];
+	int               app_fcoe_priority;
+	int               app_iscsi_priority;
+	int               app_fip_priority;
+	NMSettingDcbFlags app_fcoe_flags;
+	NMSettingDcbFlags app_iscsi_flags;
+	NMSettingDcbFlags app_fip_flags;
+	NMSettingDcbFlags pfc_flags;
+	NMSettingDcbFlags priority_group_flags;
 } NMSettingDcbPrivate;
 
 G_DEFINE_TYPE (NMSettingDcb, nm_setting_dcb, NM_TYPE_SETTING)
@@ -872,8 +866,14 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_setting_dcb_init (NMSettingDcb *setting)
+nm_setting_dcb_init (NMSettingDcb *self)
 {
+	NMSettingDcbPrivate *priv = NM_SETTING_DCB_GET_PRIVATE (self);
+
+	priv->app_fcoe_mode      = g_strdup (NM_SETTING_DCB_FCOE_MODE_FABRIC);
+	priv->app_fcoe_priority  = -1;
+	priv->app_fip_priority   = -1;
+	priv->app_iscsi_priority = -1;
 }
 
 /**
@@ -954,7 +954,6 @@ nm_setting_dcb_class_init (NMSettingDcbClass *klass)
 	    g_param_spec_int (NM_SETTING_DCB_APP_FCOE_PRIORITY, "", "",
 	                      -1, 7, -1,
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -975,7 +974,6 @@ nm_setting_dcb_class_init (NMSettingDcbClass *klass)
 	    g_param_spec_string (NM_SETTING_DCB_APP_FCOE_MODE, "", "",
 	                         NM_SETTING_DCB_FCOE_MODE_FABRIC,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -1017,7 +1015,6 @@ nm_setting_dcb_class_init (NMSettingDcbClass *klass)
 	    g_param_spec_int (NM_SETTING_DCB_APP_ISCSI_PRIORITY, "", "",
 	                      -1, 7, -1,
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -1059,7 +1056,6 @@ nm_setting_dcb_class_init (NMSettingDcbClass *klass)
 	    g_param_spec_int (NM_SETTING_DCB_APP_FIP_PRIORITY, "", "",
 	                      -1, 7, -1,
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      G_PARAM_STATIC_STRINGS);
 
 	/**
diff --git a/libnm-core/nm-setting-gsm.c b/libnm-core/nm-setting-gsm.c
index 4bc957d3..19e2554a 100644
--- a/libnm-core/nm-setting-gsm.c
+++ b/libnm-core/nm-setting-gsm.c
@@ -41,26 +41,20 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 );
 
 typedef struct {
-	gboolean auto_config;
-
 	char *number; /* For dialing, duh */
 	char *username;
 	char *password;
-	NMSettingSecretFlags password_flags;
-
-	/* Restrict connection to certain devices or SIMs */
 	char *device_id;
 	char *sim_id;
 	char *sim_operator_id;
-
 	char *apn; /* NULL for dynamic */
 	char *network_id; /* for manual registration or NULL for automatic */
-
 	char *pin;
+	NMSettingSecretFlags password_flags;
 	NMSettingSecretFlags pin_flags;
-
-	gboolean home_only;
 	guint32 mtu;
+	bool auto_config:1;
+	bool home_only:1;
 } NMSettingGsmPrivate;
 
 G_DEFINE_TYPE (NMSettingGsm, nm_setting_gsm, NM_TYPE_SETTING)
@@ -91,7 +85,7 @@ nm_setting_gsm_get_auto_config (NMSettingGsm *setting)
  *
  * Returns: the #NMSettingGsm:number property of the setting
  *
- * Deprecated: 1.16:  user-provided values for this setting are no longer used.
+ * Deprecated: 1.16: User-provided values for this setting are no longer used.
  **/
 const char *
 nm_setting_gsm_get_number (NMSettingGsm *setting)
@@ -665,7 +659,7 @@ nm_setting_gsm_class_init (NMSettingGsmClass *klass)
 	 * Legacy setting that used to help establishing PPP data sessions for
 	 * GSM-based modems.
 	 *
-	 * Deprecated: 1.16: user-provided values for this setting are no longer used.
+	 * Deprecated: 1.16: User-provided values for this setting are no longer used.
 	 **/
 	obj_properties[PROP_NUMBER] =
 	    g_param_spec_string (NM_SETTING_GSM_NUMBER, "", "",
@@ -842,7 +836,6 @@ nm_setting_gsm_class_init (NMSettingGsmClass *klass)
 	    g_param_spec_uint (NM_SETTING_GSM_MTU, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-infiniband.c b/libnm-core/nm-setting-infiniband.c
index fcaa1f9a..bfce580e 100644
--- a/libnm-core/nm-setting-infiniband.c
+++ b/libnm-core/nm-setting-infiniband.c
@@ -35,9 +35,10 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 typedef struct {
 	char *mac_address;
 	char *transport_mode;
-	guint32 mtu;
+	char *parent;
+	char *virtual_iface_name;
 	int p_key;
-	char *parent, *virtual_iface_name;
+	guint32 mtu;
 } NMSettingInfinibandPrivate;
 
 G_DEFINE_TYPE (NMSettingInfiniband, nm_setting_infiniband, NM_TYPE_SETTING)
@@ -210,39 +211,23 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		s_con = nm_connection_get_setting_connection (connection);
 	if (s_con) {
 		const char *interface_name = nm_setting_connection_get_interface_name (s_con);
-		GError *tmp_error = NULL;
 
-		if (!interface_name)
-			;
-		else if (!nm_utils_ifname_valid_kernel (interface_name, &tmp_error)) {
-			/* report the error for NMSettingConnection:interface-name, because
-			 * it's that property that is invalid -- although we currently verify()
-			 * NMSettingInfiniband.
-			 **/
-			g_set_error (error,
-			             NM_CONNECTION_ERROR,
-			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			             "'%s': %s", interface_name, tmp_error->message);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_CONNECTION_SETTING_NAME, NM_SETTING_CONNECTION_INTERFACE_NAME);
-			g_error_free (tmp_error);
-			return FALSE;
-		} else {
-			if (priv->p_key != -1) {
-				if (!priv->virtual_iface_name)
-					priv->virtual_iface_name = g_strdup_printf ("%s.%04x", priv->parent, priv->p_key);
-
-				if (strcmp (interface_name, priv->virtual_iface_name) != 0) {
-					/* We don't support renaming software infiniband devices. Later we might, but
-					 * for now just reject such connections.
-					 **/
-					g_set_error (error,
-					             NM_CONNECTION_ERROR,
-					             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-					             _("interface name of software infiniband device must be '%s' or unset (instead it is '%s')"),
-					             priv->virtual_iface_name, interface_name);
-					g_prefix_error (error, "%s.%s: ", NM_SETTING_CONNECTION_SETTING_NAME, NM_SETTING_CONNECTION_INTERFACE_NAME);
-					return FALSE;
-				}
+		if (   interface_name
+		    && priv->p_key != -1) {
+			if (!priv->virtual_iface_name)
+				priv->virtual_iface_name = g_strdup_printf ("%s.%04x", priv->parent, priv->p_key);
+
+			if (strcmp (interface_name, priv->virtual_iface_name) != 0) {
+				/* We don't support renaming software infiniband devices. Later we might, but
+				 * for now just reject such connections.
+				 **/
+				g_set_error (error,
+				             NM_CONNECTION_ERROR,
+				             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				             _("interface name of software infiniband device must be '%s' or unset (instead it is '%s')"),
+				             priv->virtual_iface_name, interface_name);
+				g_prefix_error (error, "%s.%s: ", NM_SETTING_CONNECTION_SETTING_NAME, NM_SETTING_CONNECTION_INTERFACE_NAME);
+				return FALSE;
 			}
 		}
 	}
@@ -322,12 +307,12 @@ set_property (GObject *object, guint prop_id,
 		break;
 	case PROP_P_KEY:
 		priv->p_key = g_value_get_int (value);
-		g_clear_pointer (&priv->virtual_iface_name, g_free);
+		nm_clear_g_free (&priv->virtual_iface_name);
 		break;
 	case PROP_PARENT:
 		g_free (priv->parent);
 		priv->parent = g_value_dup_string (value);
-		g_clear_pointer (&priv->virtual_iface_name, g_free);
+		nm_clear_g_free (&priv->virtual_iface_name);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -338,8 +323,11 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_setting_infiniband_init (NMSettingInfiniband *setting)
+nm_setting_infiniband_init (NMSettingInfiniband *self)
 {
+	NMSettingInfinibandPrivate *priv = NM_SETTING_INFINIBAND_GET_PRIVATE (self);
+
+	priv->p_key = -1;
 }
 
 /**
@@ -414,7 +402,7 @@ nm_setting_infiniband_class_init (NMSettingInfinibandClass *klass)
 	                         G_PARAM_READWRITE |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
-	_nm_properties_override_gobj (properties_override, obj_properties[PROP_MAC_ADDRESS], &nm_sett_info_propert_type_mac_addrees);
+	_nm_properties_override_gobj (properties_override, obj_properties[PROP_MAC_ADDRESS], &nm_sett_info_propert_type_mac_address);
 
 	/**
 	 * NMSettingInfiniband:mtu:
@@ -432,7 +420,6 @@ nm_setting_infiniband_class_init (NMSettingInfinibandClass *klass)
 	    g_param_spec_uint (NM_SETTING_INFINIBAND_MTU, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -454,7 +441,6 @@ nm_setting_infiniband_class_init (NMSettingInfinibandClass *klass)
 	    g_param_spec_string (NM_SETTING_INFINIBAND_TRANSPORT_MODE, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
@@ -480,7 +466,6 @@ nm_setting_infiniband_class_init (NMSettingInfinibandClass *klass)
 	    g_param_spec_int (NM_SETTING_INFINIBAND_P_KEY, "", "",
 	                      -1, 0xFFFF, -1,
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      NM_SETTING_PARAM_INFERRABLE |
 	                      G_PARAM_STATIC_STRINGS);
 
@@ -504,7 +489,6 @@ nm_setting_infiniband_class_init (NMSettingInfinibandClass *klass)
 	    g_param_spec_string (NM_SETTING_INFINIBAND_PARENT, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-ip-config.c b/libnm-core/nm-setting-ip-config.c
index 7f1690ac..55cec35f 100644
--- a/libnm-core/nm-setting-ip-config.c
+++ b/libnm-core/nm-setting-ip-config.c
@@ -50,19 +50,6 @@ const NMUtilsDNSOptionDesc _nm_utils_dns_option_descs[] = {
 	{ NULL,                                        FALSE,   FALSE }
 };
 
-static int
-_addr_size (int family)
-{
-	switch (family) {
-	case AF_INET:
-		return sizeof (in_addr_t);
-	case AF_INET6:
-		return sizeof (struct in6_addr);
-	default:
-		g_return_val_if_reached (0);
-	}
-}
-
 static char *
 canonicalize_ip (int family, const char *ip, gboolean null_any)
 {
@@ -84,7 +71,7 @@ canonicalize_ip (int family, const char *ip, gboolean null_any)
 	g_return_val_if_fail (ret == 1, NULL);
 
 	if (null_any) {
-		if (!memcmp (addr_bytes, &in6addr_any, _addr_size (family)))
+		if (!memcmp (addr_bytes, &in6addr_any, nm_utils_addr_family_to_size (family)))
 			return NULL;
 	}
 
@@ -106,7 +93,7 @@ canonicalize_ip_binary (int family, gconstpointer ip, gboolean null_any)
 		g_return_val_if_reached (NULL);
 	}
 	if (null_any) {
-		if (!memcmp (ip, &in6addr_any, _addr_size (family)))
+		if (!memcmp (ip, &in6addr_any, nm_utils_addr_family_to_size (family)))
 			return NULL;
 	}
 	return g_strdup (inet_ntop (family, ip, string, sizeof (string)));
@@ -120,7 +107,7 @@ valid_ip (int family, const char *ip, GError **error)
 		             family == AF_INET ? _("Missing IPv4 address") : _("Missing IPv6 address"));
 		return FALSE;
 	}
-	if (!nm_utils_ipaddr_valid (family, ip)) {
+	if (!nm_utils_ipaddr_is_valid (family, ip)) {
 		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
 		             family == AF_INET ? _("Invalid IPv4 address '%s'") : _("Invalid IPv6 address '%s'"),
 		             ip);
@@ -446,7 +433,7 @@ nm_ip_address_set_address (NMIPAddress *address,
 {
 	g_return_if_fail (address != NULL);
 	g_return_if_fail (addr != NULL);
-	g_return_if_fail (nm_utils_ipaddr_valid (address->family, addr));
+	g_return_if_fail (nm_utils_ipaddr_is_valid (address->family, addr));
 
 	g_free (address->address);
 	address->address = canonicalize_ip (address->family, addr, FALSE);
@@ -900,14 +887,14 @@ nm_ip_route_get_dest (NMIPRoute *route)
  * Sets the destination property of this route object.
  *
  * @dest must be a valid address of @route's family. If you aren't sure you
- * have a valid address, use nm_utils_ipaddr_valid() to check it.
+ * have a valid address, use nm_utils_ipaddr_is_valid() to check it.
  **/
 void
 nm_ip_route_set_dest (NMIPRoute *route,
                       const char *dest)
 {
 	g_return_if_fail (route != NULL);
-	g_return_if_fail (nm_utils_ipaddr_valid (route->family, dest));
+	g_return_if_fail (nm_utils_ipaddr_is_valid (route->family, dest));
 
 	g_free (route->dest);
 	route->dest = canonicalize_ip (route->family, dest, FALSE);
@@ -1022,7 +1009,7 @@ nm_ip_route_set_next_hop (NMIPRoute *route,
                           const char *next_hop)
 {
 	g_return_if_fail (route != NULL);
-	g_return_if_fail (!next_hop || nm_utils_ipaddr_valid (route->family, next_hop));
+	g_return_if_fail (!next_hop || nm_utils_ipaddr_is_valid (route->family, next_hop));
 
 	g_free (route->next_hop);
 	route->next_hop = canonicalize_ip (route->family, next_hop, TRUE);
@@ -1051,7 +1038,7 @@ nm_ip_route_get_next_hop_binary (NMIPRoute *route,
 		inet_pton (route->family, route->next_hop, next_hop);
 		return TRUE;
 	} else {
-		memset (next_hop, 0, _addr_size (route->family));
+		memset (next_hop, 0, nm_utils_addr_family_to_size (route->family));
 		return FALSE;
 	}
 }
@@ -1310,7 +1297,7 @@ nm_ip_route_attribute_validate  (const char *name,
 
 		switch (spec->str_type) {
 		case 'a': /* IP address */
-			if (!nm_utils_ipaddr_valid (family, string)) {
+			if (!nm_utils_ipaddr_is_valid (family, string)) {
 				g_set_error (error,
 				             NM_CONNECTION_ERROR,
 				             NM_CONNECTION_ERROR_FAILED,
@@ -1338,7 +1325,7 @@ nm_ip_route_attribute_validate  (const char *name,
 					return FALSE;
 				}
 			}
-			if (!nm_utils_ipaddr_valid (family, addr)) {
+			if (!nm_utils_ipaddr_is_valid (family, addr)) {
 				g_set_error (error,
 				             NM_CONNECTION_ERROR,
 				             NM_CONNECTION_ERROR_FAILED,
@@ -3598,27 +3585,27 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingIPConfig,
 );
 
 typedef struct {
-	char *method;
-	GPtrArray *dns;        /* array of IP address strings */
-	GPtrArray *dns_search; /* array of domain name strings */
-	GPtrArray *dns_options;/* array of DNS options */
-	int dns_priority;
-	GPtrArray *addresses;  /* array of NMIPAddress */
-	GPtrArray *routes;     /* array of NMIPRoute */
+	GPtrArray *dns;         /* array of IP address strings */
+	GPtrArray *dns_search;  /* array of domain name strings */
+	GPtrArray *dns_options; /* array of DNS options */
+	GPtrArray *addresses;   /* array of NMIPAddress */
+	GPtrArray *routes;      /* array of NMIPRoute */
 	GPtrArray *routing_rules;
-	gint64 route_metric;
-	guint32 route_table;
+	char *method;
 	char *gateway;
-	gboolean ignore_auto_routes;
-	gboolean ignore_auto_dns;
 	char *dhcp_hostname;
-	gboolean dhcp_send_hostname;
-	gboolean never_default;
-	gboolean may_fail;
-	int dad_timeout;
-	int dhcp_timeout;
 	char *dhcp_iaid;
+	gint64 route_metric;
 	guint dhcp_hostname_flags;
+	int dns_priority;
+	int dad_timeout;
+	int dhcp_timeout;
+	guint32 route_table;
+	bool ignore_auto_routes:1;
+	bool ignore_auto_dns:1;
+	bool dhcp_send_hostname:1;
+	bool never_default:1;
+	bool may_fail:1;
 } NMSettingIPConfigPrivate;
 
 G_DEFINE_ABSTRACT_TYPE (NMSettingIPConfig, nm_setting_ip_config, NM_TYPE_SETTING)
@@ -3698,7 +3685,7 @@ nm_setting_ip_config_add_dns (NMSettingIPConfig *setting, const char *dns)
 
 	g_return_val_if_fail (NM_IS_SETTING_IP_CONFIG (setting), FALSE);
 	g_return_val_if_fail (dns != NULL, FALSE);
-	g_return_val_if_fail (nm_utils_ipaddr_valid (NM_SETTING_IP_CONFIG_GET_FAMILY (setting), dns), FALSE);
+	g_return_val_if_fail (nm_utils_ipaddr_is_valid (NM_SETTING_IP_CONFIG_GET_FAMILY (setting), dns), FALSE);
 
 	priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
 
@@ -3754,7 +3741,7 @@ nm_setting_ip_config_remove_dns_by_value (NMSettingIPConfig *setting, const char
 
 	g_return_val_if_fail (NM_IS_SETTING_IP_CONFIG (setting), FALSE);
 	g_return_val_if_fail (dns != NULL, FALSE);
-	g_return_val_if_fail (nm_utils_ipaddr_valid (NM_SETTING_IP_CONFIG_GET_FAMILY (setting), dns), FALSE);
+	g_return_val_if_fail (nm_utils_ipaddr_is_valid (NM_SETTING_IP_CONFIG_GET_FAMILY (setting), dns), FALSE);
 
 	priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
 
@@ -4959,7 +4946,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	for (i = 0; i < priv->dns->len; i++) {
 		const char *dns = priv->dns->pdata[i];
 
-		if (!nm_utils_ipaddr_valid (NM_SETTING_IP_CONFIG_GET_FAMILY (setting), dns)) {
+		if (!nm_utils_ipaddr_is_valid (NM_SETTING_IP_CONFIG_GET_FAMILY (setting), dns)) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -5019,7 +5006,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 			return FALSE;
 		}
 
-		if (!nm_utils_ipaddr_valid (NM_SETTING_IP_CONFIG_GET_FAMILY (setting), priv->gateway)) {
+		if (!nm_utils_ipaddr_is_valid (NM_SETTING_IP_CONFIG_GET_FAMILY (setting), priv->gateway)) {
 			g_set_error_literal (error,
 			                     NM_CONNECTION_ERROR,
 			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -5441,7 +5428,7 @@ set_property (GObject *object, guint prop_id,
 		break;
 	case PROP_GATEWAY:
 		gateway = g_value_get_string (value);
-		g_return_if_fail (!gateway || nm_utils_ipaddr_valid (NM_SETTING_IP_CONFIG_GET_FAMILY (setting), gateway));
+		g_return_if_fail (!gateway || nm_utils_ipaddr_is_valid (NM_SETTING_IP_CONFIG_GET_FAMILY (setting), gateway));
 		g_free (priv->gateway);
 		priv->gateway = canonicalize_ip (NM_SETTING_IP_CONFIG_GET_FAMILY (setting), gateway, TRUE);
 		break;
@@ -5501,11 +5488,14 @@ nm_setting_ip_config_init (NMSettingIPConfig *setting)
 {
 	NMSettingIPConfigPrivate *priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
 
-	priv->dns = g_ptr_array_new_with_free_func (g_free);
-	priv->dns_search = g_ptr_array_new_with_free_func (g_free);
-	priv->dns_options = NULL;
-	priv->addresses = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_address_unref);
-	priv->routes = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_route_unref);
+	priv->dns                = g_ptr_array_new_with_free_func (g_free);
+	priv->dns_search         = g_ptr_array_new_with_free_func (g_free);
+	priv->addresses          = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_address_unref);
+	priv->routes             = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_ip_route_unref);
+	priv->route_metric       = -1;
+	priv->dhcp_send_hostname = TRUE;
+	priv->may_fail           = TRUE;
+	priv->dad_timeout        = -1;
 }
 
 static void
@@ -5628,7 +5618,7 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *klass)
 	 * The relative priority for DNS servers specified by this setting.  A lower
 	 * value is better (higher priority). Zero selects a globally configured
 	 * default value. If the latter is missing or zero too, it defaults to
-	 * 50 for VPNs and 100 for other connections.
+	 * 50 for VPNs (including WireGuard) and 100 for other connections.
 	 *
 	 * Note that the priority is to order DNS settings for multiple active
 	 * connections.  It does not disambiguate multiple DNS servers within the
@@ -5660,7 +5650,6 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *klass)
 	     g_param_spec_int (NM_SETTING_IP_CONFIG_DNS_PRIORITY, "", "",
 	                       G_MININT32, G_MAXINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -5725,7 +5714,6 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *klass)
 	     g_param_spec_int64 (NM_SETTING_IP_CONFIG_ROUTE_METRIC, "", "",
 	                         -1, G_MAXUINT32, -1,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -5766,7 +5754,6 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *klass)
 	    g_param_spec_boolean (NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -5782,7 +5769,6 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *klass)
 	    g_param_spec_boolean (NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -5812,7 +5798,6 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *klass)
 	    g_param_spec_boolean (NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, "", "",
 	                          TRUE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -5826,7 +5811,6 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *klass)
 	    g_param_spec_boolean (NM_SETTING_IP_CONFIG_NEVER_DEFAULT, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -5844,7 +5828,6 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *klass)
 	    g_param_spec_boolean (NM_SETTING_IP_CONFIG_MAY_FAIL, "", "",
 	                          TRUE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -5865,7 +5848,6 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *klass)
 	    g_param_spec_int (NM_SETTING_IP_CONFIG_DAD_TIMEOUT, "", "",
 	                       -1, NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX, -1,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-ip-tunnel.c b/libnm-core/nm-setting-ip-tunnel.c
index ae1df194..f6690e8b 100644
--- a/libnm-core/nm-setting-ip-tunnel.c
+++ b/libnm-core/nm-setting-ip-tunnel.c
@@ -35,18 +35,18 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 
 typedef struct {
 	char *parent;
-	NMIPTunnelMode mode;
 	char *local;
 	char *remote;
-	guint ttl;
-	guint tos;
-	gboolean path_mtu_discovery;
 	char *input_key;
 	char *output_key;
+	guint ttl;
+	guint tos;
 	guint encapsulation_limit;
 	guint flow_label;
+	NMIPTunnelMode mode;
 	guint32 mtu;
 	guint32 flags;
+	bool path_mtu_discovery:1;
 } NMSettingIPTunnelPrivate;
 
 G_DEFINE_TYPE (NMSettingIPTunnel, nm_setting_ip_tunnel, NM_TYPE_SETTING)
@@ -341,7 +341,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (priv->local && !nm_utils_ipaddr_valid (family, priv->local)) {
+	if (priv->local && !nm_utils_ipaddr_is_valid (family, priv->local)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -361,7 +361,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (!nm_utils_ipaddr_valid (family, priv->remote)) {
+	if (!nm_utils_ipaddr_is_valid (family, priv->remote)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -576,8 +576,11 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_setting_ip_tunnel_init (NMSettingIPTunnel *setting)
+nm_setting_ip_tunnel_init (NMSettingIPTunnel *self)
 {
+	NMSettingIPTunnelPrivate *priv = NM_SETTING_IP_TUNNEL_GET_PRIVATE (self);
+
+	priv->path_mtu_discovery = TRUE;
 }
 
 /**
@@ -697,7 +700,6 @@ nm_setting_ip_tunnel_class_init (NMSettingIPTunnelClass *klass)
 	    g_param_spec_uint (NM_SETTING_IP_TUNNEL_TTL, "", "",
 	                       0, 255, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -713,7 +715,6 @@ nm_setting_ip_tunnel_class_init (NMSettingIPTunnelClass *klass)
 	    g_param_spec_uint (NM_SETTING_IP_TUNNEL_TOS, "", "",
 	                       0, 255, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -728,7 +729,6 @@ nm_setting_ip_tunnel_class_init (NMSettingIPTunnelClass *klass)
 	    g_param_spec_boolean (NM_SETTING_IP_TUNNEL_PATH_MTU_DISCOVERY, "", "",
 	                          TRUE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_INFERRABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
@@ -774,7 +774,6 @@ nm_setting_ip_tunnel_class_init (NMSettingIPTunnelClass *klass)
 	    g_param_spec_uint (NM_SETTING_IP_TUNNEL_ENCAPSULATION_LIMIT, "", "",
 	                       0, 255, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -790,7 +789,6 @@ nm_setting_ip_tunnel_class_init (NMSettingIPTunnelClass *klass)
 	    g_param_spec_uint (NM_SETTING_IP_TUNNEL_FLOW_LABEL, "", "",
 	                       0, (1 << 20) - 1, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -806,7 +804,6 @@ nm_setting_ip_tunnel_class_init (NMSettingIPTunnelClass *klass)
 	    g_param_spec_uint (NM_SETTING_IP_TUNNEL_MTU, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-ip6-config.c b/libnm-core/nm-setting-ip6-config.c
index f3306ade..c7c2c48d 100644
--- a/libnm-core/nm-setting-ip6-config.c
+++ b/libnm-core/nm-setting-ip6-config.c
@@ -143,7 +143,7 @@ nm_setting_ip6_config_get_dhcp_duid (NMSettingIP6Config *setting)
  * Returns: The configured %NM_SETTING_IP6_CONFIG_RA_TIMEOUT value with the
  * timeout for router advertisements in seconds.
  *
- * Since: 1.24, 1.22.8
+ * Since: 1.24
  **/
 gint32
 nm_setting_ip6_config_get_ra_timeout (NMSettingIP6Config *setting)
@@ -254,7 +254,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 				return FALSE;
 			}
 
-			if (g_strcmp0 (priv->token, nm_utils_inet6_ntop (&i6_token, s_token)))
+			if (g_strcmp0 (priv->token, _nm_utils_inet6_ntop (&i6_token, s_token)))
 				token_needs_normalization = TRUE;
 		} else {
 			g_set_error_literal (error,
@@ -538,6 +538,10 @@ set_property (GObject *object, guint prop_id,
 static void
 nm_setting_ip6_config_init (NMSettingIP6Config *setting)
 {
+	NMSettingIP6ConfigPrivate *priv = NM_SETTING_IP6_CONFIG_GET_PRIVATE (setting);
+
+	priv->ip6_privacy   = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
+	priv->addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY;
 }
 
 /**
@@ -787,7 +791,6 @@ nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *klass)
 	                       NM_TYPE_SETTING_IP6_CONFIG_PRIVACY,
 	                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -836,7 +839,6 @@ nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *klass)
 	                      G_MININT, G_MAXINT,
 	                      NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY,
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -870,7 +872,7 @@ nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *klass)
 	 *
 	 * Set to 2147483647 (MAXINT32) for infinity.
 	 *
-	 * Since: 1.24, 1.22.8
+	 * Since: 1.24
 	 **/
 	/* ---ifcfg-rh---
 	 * property: dhcp-timeout
diff --git a/libnm-core/nm-setting-ip6-config.h b/libnm-core/nm-setting-ip6-config.h
index 4173c3b0..e6eb49bb 100644
--- a/libnm-core/nm-setting-ip6-config.h
+++ b/libnm-core/nm-setting-ip6-config.h
@@ -161,7 +161,7 @@ NM_AVAILABLE_IN_1_4
 const char *nm_setting_ip6_config_get_token (NMSettingIP6Config *setting);
 NM_AVAILABLE_IN_1_12
 const char *nm_setting_ip6_config_get_dhcp_duid (NMSettingIP6Config *setting);
-NM_AVAILABLE_IN_1_22_8
+NM_AVAILABLE_IN_1_24
 gint32 nm_setting_ip6_config_get_ra_timeout (NMSettingIP6Config *setting);
 
 G_END_DECLS
diff --git a/libnm-core/nm-setting-macsec.c b/libnm-core/nm-setting-macsec.c
index 62351eb5..87f4492d 100644
--- a/libnm-core/nm-setting-macsec.c
+++ b/libnm-core/nm-setting-macsec.c
@@ -42,14 +42,14 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 
 typedef struct {
 	char *parent;
-	NMSettingMacsecMode mode;
-	bool encrypt:1;
-	bool send_sci:1;
 	char *mka_cak;
-	NMSettingSecretFlags mka_cak_flags;
 	char *mka_ckn;
 	int port;
+	NMSettingMacsecMode mode;
+	NMSettingSecretFlags mka_cak_flags;
 	NMSettingMacsecValidation validation;
+	bool encrypt:1;
+	bool send_sci:1;
 } NMSettingMacsecPrivate;
 
 G_DEFINE_TYPE (NMSettingMacsec, nm_setting_macsec, NM_TYPE_SETTING)
@@ -458,8 +458,15 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_setting_macsec_init (NMSettingMacsec *setting)
+nm_setting_macsec_init (NMSettingMacsec *self)
 {
+	NMSettingMacsecPrivate *priv = NM_SETTING_MACSEC_GET_PRIVATE (self);
+
+	nm_assert (priv->mode == NM_SETTING_MACSEC_MODE_PSK);
+	priv->encrypt    = TRUE;
+	priv->port       = 1;
+	priv->send_sci   = TRUE;
+	priv->validation = NM_SETTING_MACSEC_VALIDATION_STRICT;
 }
 
 /**
@@ -519,7 +526,6 @@ nm_setting_macsec_class_init (NMSettingMacsecClass *klass)
 	    g_param_spec_string (NM_SETTING_MACSEC_PARENT, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
@@ -535,7 +541,6 @@ nm_setting_macsec_class_init (NMSettingMacsecClass *klass)
 	    g_param_spec_int (NM_SETTING_MACSEC_MODE, "", "",
 	                      G_MININT, G_MAXINT, NM_SETTING_MACSEC_MODE_PSK,
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      NM_SETTING_PARAM_INFERRABLE |
 	                      G_PARAM_STATIC_STRINGS);
 
@@ -550,7 +555,6 @@ nm_setting_macsec_class_init (NMSettingMacsecClass *klass)
 	    g_param_spec_boolean (NM_SETTING_MACSEC_ENCRYPT, "", "",
 	                          TRUE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -565,7 +569,6 @@ nm_setting_macsec_class_init (NMSettingMacsecClass *klass)
 	    g_param_spec_string (NM_SETTING_MACSEC_MKA_CAK, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_SECRET |
 	                         G_PARAM_STATIC_STRINGS);
 
@@ -596,7 +599,6 @@ nm_setting_macsec_class_init (NMSettingMacsecClass *klass)
 	    g_param_spec_string (NM_SETTING_MACSEC_MKA_CKN, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -610,7 +612,6 @@ nm_setting_macsec_class_init (NMSettingMacsecClass *klass)
 	    g_param_spec_int (NM_SETTING_MACSEC_PORT, "", "",
 	                      1, 65534, 1,
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      NM_SETTING_PARAM_INFERRABLE |
 	                      G_PARAM_STATIC_STRINGS);
 
@@ -625,7 +626,6 @@ nm_setting_macsec_class_init (NMSettingMacsecClass *klass)
 	    g_param_spec_int (NM_SETTING_MACSEC_VALIDATION, "", "",
 	                      G_MININT, G_MAXINT, NM_SETTING_MACSEC_VALIDATION_STRICT,
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      NM_SETTING_PARAM_INFERRABLE |
 	                      G_PARAM_STATIC_STRINGS);
 
@@ -641,7 +641,6 @@ nm_setting_macsec_class_init (NMSettingMacsecClass *klass)
 	    g_param_spec_boolean (NM_SETTING_MACSEC_SEND_SCI, "", "",
 	                          TRUE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
diff --git a/libnm-core/nm-setting-macvlan.c b/libnm-core/nm-setting-macvlan.c
index d62184c2..cbaadd3f 100644
--- a/libnm-core/nm-setting-macvlan.c
+++ b/libnm-core/nm-setting-macvlan.c
@@ -35,8 +35,8 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 typedef struct {
 	char *parent;
 	NMSettingMacvlanMode mode;
-	gboolean promiscuous;
-	gboolean tap;
+	bool promiscuous:1;
+	bool tap:1;
 } NMSettingMacvlanPrivate;
 
 G_DEFINE_TYPE (NMSettingMacvlan, nm_setting_macvlan, NM_TYPE_SETTING)
@@ -217,8 +217,11 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_setting_macvlan_init (NMSettingMacvlan *setting)
+nm_setting_macvlan_init (NMSettingMacvlan *self)
 {
+	NMSettingMacvlanPrivate *priv = NM_SETTING_MACVLAN_GET_PRIVATE (self);
+
+	priv->promiscuous = TRUE;
 }
 
 /**
@@ -275,7 +278,6 @@ nm_setting_macvlan_class_init (NMSettingMacvlanClass *klass)
 	    g_param_spec_string (NM_SETTING_MACVLAN_PARENT, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
@@ -291,7 +293,6 @@ nm_setting_macvlan_class_init (NMSettingMacvlanClass *klass)
 	    g_param_spec_uint (NM_SETTING_MACVLAN_MODE, "", "",
 	                       0, G_MAXUINT, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -306,7 +307,6 @@ nm_setting_macvlan_class_init (NMSettingMacvlanClass *klass)
 	    g_param_spec_boolean (NM_SETTING_MACVLAN_PROMISCUOUS, "", "",
 	                          TRUE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_INFERRABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
@@ -321,7 +321,6 @@ nm_setting_macvlan_class_init (NMSettingMacvlanClass *klass)
 	    g_param_spec_boolean (NM_SETTING_MACVLAN_TAP, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_INFERRABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-match.c b/libnm-core/nm-setting-match.c
index 2fbd0716..2e058810 100644
--- a/libnm-core/nm-setting-match.c
+++ b/libnm-core/nm-setting-match.c
@@ -25,7 +25,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingMatch,
 /**
  * NMSettingMatch:
  *
- * Match settings.
+ * Match settings
  *
  * Since: 1.14
  */
diff --git a/libnm-core/nm-setting-olpc-mesh.c b/libnm-core/nm-setting-olpc-mesh.c
index 0e1d27cf..7c7f1753 100644
--- a/libnm-core/nm-setting-olpc-mesh.c
+++ b/libnm-core/nm-setting-olpc-mesh.c
@@ -31,8 +31,8 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 
 typedef struct {
 	GBytes *ssid;
-	guint32 channel;
 	char *dhcp_anycast_addr;
+	guint32 channel;
 } NMSettingOlpcMeshPrivate;
 
 G_DEFINE_TYPE (NMSettingOlpcMesh, nm_setting_olpc_mesh, NM_TYPE_SETTING)
@@ -234,7 +234,6 @@ nm_setting_olpc_mesh_class_init (NMSettingOlpcMeshClass *klass)
 	    g_param_spec_uint (NM_SETTING_OLPC_MESH_CHANNEL, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -250,7 +249,7 @@ nm_setting_olpc_mesh_class_init (NMSettingOlpcMeshClass *klass)
 	                         NULL,
 	                         G_PARAM_READWRITE |
 	                         G_PARAM_STATIC_STRINGS);
-	_nm_properties_override_gobj (properties_override, obj_properties[PROP_DHCP_ANYCAST_ADDRESS], &nm_sett_info_propert_type_mac_addrees);
+	_nm_properties_override_gobj (properties_override, obj_properties[PROP_DHCP_ANYCAST_ADDRESS], &nm_sett_info_propert_type_mac_address);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
diff --git a/libnm-core/nm-setting-ovs-bridge.c b/libnm-core/nm-setting-ovs-bridge.c
index 7f9ca1dc..2f907885 100644
--- a/libnm-core/nm-setting-ovs-bridge.c
+++ b/libnm-core/nm-setting-ovs-bridge.c
@@ -39,9 +39,9 @@ struct _NMSettingOvsBridge {
 
 	char *fail_mode;
 	char *datapath_type;
-	gboolean mcast_snooping_enable;
-	gboolean rstp_enable;
-	gboolean stp_enable;
+	bool mcast_snooping_enable:1;
+	bool rstp_enable:1;
+	bool stp_enable:1;
 };
 
 struct _NMSettingOvsBridgeClass {
@@ -305,7 +305,6 @@ nm_setting_ovs_bridge_class_init (NMSettingOvsBridgeClass *klass)
 	    g_param_spec_string (NM_SETTING_OVS_BRIDGE_FAIL_MODE, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
@@ -320,7 +319,6 @@ nm_setting_ovs_bridge_class_init (NMSettingOvsBridgeClass *klass)
 	    g_param_spec_boolean (NM_SETTING_OVS_BRIDGE_MCAST_SNOOPING_ENABLE, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -334,7 +332,6 @@ nm_setting_ovs_bridge_class_init (NMSettingOvsBridgeClass *klass)
 	    g_param_spec_boolean (NM_SETTING_OVS_BRIDGE_RSTP_ENABLE, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -348,7 +345,6 @@ nm_setting_ovs_bridge_class_init (NMSettingOvsBridgeClass *klass)
 	    g_param_spec_boolean (NM_SETTING_OVS_BRIDGE_STP_ENABLE, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
diff --git a/libnm-core/nm-setting-ovs-dpdk.c b/libnm-core/nm-setting-ovs-dpdk.c
index ed3c3255..03180535 100644
--- a/libnm-core/nm-setting-ovs-dpdk.c
+++ b/libnm-core/nm-setting-ovs-dpdk.c
@@ -148,7 +148,6 @@ nm_setting_ovs_dpdk_class_init (NMSettingOvsDpdkClass *klass)
 	    g_param_spec_string (NM_SETTING_OVS_DPDK_DEVARGS, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-ovs-interface.c b/libnm-core/nm-setting-ovs-interface.c
index 849b9a4e..b9a133d0 100644
--- a/libnm-core/nm-setting-ovs-interface.c
+++ b/libnm-core/nm-setting-ovs-interface.c
@@ -64,30 +64,29 @@ nm_setting_ovs_interface_get_interface_type (NMSettingOvsInterface *self)
 
 int
 _nm_setting_ovs_interface_verify_interface_type (NMSettingOvsInterface *self,
+                                                 const char *type,
                                                  NMConnection *connection,
                                                  gboolean normalize,
                                                  gboolean *out_modified,
-                                                 const char **normalized_type,
+                                                 const char **out_normalized_type,
                                                  GError **error)
 {
-	const char *type;
 	const char *type_from_setting = NULL;
 	const char *type_setting = NULL;
 	const char *connection_type;
 	gboolean is_ovs_connection_type;
 
-	g_return_val_if_fail (NM_IS_SETTING_OVS_INTERFACE (self), FALSE);
 	if (normalize) {
+		g_return_val_if_fail (NM_IS_SETTING_OVS_INTERFACE (self), FALSE);
 		g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
 		nm_assert (self == nm_connection_get_setting_ovs_interface (connection));
-	} else
+	} else {
+		g_return_val_if_fail (!self || NM_IS_SETTING_OVS_INTERFACE (self), FALSE);
 		g_return_val_if_fail (!connection || NM_IS_CONNECTION (connection), FALSE);
-
-	g_return_val_if_fail (!normalized_type || !(*normalized_type), FALSE);
+	}
 
 	NM_SET_OUT (out_modified, FALSE);
-
-	type = self->type;
+	NM_SET_OUT (out_normalized_type, NULL);
 
 	if (   type
 	    && !NM_IN_STRSET (type, "internal", "system", "patch", "dpdk")) {
@@ -100,8 +99,10 @@ _nm_setting_ovs_interface_verify_interface_type (NMSettingOvsInterface *self,
 		return FALSE;
 	}
 
-	if (!connection)
+	if (!connection) {
+		NM_SET_OUT (out_normalized_type, type);
 		return TRUE;
+	}
 
 	connection_type = nm_connection_get_connection_type (connection);
 	if (!connection_type) {
@@ -192,6 +193,7 @@ _nm_setting_ovs_interface_verify_interface_type (NMSettingOvsInterface *self,
 				g_prefix_error (error, "%s.%s: ", NM_SETTING_OVS_INTERFACE_SETTING_NAME, NM_SETTING_OVS_INTERFACE_TYPE);
 				return FALSE;
 			}
+			NM_SET_OUT (out_normalized_type, type);
 			return TRUE;
 		}
 		type = type_from_setting;
@@ -208,16 +210,17 @@ _nm_setting_ovs_interface_verify_interface_type (NMSettingOvsInterface *self,
 		}
 	}
 
-	if (type)
+	if (type) {
+		NM_SET_OUT (out_normalized_type, type);
 		return TRUE;
+	}
 
 	if (is_ovs_connection_type)
 		type = "internal";
 	else
 		type = "system";
 
-	if (normalized_type)
-		*normalized_type = type;
+	NM_SET_OUT (out_normalized_type, type);
 
 normalize:
 	if (!normalize) {
@@ -254,8 +257,6 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
 	NMSettingOvsInterface *self = NM_SETTING_OVS_INTERFACE (setting);
 	NMSettingConnection *s_con = NULL;
-	const char *normalized_type = NULL;
-	int result = NM_SETTING_VERIFY_ERROR;
 
 	if (connection) {
 		const char *slave_type;
@@ -295,39 +296,13 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		}
 	}
 
-	result = _nm_setting_ovs_interface_verify_interface_type (self,
-	                                                          connection,
-	                                                          FALSE,
-	                                                          NULL,
-	                                                          &normalized_type,
-	                                                          error);
-
-	/* From 'man ovs-vswitchd.conf.db': OVS patch interfaces do not have
-	 * a limit on interface name length, all the other types do */
-	if (result != NM_SETTING_VERIFY_ERROR && s_con) {
-		gs_free_error GError *ifname_error = NULL;
-		const char *ifname = nm_setting_connection_get_interface_name (s_con);
-
-		normalized_type = self->type ? self->type : normalized_type;
-
-		if (   ifname
-		    && !nm_streq0 (normalized_type, "patch")
-		    && !nm_utils_ifname_valid (ifname,
-		                               NMU_IFACE_KERNEL,
-		                               &ifname_error)) {
-			g_clear_error (error);
-			g_set_error (error,
-			             NM_CONNECTION_ERROR,
-			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			             "'%s': %s", ifname, ifname_error->message);
-			g_prefix_error (error, "%s.%s: ",
-			                NM_SETTING_CONNECTION_SETTING_NAME,
-			                NM_SETTING_CONNECTION_INTERFACE_NAME);
-			return NM_SETTING_VERIFY_ERROR;
-		}
-	}
-
-	return result;
+	return _nm_setting_ovs_interface_verify_interface_type (self,
+	                                                        self->type,
+	                                                        connection,
+	                                                        FALSE,
+	                                                        NULL,
+	                                                        NULL,
+	                                                        error);
 }
 
 /*****************************************************************************/
@@ -420,7 +395,6 @@ nm_setting_ovs_interface_class_init (NMSettingOvsInterfaceClass *klass)
 	    g_param_spec_string (NM_SETTING_OVS_INTERFACE_TYPE, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-ovs-patch.c b/libnm-core/nm-setting-ovs-patch.c
index e74a35ca..6a9c9f56 100644
--- a/libnm-core/nm-setting-ovs-patch.c
+++ b/libnm-core/nm-setting-ovs-patch.c
@@ -81,8 +81,8 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (   !nm_utils_ipaddr_valid (AF_INET, self->peer)
-	    && !nm_utils_ipaddr_valid (AF_INET6, self->peer)) {
+	if (   !nm_utils_ipaddr_is_valid (AF_INET, self->peer)
+	    && !nm_utils_ipaddr_is_valid (AF_INET6, self->peer)) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -188,7 +188,6 @@ nm_setting_ovs_patch_class_init (NMSettingOvsPatchClass *klass)
 	    g_param_spec_string (NM_SETTING_OVS_PATCH_PEER, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-ovs-port.c b/libnm-core/nm-setting-ovs-port.c
index ab4b63d8..a2073ed3 100644
--- a/libnm-core/nm-setting-ovs-port.c
+++ b/libnm-core/nm-setting-ovs-port.c
@@ -39,9 +39,9 @@ struct _NMSettingOvsPort {
 	NMSetting parent;
 
 	char *vlan_mode;
-	guint tag;
 	char *lacp;
 	char *bond_mode;
+	guint tag;
 	guint bond_updelay;
 	guint bond_downdelay;
 };
@@ -367,7 +367,6 @@ nm_setting_ovs_port_class_init (NMSettingOvsPortClass *klass)
 	    g_param_spec_string (NM_SETTING_OVS_PORT_VLAN_MODE, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
@@ -382,7 +381,6 @@ nm_setting_ovs_port_class_init (NMSettingOvsPortClass *klass)
 	    g_param_spec_uint (NM_SETTING_OVS_PORT_TAG, "", "",
 	                       0, 4095, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -397,7 +395,6 @@ nm_setting_ovs_port_class_init (NMSettingOvsPortClass *klass)
 	    g_param_spec_string (NM_SETTING_OVS_PORT_LACP, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
@@ -412,7 +409,6 @@ nm_setting_ovs_port_class_init (NMSettingOvsPortClass *klass)
 	    g_param_spec_string (NM_SETTING_OVS_PORT_BOND_MODE, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
@@ -427,7 +423,6 @@ nm_setting_ovs_port_class_init (NMSettingOvsPortClass *klass)
 	    g_param_spec_uint (NM_SETTING_OVS_PORT_BOND_UPDELAY, "", "",
 	                       0, G_MAXUINT, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -442,7 +437,6 @@ nm_setting_ovs_port_class_init (NMSettingOvsPortClass *klass)
 	    g_param_spec_uint (NM_SETTING_OVS_PORT_BOND_DOWNDELAY, "", "",
 	                       0, G_MAXUINT, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-ppp.c b/libnm-core/nm-setting-ppp.c
index c42feb46..090f3c08 100644
--- a/libnm-core/nm-setting-ppp.c
+++ b/libnm-core/nm-setting-ppp.c
@@ -44,24 +44,24 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 );
 
 typedef struct {
-	gboolean noauth;
-	gboolean refuse_eap;
-	gboolean refuse_pap;
-	gboolean refuse_chap;
-	gboolean refuse_mschap;
-	gboolean refuse_mschapv2;
-	gboolean nobsdcomp;
-	gboolean nodeflate;
-	gboolean no_vj_comp;
-	gboolean require_mppe;
-	gboolean require_mppe_128;
-	gboolean mppe_stateful;
-	gboolean crtscts;
 	guint32 baud;
 	guint32 mru;
 	guint32 mtu;
 	guint32 lcp_echo_failure;
 	guint32 lcp_echo_interval;
+	bool noauth:1;
+	bool refuse_eap:1;
+	bool refuse_pap:1;
+	bool refuse_chap:1;
+	bool refuse_mschap:1;
+	bool refuse_mschapv2:1;
+	bool nobsdcomp:1;
+	bool nodeflate:1;
+	bool no_vj_comp:1;
+	bool require_mppe:1;
+	bool require_mppe_128:1;
+	bool mppe_stateful:1;
+	bool crtscts:1;
 } NMSettingPppPrivate;
 
 G_DEFINE_TYPE (NMSettingPpp, nm_setting_ppp, NM_TYPE_SETTING)
@@ -495,8 +495,11 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_setting_ppp_init (NMSettingPpp *setting)
+nm_setting_ppp_init (NMSettingPpp *self)
 {
+	NMSettingPppPrivate *priv = NM_SETTING_PPP_GET_PRIVATE (self);
+
+	priv->noauth = TRUE;
 }
 
 /**
@@ -536,7 +539,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_boolean (NM_SETTING_PPP_NOAUTH, "", "",
 	                          TRUE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -548,7 +550,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_boolean (NM_SETTING_PPP_REFUSE_EAP, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -560,7 +561,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_boolean (NM_SETTING_PPP_REFUSE_PAP, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -572,7 +572,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_boolean (NM_SETTING_PPP_REFUSE_CHAP, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -584,7 +583,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_boolean (NM_SETTING_PPP_REFUSE_MSCHAP, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -596,7 +594,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_boolean (NM_SETTING_PPP_REFUSE_MSCHAPV2, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -608,7 +605,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_boolean (NM_SETTING_PPP_NOBSDCOMP, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_FUZZY_IGNORE |
 	                          G_PARAM_STATIC_STRINGS);
 
@@ -621,7 +617,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_boolean (NM_SETTING_PPP_NODEFLATE, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_FUZZY_IGNORE |
 	                          G_PARAM_STATIC_STRINGS);
 
@@ -634,7 +629,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_boolean (NM_SETTING_PPP_NO_VJ_COMP, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_FUZZY_IGNORE |
 	                          G_PARAM_STATIC_STRINGS);
 
@@ -650,7 +644,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_boolean (NM_SETTING_PPP_REQUIRE_MPPE, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -663,7 +656,7 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	obj_properties[PROP_REQUIRE_MPPE_128] =
 	    g_param_spec_boolean (NM_SETTING_PPP_REQUIRE_MPPE_128, "", "",
 	                          FALSE,
-	                          G_PARAM_READWRITE  | G_PARAM_CONSTRUCT |
+	                          G_PARAM_READWRITE |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -676,7 +669,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_boolean (NM_SETTING_PPP_MPPE_STATEFUL, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -690,7 +682,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_boolean (NM_SETTING_PPP_CRTSCTS, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -704,7 +695,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_uint (NM_SETTING_PPP_BAUD, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -719,7 +709,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_uint (NM_SETTING_PPP_MRU, "", "",
 	                       0, 16384, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -732,7 +721,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_uint (NM_SETTING_PPP_MTU, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -748,7 +736,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_uint (NM_SETTING_PPP_LCP_ECHO_FAILURE, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -764,7 +751,6 @@ nm_setting_ppp_class_init (NMSettingPppClass *klass)
 	    g_param_spec_uint (NM_SETTING_PPP_LCP_ECHO_INTERVAL, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-pppoe.c b/libnm-core/nm-setting-pppoe.c
index 0fee96a1..d61364c9 100644
--- a/libnm-core/nm-setting-pppoe.c
+++ b/libnm-core/nm-setting-pppoe.c
@@ -302,7 +302,6 @@ nm_setting_pppoe_class_init (NMSettingPppoeClass *klass)
 	    g_param_spec_string (NM_SETTING_PPPOE_PARENT, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-proxy.c b/libnm-core/nm-setting-proxy.c
index 9e83339c..a81b2d03 100644
--- a/libnm-core/nm-setting-proxy.c
+++ b/libnm-core/nm-setting-proxy.c
@@ -34,10 +34,10 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 );
 
 typedef struct {
-	NMSettingProxyMethod method;
-	gboolean browser_only;
 	char *pac_url;
 	char *pac_script;
+	int method;
+	bool browser_only:1;
 } NMSettingProxyPrivate;
 
 G_DEFINE_TYPE (NMSettingProxy, nm_setting_proxy, NM_TYPE_SETTING)
@@ -119,11 +119,8 @@ static gboolean
 verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
 	NMSettingProxyPrivate *priv = NM_SETTING_PROXY_GET_PRIVATE (setting);
-	NMSettingProxyMethod method;
 
-	method = priv->method;
-
-	if (!NM_IN_SET (method,
+	if (!NM_IN_SET (priv->method,
 	                NM_SETTING_PROXY_METHOD_NONE,
 	                NM_SETTING_PROXY_METHOD_AUTO)) {
 		g_set_error (error,
@@ -134,7 +131,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (method != NM_SETTING_PROXY_METHOD_AUTO) {
+	if (priv->method != NM_SETTING_PROXY_METHOD_AUTO) {
 		if (priv->pac_url) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
@@ -241,8 +238,9 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_setting_proxy_init (NMSettingProxy *setting)
+nm_setting_proxy_init (NMSettingProxy *self)
 {
+	nm_assert (NM_SETTING_PROXY_GET_PRIVATE (self)->method == NM_SETTING_PROXY_METHOD_NONE);
 }
 
 /**
@@ -306,7 +304,6 @@ nm_setting_proxy_class_init (NMSettingProxyClass *klass)
 	    g_param_spec_int (NM_SETTING_PROXY_METHOD, "", "",
 	                      G_MININT32, G_MAXINT32, NM_SETTING_PROXY_METHOD_NONE,
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      G_PARAM_STATIC_STRINGS);
 
 	/**
diff --git a/libnm-core/nm-setting-serial.c b/libnm-core/nm-setting-serial.c
index c050e238..fe7c05ee 100644
--- a/libnm-core/nm-setting-serial.c
+++ b/libnm-core/nm-setting-serial.c
@@ -31,11 +31,11 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 );
 
 typedef struct {
+	guint64 send_delay;
 	guint baud;
 	guint bits;
-	char parity;
 	guint stopbits;
-	guint64 send_delay;
+	char parity;
 } NMSettingSerialPrivate;
 
 G_DEFINE_TYPE (NMSettingSerial, nm_setting_serial, NM_TYPE_SETTING)
@@ -206,8 +206,14 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_setting_serial_init (NMSettingSerial *setting)
+nm_setting_serial_init (NMSettingSerial *self)
 {
+	NMSettingSerialPrivate *priv = NM_SETTING_SERIAL_GET_PRIVATE (self);
+
+	nm_assert (priv->parity == NM_SETTING_SERIAL_PARITY_NONE);
+	priv->stopbits = 1;
+	priv->baud     = 57600;
+	priv->bits     = 8;
 }
 
 /**
@@ -246,7 +252,6 @@ nm_setting_serial_class_init (NMSettingSerialClass *klass)
 	    g_param_spec_uint (NM_SETTING_SERIAL_BAUD, "", "",
 	                       0, G_MAXUINT, 57600,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -258,7 +263,6 @@ nm_setting_serial_class_init (NMSettingSerialClass *klass)
 	    g_param_spec_uint (NM_SETTING_SERIAL_BITS, "", "",
 	                       5, 8, 8,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -286,7 +290,6 @@ nm_setting_serial_class_init (NMSettingSerialClass *klass)
 	                       NM_TYPE_SETTING_SERIAL_PARITY,
 	                       NM_SETTING_SERIAL_PARITY_NONE,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 	_nm_properties_override_gobj (properties_override,
 	                              obj_properties[PROP_PARITY],
@@ -306,7 +309,6 @@ nm_setting_serial_class_init (NMSettingSerialClass *klass)
 	    g_param_spec_uint (NM_SETTING_SERIAL_STOPBITS, "", "",
 	                       1, 2, 1,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -318,7 +320,6 @@ nm_setting_serial_class_init (NMSettingSerialClass *klass)
 	    g_param_spec_uint64 (NM_SETTING_SERIAL_SEND_DELAY, "", "",
 	                         0, G_MAXUINT64, 0,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
diff --git a/libnm-core/nm-setting-sriov.c b/libnm-core/nm-setting-sriov.c
index 9ef68cce..a679a0be 100644
--- a/libnm-core/nm-setting-sriov.c
+++ b/libnm-core/nm-setting-sriov.c
@@ -27,7 +27,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingSriov,
 /**
  * NMSettingSriov:
  *
- * SR-IOV settings.
+ * SR-IOV settings
  *
  * Since: 1.14
  */
@@ -234,7 +234,7 @@ vf_add_vlan (NMSriovVF *vf,
 		vf->vlans = _vf_vlan_create_hash ();
 
 	g_hash_table_add (vf->vlans, vlan);
-	g_clear_pointer (&vf->vlan_ids, g_free);
+	nm_clear_g_free (&vf->vlan_ids);
 }
 
 /**
@@ -530,7 +530,7 @@ nm_sriov_vf_remove_vlan (NMSriovVF *vf, guint vlan_id)
 	    || !g_hash_table_remove (vf->vlans, &vlan_id))
 		return FALSE;
 
-	g_clear_pointer (&vf->vlan_ids, g_free);
+	nm_clear_g_free (&vf->vlan_ids);
 	return TRUE;
 }
 
@@ -1217,6 +1217,8 @@ static void
 nm_setting_sriov_init (NMSettingSriov *setting)
 {
 	setting->vfs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_sriov_vf_unref);
+
+	setting->autoprobe_drivers = NM_TERNARY_DEFAULT;
 }
 
 /**
@@ -1265,7 +1267,8 @@ nm_setting_sriov_class_init (NMSettingSriovClass *klass)
 	 *
 	 * Note that when the sriov setting is present NetworkManager
 	 * enforces the number of virtual functions on the interface
-	 * also when it is zero. To prevent any changes to SR-IOV
+	 * (also when it is zero) during activation and resets it
+	 * upon deactivation. To prevent any changes to SR-IOV
 	 * parameters don't add a sriov setting to the connection.
 	 *
 	 * Since: 1.14
@@ -1282,7 +1285,6 @@ nm_setting_sriov_class_init (NMSettingSriovClass *klass)
 	                       0, G_MAXUINT32, 0,
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -1370,7 +1372,6 @@ nm_setting_sriov_class_init (NMSettingSriovClass *klass)
 	                       NM_TERNARY_DEFAULT,
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
diff --git a/libnm-core/nm-setting-tc-config.c b/libnm-core/nm-setting-tc-config.c
index af503741..86582315 100644
--- a/libnm-core/nm-setting-tc-config.c
+++ b/libnm-core/nm-setting-tc-config.c
@@ -967,7 +967,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingTCConfig,
 /**
  * NMSettingTCConfig:
  *
- * Linux Traffic Control Settings.
+ * Linux Traffic Control Settings
  *
  * Since: 1.12
  */
diff --git a/libnm-core/nm-setting-tun.c b/libnm-core/nm-setting-tun.c
index 312c5c53..ba9e64ad 100644
--- a/libnm-core/nm-setting-tun.c
+++ b/libnm-core/nm-setting-tun.c
@@ -34,12 +34,12 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 );
 
 typedef struct {
-	NMSettingTunMode mode;
 	char *owner;
 	char *group;
-	gboolean pi;
-	gboolean vnet_hdr;
-	gboolean multi_queue;
+	NMSettingTunMode mode;
+	bool pi:1;
+	bool vnet_hdr:1;
+	bool multi_queue:1;
 } NMSettingTunPrivate;
 
 G_DEFINE_TYPE (NMSettingTun, nm_setting_tun, NM_TYPE_SETTING)
@@ -248,8 +248,11 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_setting_tun_init (NMSettingTun *setting)
+nm_setting_tun_init (NMSettingTun *self)
 {
+	NMSettingTunPrivate *priv = NM_SETTING_TUN_GET_PRIVATE (self);
+
+	priv->mode = NM_SETTING_TUN_MODE_TUN;
 }
 
 /**
@@ -307,7 +310,6 @@ nm_setting_tun_class_init (NMSettingTunClass *klass)
 	    g_param_spec_uint (NM_SETTING_TUN_MODE, "", "",
 	                       0, G_MAXUINT, NM_SETTING_TUN_MODE_TUN,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-user.c b/libnm-core/nm-setting-user.c
index 87a0dfed..1c60d55b 100644
--- a/libnm-core/nm-setting-user.c
+++ b/libnm-core/nm-setting-user.c
@@ -327,7 +327,7 @@ out:
 		/* setting a value purges all invalid values that were set
 		 * via GObject property. */
 		changed = TRUE;
-		g_clear_pointer (&priv->data_invalid, g_hash_table_unref);
+		nm_clear_pointer (&priv->data_invalid, g_hash_table_unref);
 	}
 	if (changed)
 		_notify (self, PROP_DATA);
@@ -462,8 +462,8 @@ set_property (GObject *object, guint prop_id,
 
 		data = g_value_get_boxed (value);
 		if (!data || !g_hash_table_size (data)) {
-			g_clear_pointer (&priv->data, g_hash_table_unref);
-			g_clear_pointer (&priv->data_invalid, g_hash_table_unref);
+			nm_clear_pointer (&priv->data, g_hash_table_unref);
+			nm_clear_pointer (&priv->data_invalid, g_hash_table_unref);
 			return;
 		}
 
@@ -488,7 +488,7 @@ set_property (GObject *object, guint prop_id,
 		}
 		if (   priv->data_invalid
 		    && !g_hash_table_size (priv->data_invalid))
-			g_clear_pointer (&priv->data_invalid, g_hash_table_unref);
+			nm_clear_pointer (&priv->data_invalid, g_hash_table_unref);
 
 		break;
 	default:
diff --git a/libnm-core/nm-setting-vlan.c b/libnm-core/nm-setting-vlan.c
index 30f43760..b4bbdd64 100644
--- a/libnm-core/nm-setting-vlan.c
+++ b/libnm-core/nm-setting-vlan.c
@@ -36,11 +36,11 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingVlan,
 );
 
 typedef struct {
+	GSList *ingress_priority_map;
+	GSList *egress_priority_map;
 	char *parent;
 	guint32 id;
 	guint32 flags;
-	GSList *ingress_priority_map;
-	GSList *egress_priority_map;
 } NMSettingVlanPrivate;
 
 G_DEFINE_TYPE (NMSettingVlan, nm_setting_vlan, NM_TYPE_SETTING)
@@ -791,8 +791,11 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_setting_vlan_init (NMSettingVlan *setting)
+nm_setting_vlan_init (NMSettingVlan *self)
 {
+	NMSettingVlanPrivate *priv = NM_SETTING_VLAN_GET_PRIVATE (self);
+
+	priv->flags = NM_VLAN_FLAG_REORDER_HEADERS;
 }
 
 /**
@@ -854,7 +857,6 @@ nm_setting_vlan_class_init (NMSettingVlanClass *klass)
 	    g_param_spec_string (NM_SETTING_VLAN_PARENT, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
@@ -874,7 +876,6 @@ nm_setting_vlan_class_init (NMSettingVlanClass *klass)
 	    g_param_spec_uint (NM_SETTING_VLAN_ID, "", "",
 	                       0, 4095, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -905,7 +906,6 @@ nm_setting_vlan_class_init (NMSettingVlanClass *klass)
 	                        NM_TYPE_VLAN_FLAGS,
 	                        NM_VLAN_FLAG_REORDER_HEADERS,
 	                        G_PARAM_READWRITE |
-	                        G_PARAM_CONSTRUCT |
 	                        NM_SETTING_PARAM_INFERRABLE |
 	                        G_PARAM_STATIC_STRINGS);
 	_nm_properties_override_gobj (properties_override,
diff --git a/libnm-core/nm-setting-vpn.c b/libnm-core/nm-setting-vpn.c
index 05fca918..baca3f32 100644
--- a/libnm-core/nm-setting-vpn.c
+++ b/libnm-core/nm-setting-vpn.c
@@ -81,6 +81,23 @@ G_DEFINE_TYPE (NMSettingVpn, nm_setting_vpn, NM_TYPE_SETTING)
 
 /*****************************************************************************/
 
+static GHashTable *
+_ensure_strdict (GHashTable **p_hash, gboolean for_secrets)
+{
+	if (!*p_hash) {
+		*p_hash = g_hash_table_new_full (nm_str_hash,
+		                                 g_str_equal,
+		                                 g_free,
+		                                   for_secrets
+		                                 ? (GDestroyNotify) nm_free_secret
+		                                 : g_free);
+	}
+	return *p_hash;
+}
+
+
+/*****************************************************************************/
+
 /**
  * nm_setting_vpn_get_service_type:
  * @setting: the #NMSettingVpn
@@ -139,30 +156,39 @@ nm_setting_vpn_get_num_data_items (NMSettingVpn *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_VPN (setting), 0);
 
-	return g_hash_table_size (NM_SETTING_VPN_GET_PRIVATE (setting)->data);
+	return nm_g_hash_table_size (NM_SETTING_VPN_GET_PRIVATE (setting)->data);
 }
 
 /**
  * nm_setting_vpn_add_data_item:
  * @setting: the #NMSettingVpn
  * @key: a name that uniquely identifies the given value @item
- * @item: the value to be referenced by @key
+ * @item: (allow-none): the value to be referenced by @key
  *
  * Establishes a relationship between @key and @item internally in the
  * setting which may be retrieved later.  Should not be used to store passwords
  * or other secrets, which is what nm_setting_vpn_add_secret() is for.
+ *
+ * Before 1.24, @item must not be %NULL and not an empty string. Since 1.24,
+ * @item can be set to an empty string. It can also be set to %NULL to unset
+ * the key. In that case, the behavior is as if calling nm_setting_vpn_remove_data_item().
  **/
 void
 nm_setting_vpn_add_data_item (NMSettingVpn *setting,
                               const char *key,
                               const char *item)
 {
+	if (!item) {
+		nm_setting_vpn_remove_data_item (setting, key);
+		return;
+	}
+
 	g_return_if_fail (NM_IS_SETTING_VPN (setting));
 	g_return_if_fail (key && key[0]);
-	g_return_if_fail (item && item[0]);
 
-	g_hash_table_insert (NM_SETTING_VPN_GET_PRIVATE (setting)->data,
-	                     g_strdup (key), g_strdup (item));
+	g_hash_table_insert (_ensure_strdict (&NM_SETTING_VPN_GET_PRIVATE (setting)->data, FALSE),
+	                     g_strdup (key),
+	                     g_strdup (item));
 	_notify (setting, PROP_DATA);
 }
 
@@ -180,8 +206,9 @@ const char *
 nm_setting_vpn_get_data_item (NMSettingVpn *setting, const char *key)
 {
 	g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL);
+	g_return_val_if_fail (key && key[0], NULL);
 
-	return (const char *) g_hash_table_lookup (NM_SETTING_VPN_GET_PRIVATE (setting)->data, key);
+	return nm_g_hash_table_lookup (NM_SETTING_VPN_GET_PRIVATE (setting)->data, key);
 }
 
 /**
@@ -222,63 +249,58 @@ nm_setting_vpn_get_data_keys (NMSettingVpn *setting,
 gboolean
 nm_setting_vpn_remove_data_item (NMSettingVpn *setting, const char *key)
 {
-	gboolean found;
-
 	g_return_val_if_fail (NM_IS_SETTING_VPN (setting), FALSE);
-	g_return_val_if_fail (key, FALSE);
+	g_return_val_if_fail (key && key[0], FALSE);
 
-	found = g_hash_table_remove (NM_SETTING_VPN_GET_PRIVATE (setting)->data, key);
-	if (found)
+	if (nm_g_hash_table_remove (NM_SETTING_VPN_GET_PRIVATE (setting)->data, key)) {
 		_notify (setting, PROP_DATA);
-	return found;
+		return TRUE;
+	}
+	return FALSE;
 }
 
 static void
 foreach_item_helper (NMSettingVpn *self,
-                     gboolean is_secrets,
+                     GHashTable **p_hash,
                      NMVpnIterFunc func,
                      gpointer user_data)
 {
-	NMSettingVpnPrivate *priv;
-	guint len, i;
+	gs_unref_object NMSettingVpn *self_keep_alive = NULL;
 	gs_strfreev char **keys = NULL;
-	GHashTable *hash;
+	guint i, len;
 
 	nm_assert (NM_IS_SETTING_VPN (self));
 	nm_assert (func);
 
-	priv = NM_SETTING_VPN_GET_PRIVATE (self);
-
-	if (is_secrets) {
-		keys = (char **) nm_setting_vpn_get_secret_keys (self, &len);
-		hash = priv->secrets;
-	} else {
-		keys = (char **) nm_setting_vpn_get_data_keys (self, &len);
-		hash = priv->data;
-	}
-
-	if (!len) {
+	keys = nm_utils_strv_make_deep_copied (nm_utils_strdict_get_keys (*p_hash,
+	                                                                  TRUE,
+	                                                                  &len));
+	if (len == 0u) {
 		nm_assert (!keys);
 		return;
 	}
 
-	for (i = 0; i < len; i++) {
-		nm_assert (keys && keys[i]);
-		keys[i] = g_strdup (keys[i]);
-	}
-	nm_assert (!keys[i]);
+	if (len > 1u)
+		self_keep_alive = g_object_ref (self);
 
 	for (i = 0; i < len; i++) {
-		const char *value;
-
-		value = g_hash_table_lookup (hash, keys[i]);
 		/* NOTE: note that we call the function with a clone of @key,
 		 * not with the actual key from the dictionary.
 		 *
-		 * The @value on the other hand, is actually inside our dictionary,
-		 * it's not a clone. However, it might be %NULL, in case the key was
-		 * deleted while iterating. */
-		func (keys[i], value, user_data);
+		 * The @value on the other hand, is not cloned but retrieved before
+		 * invoking @func(). That means, if @func() modifies the setting while
+		 * being called, the values are as they currently are, but the
+		 * keys (and their order) were pre-determined before starting to
+		 * invoke the callbacks.
+		 *
+		 * The idea is to give some sensible, stable behavior in case the user
+		 * modifies the settings. Whether this particular behavior is optimal
+		 * is unclear. It's probably a bad idea to modify the settings while
+		 * iterating the values. But at least, it's a safe thing to do and we
+		 * do something sensible. */
+		func (keys[i],
+		      nm_g_hash_table_lookup (*p_hash, keys[i]),
+		      user_data);
 	}
 }
 
@@ -300,7 +322,7 @@ nm_setting_vpn_foreach_data_item (NMSettingVpn *setting,
 	g_return_if_fail (NM_IS_SETTING_VPN (setting));
 	g_return_if_fail (func);
 
-	foreach_item_helper (setting, FALSE, func, user_data);
+	foreach_item_helper (setting, &NM_SETTING_VPN_GET_PRIVATE (setting)->data, func, user_data);
 }
 
 /**
@@ -316,29 +338,38 @@ nm_setting_vpn_get_num_secrets (NMSettingVpn *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_VPN (setting), 0);
 
-	return g_hash_table_size (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets);
+	return nm_g_hash_table_size (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets);
 }
 
 /**
  * nm_setting_vpn_add_secret:
  * @setting: the #NMSettingVpn
  * @key: a name that uniquely identifies the given secret @secret
- * @secret: the secret to be referenced by @key
+ * @secret: (allow-none): the secret to be referenced by @key
  *
  * Establishes a relationship between @key and @secret internally in the
  * setting which may be retrieved later.
+ *
+ * Before 1.24, @secret must not be %NULL and not an empty string. Since 1.24,
+ * @secret can be set to an empty string. It can also be set to %NULL to unset
+ * the key. In that case, the behavior is as if calling nm_setting_vpn_remove_secret().
  **/
 void
 nm_setting_vpn_add_secret (NMSettingVpn *setting,
                            const char *key,
                            const char *secret)
 {
+	if (!secret) {
+		nm_setting_vpn_remove_secret (setting, key);
+		return;
+	}
+
 	g_return_if_fail (NM_IS_SETTING_VPN (setting));
 	g_return_if_fail (key && key[0]);
-	g_return_if_fail (secret && secret[0]);
 
-	g_hash_table_insert (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets,
-	                     g_strdup (key), g_strdup (secret));
+	g_hash_table_insert (_ensure_strdict (&NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, TRUE),
+	                     g_strdup (key),
+	                     g_strdup (secret));
 	_notify (setting, PROP_SECRETS);
 }
 
@@ -356,8 +387,9 @@ const char *
 nm_setting_vpn_get_secret (NMSettingVpn *setting, const char *key)
 {
 	g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL);
+	g_return_val_if_fail (key && key[0], NULL);
 
-	return (const char *) g_hash_table_lookup (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, key);
+	return nm_g_hash_table_lookup (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, key);
 }
 
 /**
@@ -398,15 +430,14 @@ nm_setting_vpn_get_secret_keys (NMSettingVpn *setting,
 gboolean
 nm_setting_vpn_remove_secret (NMSettingVpn *setting, const char *key)
 {
-	gboolean found;
-
 	g_return_val_if_fail (NM_IS_SETTING_VPN (setting), FALSE);
-	g_return_val_if_fail (key, FALSE);
+	g_return_val_if_fail (key && key[0], FALSE);
 
-	found = g_hash_table_remove (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, key);
-	if (found)
+	if (nm_g_hash_table_remove (NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, key)) {
 		_notify (setting, PROP_SECRETS);
-	return found;
+		return TRUE;
+	}
+	return FALSE;
 }
 
 /**
@@ -427,7 +458,7 @@ nm_setting_vpn_foreach_secret (NMSettingVpn *setting,
 	g_return_if_fail (NM_IS_SETTING_VPN (setting));
 	g_return_if_fail (func);
 
-	foreach_item_helper (setting, TRUE, func, user_data);
+	foreach_item_helper (setting, &NM_SETTING_VPN_GET_PRIVATE (setting)->secrets, func, user_data);
 }
 
 static gboolean
@@ -444,7 +475,7 @@ aggregate (NMSetting *setting,
 	switch (type) {
 
 	case NM_CONNECTION_AGGREGATE_ANY_SECRETS:
-		if (g_hash_table_size (priv->secrets) > 0) {
+		if (nm_g_hash_table_size (priv->secrets) > 0u) {
 			*((gboolean *) arg) = TRUE;
 			return TRUE;
 		}
@@ -452,32 +483,36 @@ aggregate (NMSetting *setting,
 
 	case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS:
 
-		g_hash_table_iter_init (&iter, priv->secrets);
-		while (g_hash_table_iter_next (&iter, (gpointer *) &key_name, NULL)) {
-			if (!nm_setting_get_secret_flags (NM_SETTING (setting), key_name, &secret_flags, NULL))
-				nm_assert_not_reached ();
-			if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) {
-				*((gboolean *) arg) = TRUE;
-				return TRUE;
+		if (priv->secrets) {
+			g_hash_table_iter_init (&iter, priv->secrets);
+			while (g_hash_table_iter_next (&iter, (gpointer *) &key_name, NULL)) {
+				if (!nm_setting_get_secret_flags (NM_SETTING (setting), key_name, &secret_flags, NULL))
+					nm_assert_not_reached ();
+				if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) {
+					*((gboolean *) arg) = TRUE;
+					return TRUE;
+				}
 			}
 		}
 
-		/* Ok, we have no secrets with system-secret flags.
+		/* OK, we have no secrets with system-secret flags.
 		 * But do we have any secret-flags (without secrets) that indicate system secrets? */
-		g_hash_table_iter_init (&iter, priv->data);
-		while (g_hash_table_iter_next (&iter, (gpointer *) &key_name, NULL)) {
-			gs_free char *secret_name = NULL;
+		if (priv->data) {
+			g_hash_table_iter_init (&iter, priv->data);
+			while (g_hash_table_iter_next (&iter, (gpointer *) &key_name, NULL)) {
+				gs_free char *secret_name = NULL;
 
-			if (!g_str_has_suffix (key_name, "-flags"))
-				continue;
-			secret_name = g_strndup (key_name, strlen (key_name) - NM_STRLEN ("-flags"));
-			if (secret_name[0] == '\0')
-				continue;
-			if (!nm_setting_get_secret_flags (NM_SETTING (setting), secret_name, &secret_flags, NULL))
-				nm_assert_not_reached ();
-			if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) {
-				*((gboolean *) arg) = TRUE;
-				return TRUE;
+				if (!NM_STR_HAS_SUFFIX (key_name, "-flags"))
+					continue;
+				secret_name = g_strndup (key_name, strlen (key_name) - NM_STRLEN ("-flags"));
+				if (secret_name[0] == '\0')
+					continue;
+				if (!nm_setting_get_secret_flags (NM_SETTING (setting), secret_name, &secret_flags, NULL))
+					nm_assert_not_reached ();
+				if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) {
+					*((gboolean *) arg) = TRUE;
+					return TRUE;
+				}
 			}
 		}
 
@@ -517,8 +552,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, NM_SETTING_VPN_SERVICE_TYPE);
 		return FALSE;
 	}
-
-	if (!strlen (priv->service_type)) {
+	if (!priv->service_type[0]) {
 		g_set_error_literal (error,
 		                     NM_CONNECTION_ERROR,
 		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -528,7 +562,8 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	}
 
 	/* default username can be NULL, but can't be zero-length */
-	if (priv->user_name && !strlen (priv->user_name)) {
+	if (   priv->user_name
+	    && !priv->user_name[0]) {
 		g_set_error_literal (error,
 		                     NM_CONNECTION_ERROR,
 		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -558,21 +593,15 @@ update_secret_string (NMSetting *setting,
 {
 	NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting);
 
-	g_return_val_if_fail (key != NULL, NM_SETTING_UPDATE_SECRET_ERROR);
-	g_return_val_if_fail (value != NULL, NM_SETTING_UPDATE_SECRET_ERROR);
-
-	if (!value || !strlen (value)) {
-		g_set_error (error, NM_CONNECTION_ERROR,
-		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-		             _("secret was empty"));
-		g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, key);
-		return NM_SETTING_UPDATE_SECRET_ERROR;
-	}
+	g_return_val_if_fail (key && key[0], NM_SETTING_UPDATE_SECRET_ERROR);
+	g_return_val_if_fail (value, NM_SETTING_UPDATE_SECRET_ERROR);
 
-	if (g_strcmp0 (g_hash_table_lookup (priv->secrets, key), value) == 0)
+	if (nm_streq0 (nm_g_hash_table_lookup (priv->secrets, key), value))
 		return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED;
 
-	g_hash_table_insert (priv->secrets, g_strdup (key), g_strdup (value));
+	g_hash_table_insert (_ensure_strdict (&priv->secrets, TRUE),
+	                     g_strdup (key),
+	                     g_strdup (value));
 	return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED;
 }
 
@@ -591,39 +620,24 @@ update_secret_dict (NMSetting *setting,
 	/* Make sure the items are valid */
 	g_variant_iter_init (&iter, secrets);
 	while (g_variant_iter_next (&iter, "{&s&s}", &name, &value)) {
-		if (!name || !strlen (name)) {
+		if (!name[0]) {
 			g_set_error_literal (error, NM_CONNECTION_ERROR,
 			                     NM_CONNECTION_ERROR_INVALID_SETTING,
 			                     _("setting contained a secret with an empty name"));
 			g_prefix_error (error, "%s: ", NM_SETTING_VPN_SETTING_NAME);
 			return NM_SETTING_UPDATE_SECRET_ERROR;
 		}
-
-		if (!value || !strlen (value)) {
-			g_set_error (error, NM_CONNECTION_ERROR,
-			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			             _("secret value was empty"));
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, name);
-			return NM_SETTING_UPDATE_SECRET_ERROR;
-		}
 	}
 
 	/* Now add the items to the settings' secrets list */
 	g_variant_iter_init (&iter, secrets);
 	while (g_variant_iter_next (&iter, "{&s&s}", &name, &value)) {
-		if (value == NULL) {
-			g_warn_if_fail (value != NULL);
-			continue;
-		}
-		if (strlen (value) == 0) {
-			g_warn_if_fail (strlen (value) > 0);
+		if (nm_streq0 (nm_g_hash_table_lookup (priv->secrets, name), value))
 			continue;
-		}
 
-		if (g_strcmp0 (g_hash_table_lookup (priv->secrets, name), value) == 0)
-			continue;
-
-		g_hash_table_insert (priv->secrets, g_strdup (name), g_strdup (value));
+		g_hash_table_insert (_ensure_strdict (&priv->secrets, TRUE),
+		                     g_strdup (name),
+		                     g_strdup (value));
 		result = NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED;
 	}
 
@@ -646,7 +660,7 @@ update_one_secret (NMSetting *setting, const char *key, GVariant *value, GError
 		 */
 		success = update_secret_string (setting, key, g_variant_get_string (value, NULL), error);
 	} else if (g_variant_is_of_type (value, G_VARIANT_TYPE ("a{ss}"))) {
-		if (strcmp (key, NM_SETTING_VPN_SECRETS) != 0) {
+		if (!nm_streq (key, NM_SETTING_VPN_SECRETS)) {
 			g_set_error_literal (error, NM_CONNECTION_ERROR,
 			                     NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
 			                     _("not a secret property"));
@@ -728,14 +742,23 @@ get_secret_flags (NMSetting *setting,
 	const char *flags_val;
 	gint64 i64;
 
+	nm_assert (secret_name);
+
+	if (!secret_name[0]) {
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+		             _("secret name cannot be empty"));
+		return FALSE;
+	}
+
 	flags_key = nm_construct_name_a ("%s-flags", secret_name, &flags_key_free);
 
-	if (!g_hash_table_lookup_extended (priv->data, flags_key, NULL, (gpointer *) &flags_val)) {
+	if (   !priv->data
+	    || !g_hash_table_lookup_extended (priv->data, flags_key, NULL, (gpointer *) &flags_val)) {
 		NM_SET_OUT (out_flags, NM_SETTING_SECRET_FLAG_NONE);
 
 		/* having no secret flag for the secret is fine, as long as there
 		 * is the secret itself... */
-		if (!g_hash_table_contains (priv->secrets, secret_name)) {
+		if (!nm_g_hash_table_lookup (priv->secrets, secret_name)) {
 			g_set_error_literal (error,
 			                     NM_CONNECTION_ERROR,
 			                     NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
@@ -768,7 +791,15 @@ set_secret_flags (NMSetting *setting,
                   NMSettingSecretFlags flags,
                   GError **error)
 {
-	g_hash_table_insert (NM_SETTING_VPN_GET_PRIVATE (setting)->data,
+	nm_assert (secret_name);
+
+	if (!secret_name[0]) {
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
+		             _("secret name cannot be empty"));
+		return FALSE;
+	}
+
+	g_hash_table_insert (_ensure_strdict (&NM_SETTING_VPN_GET_PRIVATE (setting)->data, FALSE),
 	                     g_strdup_printf ("%s-flags", secret_name),
 	                     g_strdup_printf ("%u", flags));
 	_notify (NM_SETTING_VPN (setting), PROP_SECRETS);
@@ -802,8 +833,12 @@ compare_property_secrets (NMSettingVpn *a,
 	for (run = 0; run < 2; run++) {
 		NMSettingVpn *current_a = (run == 0) ? a : b;
 		NMSettingVpn *current_b = (run == 0) ? b : a;
+		NMSettingVpnPrivate *priv_a = NM_SETTING_VPN_GET_PRIVATE (current_a);
+
+		if (!priv_a->secrets)
+			continue;
 
-		g_hash_table_iter_init (&iter, NM_SETTING_VPN_GET_PRIVATE (current_a)->secrets);
+		g_hash_table_iter_init (&iter, priv_a->secrets);
 		while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &val)) {
 
 			if (nm_streq0 (val, nm_setting_vpn_get_secret (current_b, key)))
@@ -899,11 +934,26 @@ vpn_secrets_from_dbus (NMSetting *setting,
                        NMSettingParseFlags parse_flags,
                        GError **error)
 {
-	nm_auto_unset_gvalue GValue object_value = G_VALUE_INIT;
+	NMSettingVpn *self = NM_SETTING_VPN (setting);
+	NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (self);
+	gs_unref_hashtable GHashTable *hash_free = NULL;
+	GVariantIter iter;
+	const char *key;
+	const char *val;
+
+	hash_free = g_steal_pointer (&priv->secrets);
+
+	g_variant_iter_init (&iter, value);
+	while (g_variant_iter_next (&iter, "{&s&s}", &key, &val)) {
+		if (!key[0])
+			continue;
+		g_hash_table_insert (_ensure_strdict (&priv->secrets, TRUE),
+		                     g_strdup (key),
+		                     g_strdup (val));
+	}
 
-	g_value_init (&object_value, G_TYPE_HASH_TABLE);
-	_nm_utils_strdict_from_dbus (value, &object_value);
-	return nm_g_object_set_property (G_OBJECT (setting), property, &object_value, error);
+	_notify (self, PROP_SECRETS);
+	return TRUE;
 }
 
 static GVariant *
@@ -914,29 +964,30 @@ vpn_secrets_to_dbus (const NMSettInfoSetting *sett_info,
                      NMConnectionSerializationFlags flags,
                      const NMConnectionSerializationOptions *options)
 {
-	gs_unref_hashtable GHashTable *secrets = NULL;
-	const char *property_name = sett_info->property_infos[property_idx].name;
+	NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting);
 	GVariantBuilder builder;
-	GHashTableIter iter;
-	const char *key, *value;
-	NMSettingSecretFlags secret_flags;
+	gs_free const char **keys = NULL;
+	guint i, len;
 
 	if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_NO_SECRETS))
 		return NULL;
 
 	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
-	g_object_get (setting, property_name, &secrets, NULL);
-
-	if (secrets) {
-		g_hash_table_iter_init (&iter, secrets);
-		while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) {
-			if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED)) {
-				if (   !nm_setting_get_secret_flags (setting, key, &secret_flags, NULL)
-				    || !NM_FLAGS_HAS (secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED))
-					continue;
-			}
-			g_variant_builder_add (&builder, "{ss}", key, value);
+
+	keys = nm_utils_strdict_get_keys (priv->secrets, TRUE, &len);
+	for (i = 0; i < len; i++) {
+		const char *key = keys[i];
+		NMSettingSecretFlags secret_flags;
+
+		if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED)) {
+			if (   !nm_setting_get_secret_flags (setting, key, &secret_flags, NULL)
+			    || !NM_FLAGS_HAS (secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED))
+				continue;
 		}
+		g_variant_builder_add (&builder,
+		                       "{ss}",
+		                       key,
+		                       g_hash_table_lookup (priv->secrets, key));
 	}
 
 	return g_variant_builder_end (&builder);
@@ -995,12 +1046,42 @@ set_property (GObject *object, guint prop_id,
 		priv->persistent = g_value_get_boolean (value);
 		break;
 	case PROP_DATA:
-		g_hash_table_unref (priv->data);
-		priv->data = _nm_utils_copy_strdict (g_value_get_boxed (value));
-		break;
-	case PROP_SECRETS:
-		g_hash_table_unref (priv->secrets);
-		priv->secrets = _nm_utils_copy_strdict (g_value_get_boxed (value));
+	case PROP_SECRETS: {
+			gs_unref_hashtable GHashTable *hash_free = NULL;
+			GHashTable *src_hash = g_value_get_boxed (value);
+			GHashTable **p_hash;
+			const gboolean is_secrets = (prop_id == PROP_SECRETS);
+
+			if (is_secrets)
+				p_hash = &priv->secrets;
+			else
+				p_hash = &priv->data;
+
+			hash_free = g_steal_pointer (p_hash);
+
+			if (   src_hash
+			    && g_hash_table_size (src_hash) > 0) {
+				GHashTableIter iter;
+				const char *key;
+				const char *val;
+
+				g_hash_table_iter_init (&iter, src_hash);
+				while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val)) {
+					if (   !key
+					    || !key[0]
+					    || !val) {
+						/* NULL keys/values and empty key are not allowed. Usually, we would reject them in verify(), but
+						 * then our nm_setting_vpn_remove_data_item() also doesn't allow empty keys. So, if we failed
+						 * it in verify(), it would be only fixable by setting PROP_DATA again. Instead,
+						 * silently ignore them. */
+						continue;
+					}
+					g_hash_table_insert (_ensure_strdict (p_hash, is_secrets),
+					                     g_strdup (key),
+					                     g_strdup (val));
+				}
+			}
+		}
 		break;
 	case PROP_TIMEOUT:
 		priv->timeout = g_value_get_uint (value);
@@ -1016,10 +1097,6 @@ set_property (GObject *object, guint prop_id,
 static void
 nm_setting_vpn_init (NMSettingVpn *setting)
 {
-	NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE (setting);
-
-	priv->data = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
-	priv->secrets = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, (GDestroyNotify) nm_free_secret);
 }
 
 /**
@@ -1042,8 +1119,10 @@ finalize (GObject *object)
 
 	g_free (priv->service_type);
 	g_free (priv->user_name);
-	g_hash_table_destroy (priv->data);
-	g_hash_table_destroy (priv->secrets);
+	if (priv->data)
+		g_hash_table_unref (priv->data);
+	if (priv->secrets)
+		g_hash_table_unref (priv->secrets);
 
 	G_OBJECT_CLASS (nm_setting_vpn_parent_class)->finalize (object);
 }
diff --git a/libnm-core/nm-setting-vrf.c b/libnm-core/nm-setting-vrf.c
new file mode 100644
index 00000000..296f4542
--- /dev/null
+++ b/libnm-core/nm-setting-vrf.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: LGPL-2.1+
+
+#include "nm-default.h"
+
+#include "nm-setting-vrf.h"
+
+#include "nm-connection-private.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-private.h"
+
+/**
+ * SECTION:nm-setting-vrf
+ * @short_description: Describes connection properties for vrf interfaces
+ *
+ * The #NMSettingVrf object is a #NMSetting subclass that describes properties
+ * necessary for connection to vrf devices
+ **/
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+	PROP_TABLE,
+);
+
+/**
+ * NMSettingVrf:
+ *
+ * VRF settings
+ *
+ * Since: 1.24
+ */
+struct _NMSettingVrf {
+	NMSetting parent;
+	guint32 table;
+};
+
+struct _NMSettingVrfClass {
+	NMSettingClass parent;
+};
+
+G_DEFINE_TYPE (NMSettingVrf, nm_setting_vrf, NM_TYPE_SETTING)
+
+/*****************************************************************************/
+
+/**
+ * nm_setting_vrf_get_table:
+ * @setting: the #NMSettingVrf
+ *
+ * Returns: the routing table for the VRF
+ *
+ * Since: 1.24
+ **/
+guint32
+nm_setting_vrf_get_table (NMSettingVrf *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_VRF (setting), 0);
+
+	return setting->table;
+}
+
+/*****************************************************************************/
+
+static gboolean
+verify (NMSetting *setting, NMConnection *connection, GError **error)
+{
+	NMSettingVrf *self = NM_SETTING_VRF (setting);
+
+	if (!_nm_connection_verify_required_interface_name (connection, error))
+		return FALSE;
+
+	if (self->table == 0) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("table cannot be zero"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_VRF_SETTING_NAME,
+		                NM_SETTING_VRF_TABLE);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMSettingVrf *self = NM_SETTING_VRF (object);
+
+	switch (prop_id) {
+	case PROP_TABLE:
+		g_value_set_uint (value, self->table);
+		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)
+{
+	NMSettingVrf *self = NM_SETTING_VRF (object);
+
+	switch (prop_id) {
+	case PROP_TABLE:
+		self->table = g_value_get_uint (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_setting_vrf_init (NMSettingVrf *setting)
+{
+}
+
+/**
+ * nm_setting_vrf_new:
+ *
+ * Creates a new #NMSettingVrf object with default values.
+ *
+ * Returns: (transfer full): the new empty #NMSettingVrf object
+ *
+ * Since: 1.24
+ **/
+NMSetting *
+nm_setting_vrf_new (void)
+{
+	return (NMSetting *) g_object_new (NM_TYPE_SETTING_VRF, NULL);
+}
+
+static void
+nm_setting_vrf_class_init (NMSettingVrfClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMSettingClass *setting_class = NM_SETTING_CLASS (klass);
+
+	object_class->get_property = get_property;
+	object_class->set_property = set_property;
+
+	setting_class->verify = verify;
+
+	/**
+	 * NMSettingVrf:table:
+	 *
+	 * The routing table for this VRF.
+	 *
+	 * Since: 1.24
+	 **/
+	obj_properties[PROP_TABLE] =
+	    g_param_spec_uint (NM_SETTING_VRF_TABLE, "", "",
+	                       0, G_MAXUINT32, 0,
+	                       G_PARAM_READWRITE |
+	                       NM_SETTING_PARAM_INFERRABLE |
+	                       G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+	_nm_setting_class_commit (setting_class, NM_META_SETTING_TYPE_VRF);
+}
diff --git a/libnm-core/nm-setting-vrf.h b/libnm-core/nm-setting-vrf.h
new file mode 100644
index 00000000..774a4824
--- /dev/null
+++ b/libnm-core/nm-setting-vrf.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: LGPL-2.1+
+
+#ifndef __NM_SETTING_VRF_H__
+#define __NM_SETTING_VRF_H__
+
+#if !defined (__NETWORKMANAGER_H_INSIDE__) && !defined (NETWORKMANAGER_COMPILATION)
+#error "Only <NetworkManager.h> can be included directly."
+#endif
+
+#include "nm-setting.h"
+
+G_BEGIN_DECLS
+
+#define NM_TYPE_SETTING_VRF            (nm_setting_vrf_get_type ())
+#define NM_SETTING_VRF(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTING_VRF, NMSettingVrf))
+#define NM_SETTING_VRF_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTING_VRFCONFIG, NMSettingVrfClass))
+#define NM_IS_SETTING_VRF(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SETTING_VRF))
+#define NM_IS_SETTING_VRF_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SETTING_VRF))
+#define NM_SETTING_VRF_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SETTING_VRF, NMSettingVrfClass))
+
+#define NM_SETTING_VRF_SETTING_NAME         "vrf"
+
+#define NM_SETTING_VRF_TABLE                "table"
+
+typedef struct _NMSettingVrfClass NMSettingVrfClass;
+
+NM_AVAILABLE_IN_1_24
+GType nm_setting_vrf_get_type (void);
+NM_AVAILABLE_IN_1_24
+NMSetting *nm_setting_vrf_new (void);
+NM_AVAILABLE_IN_1_24
+guint32 nm_setting_vrf_get_table (NMSettingVrf *setting);
+
+G_END_DECLS
+
+#endif /* __NM_SETTING_VRF_H__ */
diff --git a/libnm-core/nm-setting-vxlan.c b/libnm-core/nm-setting-vxlan.c
index 4e449bc8..edde1ad5 100644
--- a/libnm-core/nm-setting-vxlan.c
+++ b/libnm-core/nm-setting-vxlan.c
@@ -45,9 +45,9 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 
 typedef struct {
 	char *parent;
-	guint id;
 	char *local;
 	char *remote;
+	guint id;
 	guint source_port_min;
 	guint source_port_max;
 	guint destination_port;
@@ -55,11 +55,11 @@ typedef struct {
 	guint ttl;
 	guint ageing;
 	guint limit;
-	gboolean learning;
-	gboolean proxy;
-	gboolean rsc;
-	gboolean l2_miss;
-	gboolean l3_miss;
+	bool learning:1;
+	bool proxy:1;
+	bool rsc:1;
+	bool l2_miss:1;
+	bool l3_miss:1;
 } NMSettingVxlanPrivate;
 
 G_DEFINE_TYPE (NMSettingVxlan, nm_setting_vxlan, NM_TYPE_SETTING)
@@ -327,9 +327,9 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (nm_utils_ipaddr_valid (AF_INET, priv->remote))
+	if (nm_utils_ipaddr_is_valid (AF_INET, priv->remote))
 		family = AF_INET;
-	else if (nm_utils_ipaddr_valid (AF_INET6, priv->remote))
+	else if (nm_utils_ipaddr_is_valid (AF_INET6, priv->remote))
 		family = AF_INET6;
 	else {
 		g_set_error (error,
@@ -344,7 +344,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	}
 
 	if (priv->local) {
-		if (!nm_utils_ipaddr_valid (family, priv->local)) {
+		if (!nm_utils_ipaddr_is_valid (family, priv->local)) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -519,8 +519,13 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_setting_vxlan_init (NMSettingVxlan *setting)
+nm_setting_vxlan_init (NMSettingVxlan *self)
 {
+	NMSettingVxlanPrivate *priv = NM_SETTING_VXLAN_GET_PRIVATE (self);
+
+	priv->destination_port = DST_PORT_DEFAULT;
+	priv->ageing           = 300;
+	priv->learning         = TRUE;
 }
 
 /**
@@ -576,7 +581,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_string (NM_SETTING_VXLAN_PARENT, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 	/**
@@ -591,7 +595,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_uint (NM_SETTING_VXLAN_ID, "", "",
 	                       0, (1 << 24) - 1, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -606,7 +609,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_string (NM_SETTING_VXLAN_LOCAL, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
@@ -623,7 +625,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_string (NM_SETTING_VXLAN_REMOTE, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
-	                         G_PARAM_CONSTRUCT |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
@@ -639,7 +640,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_uint (NM_SETTING_VXLAN_SOURCE_PORT_MIN, "", "",
 	                       0, G_MAXUINT16, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -655,7 +655,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_uint (NM_SETTING_VXLAN_SOURCE_PORT_MAX, "", "",
 	                       0, G_MAXUINT16, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -671,7 +670,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_uint (NM_SETTING_VXLAN_DESTINATION_PORT, "", "",
 	                       0, G_MAXUINT16, DST_PORT_DEFAULT,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -686,7 +684,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_uint (NM_SETTING_VXLAN_AGEING, "", "",
 	                       0, G_MAXUINT32, 300,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -702,7 +699,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_uint (NM_SETTING_VXLAN_LIMIT, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -717,7 +713,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_uint (NM_SETTING_VXLAN_TOS, "", "",
 	                       0, 255, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -732,7 +727,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_uint (NM_SETTING_VXLAN_TTL, "", "",
 	                       0, 255, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_INFERRABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -747,7 +741,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_boolean (NM_SETTING_VXLAN_PROXY, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_INFERRABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
@@ -763,7 +756,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_boolean (NM_SETTING_VXLAN_LEARNING, "", "",
 	                          TRUE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_INFERRABLE |
 	                          G_PARAM_STATIC_STRINGS);
 	/**
@@ -777,7 +769,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_boolean (NM_SETTING_VXLAN_RSC, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_INFERRABLE |
 	                          G_PARAM_STATIC_STRINGS);
 	/**
@@ -791,7 +782,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_boolean (NM_SETTING_VXLAN_L2_MISS, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_INFERRABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
@@ -806,7 +796,6 @@ nm_setting_vxlan_class_init (NMSettingVxlanClass *klass)
 	    g_param_spec_boolean (NM_SETTING_VXLAN_L3_MISS, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          NM_SETTING_PARAM_INFERRABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-wifi-p2p.c b/libnm-core/nm-setting-wifi-p2p.c
index df33f1d0..065f6e32 100644
--- a/libnm-core/nm-setting-wifi-p2p.c
+++ b/libnm-core/nm-setting-wifi-p2p.c
@@ -183,7 +183,7 @@ set_property (GObject *object, guint prop_id,
 		priv->wps_method = g_value_get_uint (value);
 		break;
 	case PROP_WFD_IES:
-		g_clear_pointer (&priv->wfd_ies, g_bytes_unref);
+		nm_clear_pointer (&priv->wfd_ies, g_bytes_unref);
 		priv->wfd_ies = g_value_dup_boxed (value);
 		break;
 	default:
diff --git a/libnm-core/nm-setting-wimax.c b/libnm-core/nm-setting-wimax.c
index cfc1250e..0e547989 100644
--- a/libnm-core/nm-setting-wimax.c
+++ b/libnm-core/nm-setting-wimax.c
@@ -236,7 +236,7 @@ nm_setting_wimax_class_init (NMSettingWimaxClass *klass)
 	                         NULL,
 	                         G_PARAM_READWRITE |
 	                         G_PARAM_STATIC_STRINGS);
-	_nm_properties_override_gobj (properties_override, obj_properties[PROP_MAC_ADDRESS], &nm_sett_info_propert_type_mac_addrees);
+	_nm_properties_override_gobj (properties_override, obj_properties[PROP_MAC_ADDRESS], &nm_sett_info_propert_type_mac_address);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
diff --git a/libnm-core/nm-setting-wired.c b/libnm-core/nm-setting-wired.c
index f328e7a6..ab13d88a 100644
--- a/libnm-core/nm-setting-wired.c
+++ b/libnm-core/nm-setting-wired.c
@@ -48,24 +48,24 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingWired,
 );
 
 typedef struct {
+	struct {
+		NMUtilsNamedValue *arr;
+		guint len;
+		guint n_alloc;
+	} s390_options;
+	GArray *mac_address_blacklist;
+	char **s390_subchannels;
 	char *port;
-	guint32 speed;
 	char *duplex;
-	gboolean auto_negotiate;
 	char *device_mac_address;
 	char *cloned_mac_address;
 	char *generate_mac_address_mask;
-	GArray *mac_address_blacklist;
-	guint32 mtu;
-	char **s390_subchannels;
 	char *s390_nettype;
-	struct {
-		NMUtilsNamedValue *arr;
-		guint len;
-		guint n_alloc;
-	} s390_options;
-	NMSettingWiredWakeOnLan wol;
 	char *wol_password;
+	NMSettingWiredWakeOnLan wol;
+	guint32 speed;
+	guint32 mtu;
+	bool auto_negotiate:1;
 } NMSettingWiredPrivate;
 
 G_DEFINE_TYPE (NMSettingWired, nm_setting_wired, NM_TYPE_SETTING)
@@ -1145,6 +1145,8 @@ nm_setting_wired_init (NMSettingWired *setting)
 	/* We use GArray rather than GPtrArray so it will automatically be NULL-terminated */
 	priv->mac_address_blacklist = g_array_new (TRUE, FALSE, sizeof (char *));
 	g_array_set_clear_func (priv->mac_address_blacklist, (GDestroyNotify) clear_blacklist_item);
+
+	priv->wol = NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT;
 }
 
 /**
@@ -1248,7 +1250,6 @@ nm_setting_wired_class_init (NMSettingWiredClass *klass)
 	    g_param_spec_uint (NM_SETTING_WIRED_SPEED, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -1304,7 +1305,6 @@ nm_setting_wired_class_init (NMSettingWiredClass *klass)
 	    g_param_spec_boolean (NM_SETTING_WIRED_AUTO_NEGOTIATE, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 	_nm_properties_override_gobj (properties_override,
 	                              obj_properties[PROP_AUTO_NEGOTIATE],
@@ -1343,7 +1343,7 @@ nm_setting_wired_class_init (NMSettingWiredClass *klass)
 	                         G_PARAM_READWRITE |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
-	_nm_properties_override_gobj (properties_override, obj_properties[PROP_MAC_ADDRESS], &nm_sett_info_propert_type_mac_addrees);
+	_nm_properties_override_gobj (properties_override, obj_properties[PROP_MAC_ADDRESS], &nm_sett_info_propert_type_mac_address);
 
 	/**
 	 * NMSettingWired:cloned-mac-address:
@@ -1501,7 +1501,6 @@ nm_setting_wired_class_init (NMSettingWiredClass *klass)
 	    g_param_spec_uint (NM_SETTING_WIRED_MTU, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1591,7 +1590,6 @@ nm_setting_wired_class_init (NMSettingWiredClass *klass)
 	obj_properties[PROP_WAKE_ON_LAN] =
 	    g_param_spec_uint (NM_SETTING_WIRED_WAKE_ON_LAN, "", "",
 	                       0, G_MAXUINT32, NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT,
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_READWRITE |
 	                       G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-wireless-security.c b/libnm-core/nm-setting-wireless-security.c
index a634546d..036434cc 100644
--- a/libnm-core/nm-setting-wireless-security.c
+++ b/libnm-core/nm-setting-wireless-security.c
@@ -13,6 +13,7 @@
 #include "nm-utils-private.h"
 #include "nm-setting-private.h"
 #include "nm-setting-wireless.h"
+#include "nm-glib-aux/nm-secret-utils.h"
 
 /**
  * SECTION:nm-setting-wireless-security
@@ -62,36 +63,26 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingWirelessSecurity,
 );
 
 typedef struct {
+	GSList *proto;    /* GSList of strings */
+	GSList *pairwise; /* GSList of strings */
+	GSList *group;    /* GSList of strings */
 	char *key_mgmt;
 	char *auth_alg;
-	GSList *proto; /* GSList of strings */
-	GSList *pairwise; /* GSList of strings */
-	GSList *group; /* GSList of strings */
-	NMSettingWirelessSecurityPmf pmf;
-
-	/* LEAP */
 	char *leap_username;
 	char *leap_password;
-	NMSettingSecretFlags leap_password_flags;
-
-	/* WEP */
 	char *wep_key0;
 	char *wep_key1;
 	char *wep_key2;
 	char *wep_key3;
-	NMSettingSecretFlags wep_key_flags;
-	NMWepKeyType wep_key_type;
-	guint32 wep_tx_keyidx;
-
-	/* WPA-PSK */
 	char *psk;
+	NMSettingSecretFlags leap_password_flags;
+	NMSettingSecretFlags wep_key_flags;
 	NMSettingSecretFlags psk_flags;
-
-	/* WPS */
+	NMSettingWirelessSecurityPmf pmf;
+	NMWepKeyType wep_key_type;
 	NMSettingWirelessSecurityWpsMethod wps_method;
-
-	/* FILS */
 	NMSettingWirelessSecurityFils fils;
+	guint32 wep_tx_keyidx;
 } NMSettingWirelessSecurityPrivate;
 
 G_DEFINE_TYPE (NMSettingWirelessSecurity, nm_setting_wireless_security, NM_TYPE_SETTING)
@@ -175,7 +166,7 @@ nm_setting_wireless_security_add_proto (NMSettingWirelessSecurity *setting, cons
 
 	priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE (setting);
 	for (iter = priv->proto; iter; iter = g_slist_next (iter)) {
-		if (strcasecmp (proto, (char *) iter->data) == 0)
+		if (g_ascii_strcasecmp (proto, (char *) iter->data) == 0)
 			return FALSE;
 	}
 
@@ -229,7 +220,7 @@ nm_setting_wireless_security_remove_proto_by_value (NMSettingWirelessSecurity *s
 
 	priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE (setting);
 	for (iter = priv->proto; iter; iter = g_slist_next (iter)) {
-		if (strcasecmp (proto, (char *) iter->data) == 0) {
+		if (g_ascii_strcasecmp (proto, (char *) iter->data) == 0) {
 			priv->proto = g_slist_delete_link (priv->proto, iter);
 			_notify (setting, PROP_PROTO);
 			return TRUE;
@@ -319,7 +310,7 @@ nm_setting_wireless_security_add_pairwise (NMSettingWirelessSecurity *setting, c
 
 	priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE (setting);
 	for (iter = priv->pairwise; iter; iter = g_slist_next (iter)) {
-		if (strcasecmp (pairwise, (char *) iter->data) == 0)
+		if (g_ascii_strcasecmp (pairwise, (char *) iter->data) == 0)
 			return FALSE;
 	}
 
@@ -375,7 +366,7 @@ nm_setting_wireless_security_remove_pairwise_by_value (NMSettingWirelessSecurity
 
 	priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE (setting);
 	for (iter = priv->pairwise; iter; iter = g_slist_next (iter)) {
-		if (strcasecmp (pairwise, (char *) iter->data) == 0) {
+		if (g_ascii_strcasecmp (pairwise, (char *) iter->data) == 0) {
 			priv->pairwise = g_slist_delete_link (priv->pairwise, iter);
 			_notify (setting, PROP_PAIRWISE);
 			return TRUE;
@@ -466,7 +457,7 @@ nm_setting_wireless_security_add_group (NMSettingWirelessSecurity *setting, cons
 
 	priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE (setting);
 	for (iter = priv->group; iter; iter = g_slist_next (iter)) {
-		if (strcasecmp (group, (char *) iter->data) == 0)
+		if (g_ascii_strcasecmp (group, (char *) iter->data) == 0)
 			return FALSE;
 	}
 
@@ -523,7 +514,7 @@ nm_setting_wireless_security_remove_group_by_value (NMSettingWirelessSecurity *s
 
 	priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE (setting);
 	for (iter = priv->group; iter; iter = g_slist_next (iter)) {
-		if (strcasecmp (group, (char *) iter->data) == 0) {
+		if (g_ascii_strcasecmp (group, (char *) iter->data) == 0) {
 			priv->group = g_slist_delete_link (priv->group, iter);
 			_notify (setting, PROP_GROUP);
 			return TRUE;
@@ -865,7 +856,8 @@ need_secrets (NMSetting *setting)
 	}
 
 	if (   (strcmp (priv->key_mgmt, "ieee8021x") == 0)
-	    || (strcmp (priv->key_mgmt, "wpa-eap") == 0)) {
+	    || (strcmp (priv->key_mgmt, "wpa-eap") == 0)
+	    || (strcmp (priv->key_mgmt, "owe") == 0)) {
 		/* Let caller check the 802.1x setting for secrets */
 		goto no_secrets;
 	}
@@ -884,7 +876,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-psk", "wpa-eap", "sae", NULL };
+	const char *valid_key_mgmt[] = { "none", "ieee8021x", "wpa-psk", "wpa-eap", "sae", "owe", NULL };
 	const char *valid_auth_algs[] = { "open", "shared", "leap", NULL };
 	const char *valid_protos[] = { "wpa", "rsn", NULL };
 	const char *valid_pairwise[] = { "tkip", "ccmp", NULL };
@@ -1054,7 +1046,7 @@ 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", "sae")) {
+	    && !NM_IN_STRSET (priv->key_mgmt, "wpa-eap", "wpa-psk", "sae", "owe")) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -1315,33 +1307,33 @@ set_property (GObject *object, guint prop_id,
 		priv->leap_username = g_value_dup_string (value);
 		break;
 	case PROP_WEP_KEY0:
-		g_free (priv->wep_key0);
+		nm_free_secret (priv->wep_key0);
 		priv->wep_key0 = g_value_dup_string (value);
 		break;
 	case PROP_WEP_KEY1:
-		g_free (priv->wep_key1);
+		nm_free_secret (priv->wep_key1);
 		priv->wep_key1 = g_value_dup_string (value);
 		break;
 	case PROP_WEP_KEY2:
-		g_free (priv->wep_key2);
+		nm_free_secret (priv->wep_key2);
 		priv->wep_key2 = g_value_dup_string (value);
 		break;
 	case PROP_WEP_KEY3:
-		g_free (priv->wep_key3);
+		nm_free_secret (priv->wep_key3);
 		priv->wep_key3 = g_value_dup_string (value);
 		break;
 	case PROP_WEP_KEY_FLAGS:
 		priv->wep_key_flags = g_value_get_flags (value);
 		break;
 	case PROP_PSK:
-		g_free (priv->psk);
+		nm_free_secret (priv->psk);
 		priv->psk = g_value_dup_string (value);
 		break;
 	case PROP_PSK_FLAGS:
 		priv->psk_flags = g_value_get_flags (value);
 		break;
 	case PROP_LEAP_PASSWORD:
-		g_free (priv->leap_password);
+		nm_free_secret (priv->leap_password);
 		priv->leap_password = g_value_dup_string (value);
 		break;
 	case PROP_LEAP_PASSWORD_FLAGS:
@@ -1365,8 +1357,10 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_setting_wireless_security_init (NMSettingWirelessSecurity *setting)
+nm_setting_wireless_security_init (NMSettingWirelessSecurity *self)
 {
+	nm_assert (NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE (self)->wep_key_type == NM_WEP_KEY_TYPE_UNKNOWN);
+	nm_assert (NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE (self)->wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT);
 }
 
 /**
@@ -1391,12 +1385,12 @@ finalize (GObject *object)
 	g_free (priv->key_mgmt);
 	g_free (priv->auth_alg);
 	g_free (priv->leap_username);
-	g_free (priv->wep_key0);
-	g_free (priv->wep_key1);
-	g_free (priv->wep_key2);
-	g_free (priv->wep_key3);
-	g_free (priv->psk);
-	g_free (priv->leap_password);
+	nm_free_secret (priv->wep_key0);
+	nm_free_secret (priv->wep_key1);
+	nm_free_secret (priv->wep_key2);
+	nm_free_secret (priv->wep_key3);
+	nm_free_secret (priv->psk);
+	nm_free_secret (priv->leap_password);
 
 	g_slist_free_full (priv->proto, g_free);
 	g_slist_free_full (priv->pairwise, g_free);
@@ -1429,7 +1423,8 @@ nm_setting_wireless_security_class_init (NMSettingWirelessSecurityClass *klass)
 	 *
 	 * Key management used for the connection.  One of "none" (WEP),
 	 * "ieee8021x" (Dynamic WEP), "wpa-psk" (infrastructure WPA-PSK), "sae"
-	 * (SAE) or "wpa-eap" (WPA-Enterprise).  This property must be set for
+	 * (SAE), "owe" (Opportunistic Wireless Encryption) or "wpa-eap"
+	 * (WPA-Enterprise).  This property must be set for
 	 * any Wi-Fi connection that uses security.
 	 **/
 	/* ---ifcfg-rh---
@@ -1466,7 +1461,6 @@ nm_setting_wireless_security_class_init (NMSettingWirelessSecurityClass *klass)
 	    g_param_spec_uint (NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, "", "",
 	                       0, 3, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -1582,7 +1576,6 @@ nm_setting_wireless_security_class_init (NMSettingWirelessSecurityClass *klass)
 	    g_param_spec_int (NM_SETTING_WIRELESS_SECURITY_PMF, "", "",
 	                      G_MININT32, G_MAXINT32, 0,
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      NM_SETTING_PARAM_FUZZY_IGNORE |
 	                      G_PARAM_STATIC_STRINGS);
 
@@ -1809,7 +1802,6 @@ nm_setting_wireless_security_class_init (NMSettingWirelessSecurityClass *klass)
 	                       NM_TYPE_WEP_KEY_TYPE,
 	                       NM_WEP_KEY_TYPE_UNKNOWN,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 	_nm_properties_override_gobj (properties_override,
 	                              obj_properties[PROP_WEP_KEY_TYPE],
@@ -1844,7 +1836,6 @@ nm_setting_wireless_security_class_init (NMSettingWirelessSecurityClass *klass)
 	    g_param_spec_uint (NM_SETTING_WIRELESS_SECURITY_WPS_METHOD, "", "",
 	                       0, G_MAXUINT32, NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1874,7 +1865,6 @@ nm_setting_wireless_security_class_init (NMSettingWirelessSecurityClass *klass)
 	    g_param_spec_int (NM_SETTING_WIRELESS_SECURITY_FILS, "", "",
 	                      G_MININT32, G_MAXINT32, 0,
 	                      G_PARAM_READWRITE |
-	                      G_PARAM_CONSTRUCT |
 	                      NM_SETTING_PARAM_FUZZY_IGNORE |
 	                      G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting-wireless.c b/libnm-core/nm-setting-wireless.c
index 1731673f..2ef51088 100644
--- a/libnm-core/nm-setting-wireless.c
+++ b/libnm-core/nm-setting-wireless.c
@@ -47,22 +47,22 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingWireless,
 
 typedef struct {
 	GBytes *ssid;
+	GArray *mac_address_blacklist;
+	GPtrArray *seen_bssids;
 	char *mode;
 	char *band;
-	guint32 channel;
 	char *bssid;
-	guint32 rate;
-	guint32 tx_power;
 	char *device_mac_address;
 	char *cloned_mac_address;
 	char *generate_mac_address_mask;
-	GArray *mac_address_blacklist;
-	GPtrArray *seen_bssids;
+	guint32 channel;
+	guint32 rate;
+	guint32 tx_power;
 	guint32 mtu;
-	gboolean hidden;
 	guint32 powersave;
-	NMSettingMacRandomization mac_address_randomization;
 	guint32 wowl;
+	NMSettingMacRandomization mac_address_randomization;
+	bool hidden:1;
 } NMSettingWirelessPrivate;
 
 G_DEFINE_TYPE (NMSettingWireless, nm_setting_wireless, NM_TYPE_SETTING)
@@ -206,7 +206,8 @@ nm_setting_wireless_ap_security_compatible (NMSettingWireless *s_wireless,
 	/* WPA[2]-PSK and WPA[2] Enterprise */
 	if (   !strcmp (key_mgmt, "wpa-psk")
 	    || !strcmp (key_mgmt, "wpa-eap")
-	    || !strcmp (key_mgmt, "sae")) {
+	    || !strcmp (key_mgmt, "sae")
+	    || !strcmp (key_mgmt, "owe")) {
 
 		if (!strcmp (key_mgmt, "wpa-psk")) {
 			if (   !(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_PSK)
@@ -220,6 +221,10 @@ nm_setting_wireless_ap_security_compatible (NMSettingWireless *s_wireless,
 			if (   !(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_SAE)
 			    && !(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_SAE))
 				return FALSE;
+		} else if (!strcmp (key_mgmt, "owe")) {
+			if (   !(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_OWE)
+			    && !(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_OWE))
+				return FALSE;
 		}
 
 		// FIXME: should handle WPA and RSN separately here to ensure that
@@ -1214,6 +1219,8 @@ nm_setting_wireless_init (NMSettingWireless *setting)
 	/* We use GArray rather than GPtrArray so it will automatically be NULL-terminated */
 	priv->mac_address_blacklist = g_array_new (TRUE, FALSE, sizeof (char *));
 	g_array_set_clear_func (priv->mac_address_blacklist, (GDestroyNotify) clear_blacklist_item);
+
+	priv->wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT;
 }
 
 /**
@@ -1354,7 +1361,6 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *klass)
 	    g_param_spec_uint (NM_SETTING_WIRELESS_CHANNEL, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_STATIC_STRINGS);
 
 	/**
@@ -1377,7 +1383,7 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *klass)
 	                         NULL,
 	                         G_PARAM_READWRITE |
 	                         G_PARAM_STATIC_STRINGS);
-	_nm_properties_override_gobj (properties_override, obj_properties[PROP_BSSID], &nm_sett_info_propert_type_mac_addrees);
+	_nm_properties_override_gobj (properties_override, obj_properties[PROP_BSSID], &nm_sett_info_propert_type_mac_address);
 
 	/**
 	 * NMSettingWireless:rate:
@@ -1397,7 +1403,6 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *klass)
 	    g_param_spec_uint (NM_SETTING_WIRELESS_RATE, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1418,7 +1423,6 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *klass)
 	    g_param_spec_uint (NM_SETTING_WIRELESS_TX_POWER, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1451,7 +1455,7 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *klass)
 	                         NULL,
 	                         G_PARAM_READWRITE |
 	                         G_PARAM_STATIC_STRINGS);
-	_nm_properties_override_gobj (properties_override, obj_properties[PROP_MAC_ADDRESS], &nm_sett_info_propert_type_mac_addrees);
+	_nm_properties_override_gobj (properties_override, obj_properties[PROP_MAC_ADDRESS], &nm_sett_info_propert_type_mac_address);
 
 	/**
 	 * NMSettingWireless:cloned-mac-address:
@@ -1635,7 +1639,6 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *klass)
 	    g_param_spec_uint (NM_SETTING_WIRELESS_MTU, "", "",
 	                       0, G_MAXUINT32, 0,
 	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT |
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_STATIC_STRINGS);
 
@@ -1705,7 +1708,7 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *klass)
 	 * 'cloned-mac-address'.
 	 *
 	 * Since: 1.2
-	 * Deprecated: 1.4: Deprecated by NMSettingWireless:cloned-mac-address property
+	 * Deprecated: 1.4: Deprecated by NMSettingWireless:cloned-mac-address property.
 	 **/
 	/* ---ifcfg-rh---
 	 * property: mac-address-randomization
@@ -1763,7 +1766,6 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *klass)
 	obj_properties[PROP_WAKE_ON_WLAN] =
 	    g_param_spec_uint (NM_SETTING_WIRELESS_WAKE_ON_WLAN, "", "",
 	                       0, G_MAXUINT32, NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT,
-	                       G_PARAM_CONSTRUCT |
 	                       G_PARAM_READWRITE |
 	                       G_PARAM_STATIC_STRINGS);
 
diff --git a/libnm-core/nm-setting.c b/libnm-core/nm-setting.c
index b7be9c65..a75302f0 100644
--- a/libnm-core/nm-setting.c
+++ b/libnm-core/nm-setting.c
@@ -151,15 +151,18 @@ _nm_setting_slave_type_is_valid (const char *slave_type, const char **out_port_t
 
 	if (!slave_type)
 		found = FALSE;
-	else if (!strcmp (slave_type, NM_SETTING_BOND_SETTING_NAME))
-		;
-	else if (!strcmp (slave_type, NM_SETTING_BRIDGE_SETTING_NAME))
+	else if (NM_IN_STRSET (slave_type,
+	                       NM_SETTING_BOND_SETTING_NAME,
+	                       NM_SETTING_VRF_SETTING_NAME)) {
+		/* pass */
+	}
+	else if (nm_streq (slave_type, NM_SETTING_BRIDGE_SETTING_NAME))
 		port_type = NM_SETTING_BRIDGE_PORT_SETTING_NAME;
-	else if (!strcmp (slave_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME))
+	else if (nm_streq (slave_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME))
 		port_type = NM_SETTING_OVS_PORT_SETTING_NAME;
-	else if (!strcmp (slave_type, NM_SETTING_OVS_PORT_SETTING_NAME))
+	else if (nm_streq (slave_type, NM_SETTING_OVS_PORT_SETTING_NAME))
 		port_type = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
-	else if (!strcmp (slave_type, NM_SETTING_TEAM_SETTING_NAME))
+	else if (nm_streq (slave_type, NM_SETTING_TEAM_SETTING_NAME))
 		port_type = NM_SETTING_TEAM_PORT_SETTING_NAME;
 	else
 		found = FALSE;
diff --git a/libnm-core/nm-utils-private.h b/libnm-core/nm-utils-private.h
index 4ca9dfc2..f3e9d3f1 100644
--- a/libnm-core/nm-utils-private.h
+++ b/libnm-core/nm-utils-private.h
@@ -49,7 +49,7 @@ gboolean _nm_utils_wps_method_validate (NMSettingWirelessSecurityWpsMethod wps_m
 
 extern const NMSettInfoPropertType nm_sett_info_propert_type_strdict;
 
-extern const NMSettInfoPropertType nm_sett_info_propert_type_mac_addrees;
+extern const NMSettInfoPropertType nm_sett_info_propert_type_mac_address;
 
 extern const NMSettInfoPropertType nm_sett_info_propert_type_cloned_mac_address;
 
@@ -65,6 +65,8 @@ void        _nm_utils_bytes_from_dbus   (GVariant *dbus_value,
 
 char *      _nm_utils_hwaddr_canonical_or_invalid (const char *mac, gssize length);
 
+gboolean    _nm_utils_hwaddr_link_local_valid (const char *mac);
+
 void        _nm_utils_format_variant_attributes_full (GString *str,
                                                       const NMUtilsNamedValue *values,
                                                       guint num_values,
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c
index ea4c52c0..062ff393 100644
--- a/libnm-core/nm-utils.c
+++ b/libnm-core/nm-utils.c
@@ -802,53 +802,7 @@ _nm_utils_hash_values_to_slist (GHashTable *hash)
 static GVariant *
 _nm_utils_strdict_to_dbus (const GValue *prop_value)
 {
-	GHashTable *hash;
-	GHashTableIter iter;
-	const char *key, *value;
-	GVariantBuilder builder;
-	guint i, len;
-
-	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
-
-	hash = g_value_get_boxed (prop_value);
-	if (!hash)
-		goto out;
-	len = g_hash_table_size (hash);
-	if (!len)
-		goto out;
-
-	g_hash_table_iter_init (&iter, hash);
-	if (!g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value))
-		nm_assert_not_reached ();
-
-	if (len == 1)
-		g_variant_builder_add (&builder, "{ss}", key, value);
-	else {
-		gs_free NMUtilsNamedValue *idx_free = NULL;
-		NMUtilsNamedValue *idx;
-
-		if (len > 300 / sizeof (NMUtilsNamedValue)) {
-			idx_free = g_new (NMUtilsNamedValue, len);
-			idx = idx_free;
-		} else
-			idx = g_alloca (sizeof (NMUtilsNamedValue) * len);
-
-		i = 0;
-		do {
-			idx[i].name = key;
-			idx[i].value_str = value;
-			i++;
-		} while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value));
-		nm_assert (i == len);
-
-		nm_utils_named_value_list_sort (idx, len, NULL, NULL);
-
-		for (i = 0; i < len; i++)
-			g_variant_builder_add (&builder, "{ss}", idx[i].name, idx[i].value_str);
-	}
-
-out:
-	return g_variant_builder_end (&builder);
+	return nm_utils_strdict_to_variant_ass (g_value_get_boxed (prop_value));
 }
 
 void
@@ -1119,8 +1073,14 @@ nm_utils_ap_mode_security_valid (NMUtilsSecurityType type,
 	case NMU_SEC_WPA_PSK:
 	case NMU_SEC_WPA2_PSK:
 	case NMU_SEC_SAE:
+	case NMU_SEC_OWE:
 		return TRUE;
-	default:
+	case NMU_SEC_LEAP:
+	case NMU_SEC_DYNAMIC_WEP:
+	case NMU_SEC_WPA_ENTERPRISE:
+	case NMU_SEC_WPA2_ENTERPRISE:
+		return FALSE;
+	case NMU_SEC_INVALID:
 		break;
 	}
 	return FALSE;
@@ -1159,48 +1119,46 @@ nm_utils_security_valid (NMUtilsSecurityType type,
                          NM80211ApSecurityFlags ap_wpa,
                          NM80211ApSecurityFlags ap_rsn)
 {
-	gboolean good = TRUE;
-
-	if (!have_ap) {
-		if (type == NMU_SEC_NONE)
-			return TRUE;
-		if (   (type == NMU_SEC_STATIC_WEP)
-		    || ((type == NMU_SEC_DYNAMIC_WEP) && !adhoc)
-		    || ((type == NMU_SEC_LEAP) && !adhoc)) {
-			if (wifi_caps & (NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104))
-				return TRUE;
-			else
-				return FALSE;
-		}
-	}
-
 	switch (type) {
 	case NMU_SEC_NONE:
-		g_assert (have_ap);
+		if (!have_ap)
+			return TRUE;
 		if (ap_flags & NM_802_11_AP_FLAGS_PRIVACY)
 			return FALSE;
-		if (ap_wpa || ap_rsn)
+		if (   ap_wpa
+		    || ap_rsn)
 			return FALSE;
-		break;
+		return TRUE;
 	case NMU_SEC_LEAP: /* require PRIVACY bit for LEAP? */
 		if (adhoc)
 			return FALSE;
 		/* fall-through */
 	case NMU_SEC_STATIC_WEP:
-		g_assert (have_ap);
+		if (!have_ap) {
+			if (wifi_caps & (NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104))
+				return TRUE;
+			return FALSE;
+		}
 		if (!(ap_flags & NM_802_11_AP_FLAGS_PRIVACY))
 			return FALSE;
-		if (ap_wpa || ap_rsn) {
-			if (!device_supports_ap_ciphers (wifi_caps, ap_wpa, TRUE))
+		if (   ap_wpa
+		    || ap_rsn) {
+			if (!device_supports_ap_ciphers (wifi_caps, ap_wpa, TRUE)) {
 				if (!device_supports_ap_ciphers (wifi_caps, ap_rsn, TRUE))
 					return FALSE;
+			}
 		}
-		break;
+		return TRUE;
 	case NMU_SEC_DYNAMIC_WEP:
 		if (adhoc)
 			return FALSE;
-		g_assert (have_ap);
-		if (ap_rsn || !(ap_flags & NM_802_11_AP_FLAGS_PRIVACY))
+		if (!have_ap) {
+			if (wifi_caps & (NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104))
+				return TRUE;
+			return FALSE;
+		}
+		if (   ap_rsn
+		    || !(ap_flags & NM_802_11_AP_FLAGS_PRIVACY))
 			return FALSE;
 		/* Some APs broadcast minimal WPA-enabled beacons that must be handled */
 		if (ap_wpa) {
@@ -1209,102 +1167,99 @@ nm_utils_security_valid (NMUtilsSecurityType type,
 			if (!device_supports_ap_ciphers (wifi_caps, ap_wpa, FALSE))
 				return FALSE;
 		}
-		break;
+		return TRUE;
 	case NMU_SEC_WPA_PSK:
 		if (adhoc)
 			return FALSE;
 		if (!(wifi_caps & NM_WIFI_DEVICE_CAP_WPA))
 			return FALSE;
-		if (have_ap) {
-			if (ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_PSK) {
-				if (   (ap_wpa & NM_802_11_AP_SEC_PAIR_TKIP)
-				    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP))
-					return TRUE;
-				if (   (ap_wpa & NM_802_11_AP_SEC_PAIR_CCMP)
-				    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP))
-					return TRUE;
-			}
-			return FALSE;
+		if (!have_ap)
+			return TRUE;
+		if (ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_PSK) {
+			if (   (ap_wpa & NM_802_11_AP_SEC_PAIR_TKIP)
+			    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP))
+				return TRUE;
+			if (   (ap_wpa & NM_802_11_AP_SEC_PAIR_CCMP)
+			    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP))
+				return TRUE;
 		}
-		break;
+		return FALSE;
 	case NMU_SEC_WPA2_PSK:
 		if (!(wifi_caps & NM_WIFI_DEVICE_CAP_RSN))
 			return FALSE;
-		if (have_ap) {
-			if (adhoc) {
-				if (!(wifi_caps & NM_WIFI_DEVICE_CAP_IBSS_RSN))
-					return FALSE;
-				if (   (ap_rsn & NM_802_11_AP_SEC_PAIR_CCMP)
-				    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP))
-					return TRUE;
-			} else {
-				if (ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_PSK) {
-					if (   (ap_rsn & NM_802_11_AP_SEC_PAIR_TKIP)
-					    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP))
-						return TRUE;
-					if (   (ap_rsn & NM_802_11_AP_SEC_PAIR_CCMP)
-					    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP))
-						return TRUE;
-				}
-			}
+		if (!have_ap)
+			return TRUE;
+		if (adhoc) {
+			if (!(wifi_caps & NM_WIFI_DEVICE_CAP_IBSS_RSN))
+				return FALSE;
+			if (   (ap_rsn & NM_802_11_AP_SEC_PAIR_CCMP)
+			    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP))
+				return TRUE;
 			return FALSE;
 		}
-		break;
+		if (ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_PSK) {
+			if (   (ap_rsn & NM_802_11_AP_SEC_PAIR_TKIP)
+			    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP))
+				return TRUE;
+			if (   (ap_rsn & NM_802_11_AP_SEC_PAIR_CCMP)
+			    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP))
+				return TRUE;
+		}
+		return FALSE;
 	case NMU_SEC_WPA_ENTERPRISE:
 		if (adhoc)
 			return FALSE;
 		if (!(wifi_caps & NM_WIFI_DEVICE_CAP_WPA))
 			return FALSE;
-		if (have_ap) {
-			if (!(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_802_1X))
-				return FALSE;
-			/* Ensure at least one WPA cipher is supported */
-			if (!device_supports_ap_ciphers (wifi_caps, ap_wpa, FALSE))
-				return FALSE;
-		}
-		break;
+		if (!have_ap)
+			return TRUE;
+		if (!(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_802_1X))
+			return FALSE;
+		/* Ensure at least one WPA cipher is supported */
+		if (!device_supports_ap_ciphers (wifi_caps, ap_wpa, FALSE))
+			return FALSE;
+		return TRUE;
 	case NMU_SEC_WPA2_ENTERPRISE:
 		if (adhoc)
 			return FALSE;
 		if (!(wifi_caps & NM_WIFI_DEVICE_CAP_RSN))
 			return FALSE;
-		if (have_ap) {
-			if (!(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_802_1X))
-				return FALSE;
-			/* Ensure at least one WPA cipher is supported */
-			if (!device_supports_ap_ciphers (wifi_caps, ap_rsn, FALSE))
-				return FALSE;
-		}
-		break;
+		if (!have_ap)
+			return TRUE;
+		if (!(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_802_1X))
+			return FALSE;
+		/* Ensure at least one WPA cipher is supported */
+		if (!device_supports_ap_ciphers (wifi_caps, ap_rsn, FALSE))
+			return FALSE;
+		return TRUE;
 	case NMU_SEC_SAE:
 		if (!(wifi_caps & NM_WIFI_DEVICE_CAP_RSN))
 			return FALSE;
-		if (have_ap) {
-			if (adhoc) {
-				if (!(wifi_caps & NM_WIFI_DEVICE_CAP_IBSS_RSN))
-					return FALSE;
-				if (   (ap_rsn & NM_802_11_AP_SEC_PAIR_CCMP)
-				    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP))
-					return TRUE;
-			} else {
-				if (ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_SAE) {
-					if (   (ap_rsn & NM_802_11_AP_SEC_PAIR_TKIP)
-					    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP))
-						return TRUE;
-					if (   (ap_rsn & NM_802_11_AP_SEC_PAIR_CCMP)
-					    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP))
-						return TRUE;
-				}
-			}
+		if (adhoc)
 			return FALSE;
+		if (!have_ap)
+			return TRUE;
+		if (ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_SAE) {
+			if (   (ap_rsn & NM_802_11_AP_SEC_PAIR_CCMP)
+			    && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP))
+				return TRUE;
 		}
-		break;
-	default:
-		good = FALSE;
+		return FALSE;
+	case NMU_SEC_OWE:
+		if (adhoc)
+			return FALSE;
+		if (!(wifi_caps & NM_WIFI_DEVICE_CAP_RSN))
+			return FALSE;
+		if (!have_ap)
+			return TRUE;
+		if (!(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_OWE))
+			return FALSE;
+		return TRUE;
+	case NMU_SEC_INVALID:
 		break;
 	}
 
-	return good;
+	return FALSE;
 }
 
 /**
@@ -2298,7 +2253,7 @@ _nm_utils_parse_tc_handle (const char *str, GError **error)
 
 	nm_assert (str);
 
-	maj = g_ascii_strtoll (str, (char **) &sep, 0x10);
+	maj = nm_g_ascii_strtoll (str, (char **) &sep, 0x10);
 	if (sep == str)
 		goto fail;
 
@@ -2307,7 +2262,7 @@ _nm_utils_parse_tc_handle (const char *str, GError **error)
 	if (sep[0] == ':') {
 		const char *str2 = &sep[1];
 
-		min = g_ascii_strtoll (str2, (char **) &sep, 0x10);
+		min = nm_g_ascii_strtoll (str2, (char **) &sep, 0x10);
 		sep = nm_str_skip_leading_spaces (sep);
 		if (sep[0] != '\0')
 			goto fail;
@@ -4231,6 +4186,41 @@ _nm_utils_hwaddr_canonical_or_invalid (const char *mac, gssize length)
 		return g_strdup (mac);
 }
 
+/*
+ * Determine if given Ethernet address is link-local
+ *
+ * Return value: %TRUE if @mac is link local
+ * reserved addr (01:80:c2:00:00:0X) per IEEE 802.1Q 8.6.3 Frame filtering, %FALSE if not.
+ */
+gboolean
+_nm_utils_hwaddr_link_local_valid (const char *mac)
+{
+	guint8 mac_net[ETH_ALEN];
+	static const guint8 eth_reserved_addr_base[] = {
+		0x01, 0x80,
+		0xc2, 0x00,
+		0x00
+	};
+
+	if (!mac)
+		return FALSE;
+
+	if (!nm_utils_hwaddr_aton (mac, mac_net, ETH_ALEN))
+		return FALSE;
+
+	if (   memcmp (mac_net,
+	               eth_reserved_addr_base, ETH_ALEN - 1)
+	    || (mac_net[5] & 0xF0))
+		return FALSE;
+
+	if (   mac_net[5] == 1  /* 802.3x Pause address */
+	    || mac_net[5] == 2  /* 802.3ad Slow protocols */
+	    || mac_net[5] == 3) /* 802.1X PAE address */
+		return FALSE;
+
+	return TRUE;
+}
+
 /**
  * nm_utils_hwaddr_matches:
  * @hwaddr1: (nullable): pointer to a binary or ASCII hardware address, or %NULL
@@ -4487,7 +4477,7 @@ _nm_utils_hwaddr_from_dbus (GVariant *dbus_value,
 	g_value_take_string (prop_value, str);
 }
 
-const NMSettInfoPropertType nm_sett_info_propert_type_mac_addrees = {
+const NMSettInfoPropertType nm_sett_info_propert_type_mac_address = {
 	.dbus_type           = G_VARIANT_TYPE_BYTESTRING,
 	.gprop_to_dbus_fcn   = _nm_utils_hwaddr_to_dbus,
 	.gprop_from_dbus_fcn = _nm_utils_hwaddr_from_dbus,
@@ -4728,7 +4718,7 @@ nm_utils_is_valid_iface_name (const char *name, GError **error)
  *
  * Validate the network interface name.
  *
- * Deprecated: 1.6: use nm_utils_is_valid_iface_name() instead, with better error reporting.
+ * Deprecated: 1.6: Use nm_utils_is_valid_iface_name() instead, with better error reporting.
  *
  * Returns: %TRUE if interface name is valid, otherwise %FALSE is returned.
  *
@@ -4781,23 +4771,6 @@ nm_utils_is_uuid (const char *str)
 
 static char _nm_utils_inet_ntop_buffer[NM_UTILS_INET_ADDRSTRLEN];
 
-const char *
-nm_utils_inet_ntop (int addr_family, gconstpointer addr, char *dst)
-{
-	const char *s;
-
-	nm_assert_addr_family (addr_family);
-	nm_assert (addr);
-	nm_assert (dst);
-
-	s = inet_ntop (addr_family,
-	               addr,
-	               dst,
-	               addr_family == AF_INET6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN);
-	nm_assert (s);
-	return s;
-}
-
 /**
  * nm_utils_inet4_ntop: (skip)
  * @inaddr: the address that should be converted to string.
@@ -4823,8 +4796,7 @@ nm_utils_inet4_ntop (in_addr_t inaddr, char *dst)
 	 *
 	 * However, still support it to be lenient against mistakes and because
 	 * this is public API of libnm. */
-	return inet_ntop (AF_INET, &inaddr, dst ?: _nm_utils_inet_ntop_buffer,
-	                  INET_ADDRSTRLEN);
+	return _nm_utils_inet4_ntop (inaddr, dst ?: _nm_utils_inet_ntop_buffer);
 }
 
 /**
@@ -4854,8 +4826,7 @@ nm_utils_inet6_ntop (const struct in6_addr *in6addr, char *dst)
 	 * However, still support it to be lenient against mistakes and because
 	 * this is public API of libnm. */
 	g_return_val_if_fail (in6addr, NULL);
-	return inet_ntop (AF_INET6, in6addr, dst ?: _nm_utils_inet_ntop_buffer,
-	                  INET6_ADDRSTRLEN);
+	return _nm_utils_inet6_ntop (in6addr, dst ?: _nm_utils_inet_ntop_buffer);
 }
 
 /**
@@ -4871,17 +4842,9 @@ nm_utils_inet6_ntop (const struct in6_addr *in6addr, char *dst)
 gboolean
 nm_utils_ipaddr_valid (int family, const char *ip)
 {
-	guint8 buf[sizeof (struct in6_addr)];
-
 	g_return_val_if_fail (family == AF_INET || family == AF_INET6 || family == AF_UNSPEC, FALSE);
 
-	if (!ip)
-		return FALSE;
-
-	if (family == AF_UNSPEC)
-		family = strchr (ip, ':') ? AF_INET6 : AF_INET;
-
-	return inet_pton (family, ip, buf) == 1;
+	return nm_utils_ipaddr_is_valid (family, ip);
 }
 
 /**
@@ -5835,7 +5798,7 @@ nm_utils_get_timestamp_msec (void)
 {
 	gint64 ts;
 
-	ts = nm_utils_clock_gettime_ms (CLOCK_BOOTTIME);
+	ts = nm_utils_clock_gettime_msec (CLOCK_BOOTTIME);
 	if (ts >= 0)
 		return ts;
 
@@ -5844,7 +5807,7 @@ nm_utils_get_timestamp_msec (void)
 		 * criminally old kernel, prior to 2.6.39 (released on 18 May, 2011).
 		 * That happens during buildcheck on old builders, we don't expect to
 		 * be actually runs on kernels that old. */
-		ts = nm_utils_clock_gettime_ms (CLOCK_MONOTONIC);
+		ts = nm_utils_clock_gettime_msec (CLOCK_MONOTONIC);
 		if (ts >= 0)
 			return ts;
 	}
diff --git a/libnm-core/nm-utils.h b/libnm-core/nm-utils.h
index 5418a1e6..4cbf24ab 100644
--- a/libnm-core/nm-utils.h
+++ b/libnm-core/nm-utils.h
@@ -51,6 +51,7 @@ char *      nm_utils_ssid_to_utf8  (const guint8 *ssid, gsize len);
  * @NMU_SEC_WPA2_PSK: WPA2/RSN is used with Pre-Shared Keys (PSK)
  * @NMU_SEC_WPA2_ENTERPRISE: WPA2 is used with 802.1x authentication
  * @NMU_SEC_SAE: is used with WPA3 Enterprise
+ * @NMU_SEC_OWE: is used with Enhanced Open
  *
  * Describes generic security mechanisms that 802.11 access points may offer.
  * Used with nm_utils_security_valid() for checking whether a given access
@@ -67,6 +68,7 @@ typedef enum {
 	NMU_SEC_WPA2_PSK,
 	NMU_SEC_WPA2_ENTERPRISE,
 	NMU_SEC_SAE,
+	NMU_SEC_OWE,
 } NMUtilsSecurityType;
 
 gboolean nm_utils_security_valid (NMUtilsSecurityType type,
diff --git a/libnm-core/nm-version.h b/libnm-core/nm-version.h
index 3fd2f346..195fc196 100644
--- a/libnm-core/nm-version.h
+++ b/libnm-core/nm-version.h
@@ -215,24 +215,18 @@
 # define NM_AVAILABLE_IN_1_22
 #endif
 
-#if NM_VERSION_MIN_REQUIRED >= NM_VERSION_1_22_2
-# define NM_DEPRECATED_IN_1_22_2           G_DEPRECATED
-# define NM_DEPRECATED_IN_1_22_2_FOR(f)    G_DEPRECATED_FOR(f)
+#if NM_VERSION_MIN_REQUIRED >= NM_VERSION_1_24
+# define NM_DEPRECATED_IN_1_24           G_DEPRECATED
+# define NM_DEPRECATED_IN_1_24_FOR(f)    G_DEPRECATED_FOR(f)
 #else
-# define NM_DEPRECATED_IN_1_22_2
-# define NM_DEPRECATED_IN_1_22_2_FOR(f)
+# define NM_DEPRECATED_IN_1_24
+# define NM_DEPRECATED_IN_1_24_FOR(f)
 #endif
 
-#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_22_2
-# define NM_AVAILABLE_IN_1_22_2            G_UNAVAILABLE(1,22.2)
+#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_24
+# define NM_AVAILABLE_IN_1_24            G_UNAVAILABLE(1,24)
 #else
-# define NM_AVAILABLE_IN_1_22_2
-#endif
-
-#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_22_8
-# define NM_AVAILABLE_IN_1_22_8            G_UNAVAILABLE(1,22.8)
-#else
-# define NM_AVAILABLE_IN_1_22_8
+# define NM_AVAILABLE_IN_1_24
 #endif
 
 /*
@@ -255,10 +249,11 @@
  *     around g_dbus_connection_call_sync(). You may call it directly
  *     without feeling dirty.
  *
- * We don't want to force users away from this API, for that reason the
- * macro does not yet expand to G_DEPRECATED.
+ * The API is marked as deprecated since 1.22, however the macro only starts
+ * complaining in 1.24. That's intentional, because in 1.22 the asynchronous
+ * alternative was not yet available.
  */
-#define _NM_DEPRECATED_SYNC_METHOD            /*NM_DEPRECATED_IN_1_22*/
-#define _NM_DEPRECATED_SYNC_WRITABLE_PROPERTY /*NM_DEPRECATED_IN_1_22*/
+#define _NM_DEPRECATED_SYNC_METHOD               NM_DEPRECATED_IN_1_24
+#define _NM_DEPRECATED_SYNC_WRITABLE_PROPERTY /* NM_DEPRECATED_IN_1_22 */
 
 #endif  /* NM_VERSION_H */
diff --git a/libnm-core/nm-vpn-plugin-info.c b/libnm-core/nm-vpn-plugin-info.c
index df3ef08d..7fbd8954 100644
--- a/libnm-core/nm-vpn-plugin-info.c
+++ b/libnm-core/nm-vpn-plugin-info.c
@@ -42,13 +42,31 @@ typedef struct {
 	NMVpnEditorPlugin *editor_plugin;
 } NMVpnPluginInfoPrivate;
 
+/**
+ * NMVpnPluginInfo:
+ */
+struct _NMVpnPluginInfo {
+	GObject parent;
+	NMVpnPluginInfoPrivate _priv;
+};
+
+struct _NMVpnPluginInfoClass {
+	GObjectClass parent;
+};
+
+#define NM_VPN_PLUGIN_INFO_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMVpnPluginInfo, NM_IS_VPN_PLUGIN_INFO)
+
 static void nm_vpn_plugin_info_initable_iface_init (GInitableIface *iface);
 
 G_DEFINE_TYPE_WITH_CODE (NMVpnPluginInfo, nm_vpn_plugin_info, G_TYPE_OBJECT,
                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, nm_vpn_plugin_info_initable_iface_init);
                          )
 
-#define NM_VPN_PLUGIN_INFO_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_PLUGIN_INFO, NMVpnPluginInfoPrivate))
+/*****************************************************************************/
+
+static NMVpnPluginInfo *_list_find_by_service (GSList *list,
+                                               const char *name,
+                                               const char *service);
 
 /*****************************************************************************/
 
@@ -172,23 +190,6 @@ _sort_files (LoadDirInfo *a, LoadDirInfo *b)
 	                  nm_vpn_plugin_info_get_filename (b->plugin_info));
 }
 
-#define DEFINE_DEFAULT_DIR_LIST(dir) \
-	const char *dir[] = { \
-		/* We load plugins from NM_VPN_PLUGIN_DIR *and* DEFAULT_DIR*, with
-		 * preference to the former.
-		 *
-		 * load user directory with highest priority. */ \
-		_nm_vpn_plugin_info_get_default_dir_user (), \
-		\
-		/* lib directory has higher priority then etc. The reason is that
-		 * etc is deprecated and used by old plugins. We expect newer plugins
-		 * to install their file in lib, where they have higher priority.
-		 *
-		 * Optimally, there are no duplicates anyway, so it doesn't really matter. */ \
-		_nm_vpn_plugin_info_get_default_dir_lib (), \
-		_nm_vpn_plugin_info_get_default_dir_etc (), \
-	}
-
 /**
  * _nm_vpn_plugin_info_get_default_dir_etc:
  *
@@ -223,7 +224,7 @@ _nm_vpn_plugin_info_get_default_dir_lib ()
 const char *
 _nm_vpn_plugin_info_get_default_dir_user ()
 {
-	return g_getenv ("NM_VPN_PLUGIN_DIR");
+	return nm_str_not_empty (g_getenv ("NM_VPN_PLUGIN_DIR"));
 }
 
 /**
@@ -316,7 +317,21 @@ nm_vpn_plugin_info_list_load ()
 	gint64 uid;
 	GSList *list = NULL;
 	GSList *infos, *info;
-	DEFINE_DEFAULT_DIR_LIST (dir);
+	const char *const dir[] = {
+		/* We load plugins from NM_VPN_PLUGIN_DIR *and* DEFAULT_DIR*, with
+		 * preference to the former.
+		 *
+		 * load user directory with highest priority. */
+		_nm_vpn_plugin_info_get_default_dir_user (),
+
+		/* lib directory has higher priority then etc. The reason is that
+		 * etc is deprecated and used by old plugins. We expect newer plugins
+		 * to install their file in lib, where they have higher priority.
+		 *
+		 * Optimally, there are no duplicates anyway, so it doesn't really matter. */
+		_nm_vpn_plugin_info_get_default_dir_lib (),
+		_nm_vpn_plugin_info_get_default_dir_etc (),
+	};
 
 	uid = getuid ();
 
@@ -354,45 +369,16 @@ nm_vpn_plugin_info_list_load ()
 NMVpnPluginInfo *
 nm_vpn_plugin_info_new_search_file (const char *name, const char *service)
 {
-	int i;
-	gint64 uid;
-	NMVpnPluginInfo *plugin_info = NULL;
-	GSList *infos, *info;
-	DEFINE_DEFAULT_DIR_LIST (dir);
+	NMVpnPluginInfo *info;
+	GSList *infos;
 
 	if (!name && !service)
 		g_return_val_if_reached (NULL);
 
-	uid = getuid ();
-
-	for (i = 0; !plugin_info && i < G_N_ELEMENTS (dir); i++) {
-		if (   !dir[i]
-		    || nm_utils_strv_find_first ((char **) dir, i, dir[i]) >= 0)
-			continue;
-
-		/* We still must load the entire directory while searching for the matching
-		 * plugin-info. The reason is that reading the directory has no stable
-		 * order and we can only sort them after reading the entire directory --
-		 * which _nm_vpn_plugin_info_list_load_dir() does. */
-		infos = _nm_vpn_plugin_info_list_load_dir (dir[i], TRUE, uid, NULL, NULL);
-
-		for (info = infos; info; info = info->next) {
-			NMVpnPluginInfo *p = info->data;
-
-			if (name && !nm_streq (nm_vpn_plugin_info_get_name (p), name))
-				continue;
-			if (   service
-			    && !nm_streq (nm_vpn_plugin_info_get_service (p), service)
-			    && (nm_utils_strv_find_first (NM_VPN_PLUGIN_INFO_GET_PRIVATE (p)->aliases,
-			                                  -1, service) < 0))
-				continue;
-			plugin_info = g_object_ref (p);
-			break;
-		}
-
-		g_slist_free_full (infos, g_object_unref);
-	}
-	return plugin_info;
+	infos = nm_vpn_plugin_info_list_load ();
+	info = nm_g_object_ref (_list_find_by_service (infos, name, service));
+	g_slist_free_full (infos, g_object_unref);
+	return info;
 }
 
 /*****************************************************************************/
@@ -557,14 +543,22 @@ nm_vpn_plugin_info_list_find_by_filename (GSList *list, const char *filename)
 }
 
 static NMVpnPluginInfo *
-_list_find_by_service (GSList *list, const char *service)
+_list_find_by_service (GSList *list,
+                       const char *name,
+                       const char *service)
 {
 	for (; list; list = list->next) {
 		NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (list->data);
 
-		if (   nm_streq (priv->service, service)
-		    || nm_utils_strv_find_first (priv->aliases, -1, service) >= 0)
-			return list->data;
+		if (   name
+		    && !nm_streq (name, priv->name))
+			continue;
+		if (   service
+		    && !nm_streq (priv->service, service)
+		    && (nm_utils_strv_find_first (priv->aliases, -1, service) < 0))
+			continue;
+
+		return list->data;
 	}
 	return NULL;
 }
@@ -584,7 +578,7 @@ nm_vpn_plugin_info_list_find_by_service (GSList *list, const char *service)
 {
 	if (!service)
 		g_return_val_if_reached (NULL);
-	return _list_find_by_service (list, service);
+	return _list_find_by_service (list, NULL, service);
 }
 
 /* known_names are well known short names for the service-type. They all implicitly
@@ -627,7 +621,7 @@ static const char *known_names[] = {
 char *
 nm_vpn_plugin_info_list_find_service_type (GSList *list, const char *name)
 {
-	GSList *iter;
+	NMVpnPluginInfo *info;
 	char *n;
 
 	if (!name)
@@ -636,17 +630,15 @@ nm_vpn_plugin_info_list_find_service_type (GSList *list, const char *name)
 		return NULL;
 
 	/* First, try to interpret @name as a full service-type (or alias). */
-	if (_list_find_by_service (list, name))
+	info = _list_find_by_service (list, NULL, name);
+	if (info)
 		return g_strdup (name);
 
 	/* try to interpret @name as plugin name, in which case we return
 	 * the main service-type (not an alias). */
-	for (iter = list; iter; iter = iter->next) {
-		NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE (iter->data);
-
-		if (nm_streq (priv->name, name))
-			return g_strdup (priv->service);
-	}
+	info = _list_find_by_service (list, name, NULL);
+	if (info)
+		return g_strdup (NM_VPN_PLUGIN_INFO_GET_PRIVATE (info)->service);
 
 	/* check the hard-coded list of short-names. They all have have the same
 	 * well-known prefix org.freedesktop.NetworkManager and the name. */
@@ -656,7 +648,7 @@ nm_vpn_plugin_info_list_find_service_type (GSList *list, const char *name)
 	/* try, if there exists a plugin with @name under org.freedesktop.NetworkManager.
 	 * Allow this to be a valid abbreviation. */
 	n = g_strdup_printf ("%s.%s", NM_DBUS_INTERFACE, name);
-	if (_list_find_by_service (list, n))
+	if (_list_find_by_service (list, NULL, n))
 		return n;
 	g_free (n);
 
@@ -1163,7 +1155,7 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
 
 	priv->aliases = g_key_file_get_string_list (priv->keyfile, NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "aliases", NULL, NULL);
 	if (priv->aliases && !priv->aliases[0])
-		g_clear_pointer (&priv->aliases, g_free);
+		nm_clear_g_free (&priv->aliases);
 
 	priv->keys = g_hash_table_new_full (_nm_utils_strstrdictkey_hash,
 	                                    _nm_utils_strstrdictkey_equal,
@@ -1184,7 +1176,7 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
 		}
 	}
 
-	g_clear_pointer (&priv->keyfile, g_key_file_unref);
+	nm_clear_pointer (&priv->keyfile, g_key_file_unref);
 
 	return TRUE;
 }
@@ -1269,7 +1261,7 @@ finalize (GObject *object)
 	g_free (priv->filename);
 	g_hash_table_unref (priv->keys);
 
-	g_clear_pointer (&priv->keyfile, g_key_file_unref);
+	nm_clear_pointer (&priv->keyfile, g_key_file_unref);
 
 	G_OBJECT_CLASS (nm_vpn_plugin_info_parent_class)->finalize (object);
 }
@@ -1279,8 +1271,6 @@ nm_vpn_plugin_info_class_init (NMVpnPluginInfoClass *plugin_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (plugin_class);
 
-	g_type_class_add_private (object_class, sizeof (NMVpnPluginInfoPrivate));
-
 	object_class->set_property = set_property;
 	object_class->get_property = get_property;
 	object_class->dispose      = dispose;
diff --git a/libnm-core/nm-vpn-plugin-info.h b/libnm-core/nm-vpn-plugin-info.h
index c6242ac6..bc356cf2 100644
--- a/libnm-core/nm-vpn-plugin-info.h
+++ b/libnm-core/nm-vpn-plugin-info.h
@@ -29,24 +29,7 @@ G_BEGIN_DECLS
 #define NM_VPN_PLUGIN_INFO_KF_GROUP_LIBNM        "libnm"
 #define NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME        "GNOME"
 
-struct _NMVpnPluginInfo;
-
-/**
- * NMVpnPluginInfo:
- */
-struct _NMVpnPluginInfo {
-	NM_AVAILABLE_IN_1_2
-	GObject parent;
-};
-
-typedef struct {
-	NM_AVAILABLE_IN_1_2
-	GObjectClass parent;
-
-	/*< private >*/
-	NM_AVAILABLE_IN_1_2
-	gpointer padding[8];
-} NMVpnPluginInfoClass NM_AVAILABLE_IN_1_2;
+typedef struct _NMVpnPluginInfoClass NMVpnPluginInfoClass;
 
 NM_AVAILABLE_IN_1_2
 GType  nm_vpn_plugin_info_get_type       (void);
diff --git a/libnm-core/tests/meson.build b/libnm-core/tests/meson.build
index 292eeaa0..58615e4d 100644
--- a/libnm-core/tests/meson.build
+++ b/libnm-core/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 # test-cert.p12 created with:
 #
 # openssl pkcs12 -export \
@@ -16,6 +18,7 @@ enum_sources = gnome.mkenums_simple(
 )
 
 deps = [
+  libnm_keyfile_dep,
   libnm_core_dep,
   libnm_core_nm_default_dep,
 ]
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index 0ccdd5d2..138702e4 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -10,7 +10,6 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
-#include <glib-unix.h>
 
 #include "nm-std-aux/c-list-util.h"
 #include "nm-glib-aux/nm-enum-utils.h"
@@ -52,7 +51,7 @@
 #include "nm-setting-wireless-security.h"
 #include "nm-setting-wpan.h"
 #include "nm-simple-connection.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 #include "nm-glib-aux/nm-dedup-multi.h"
 #include "nm-libnm-core-intern/nm-ethtool-utils.h"
 
@@ -660,6 +659,333 @@ test_nm_utils_strsplit_set (void)
 
 /*****************************************************************************/
 
+static char *
+_escaped_tokens_create_random_word_full (const char *const*tokens,
+                                         gsize n_tokens,
+                                         gsize len)
+{
+	GString *gstr = g_string_new (NULL);
+	gsize i;
+	char random_token[2] = { 0 };
+
+	for (i = 0; i < len; i++) {
+		const char *token = tokens[nmtst_get_rand_uint32 () % n_tokens];
+
+		if (!token[0]) {
+			do {
+				random_token[0] = nmtst_get_rand_uint32 ();
+			} while (random_token[0] == '\0');
+			token = random_token;
+		}
+		g_string_append (gstr, token);
+	}
+
+	/* reallocate the string, so that we don't have any excess memory from
+	 * the GString buffer. This is so that valgrind may better detect an out
+	 * or range access. */
+	return nm_str_realloc (g_string_free (gstr, FALSE));
+}
+
+/* set to 1 to exclude characters that are annoying to see in the debugger
+ * and printf() output. */
+#define ESCAPED_TOKENS_ONLY_NICE_CHARS 0
+
+static char *
+_escaped_tokens_create_random_whitespace (void)
+{
+	static const char *tokens[] = {
+		" ",
+#if !ESCAPED_TOKENS_ONLY_NICE_CHARS
+		"\n",
+		"\t",
+		"\r",
+		"\f",
+#endif
+	};
+
+	return _escaped_tokens_create_random_word_full (tokens, G_N_ELEMENTS (tokens), nmtst_get_rand_word_length (NULL) / 4u);
+}
+
+static char *
+_escaped_tokens_create_random_word (void)
+{
+	static const char *tokens[] = {
+		"a",
+		"b",
+		"c",
+		" ",
+		",",
+		"=",
+		"\\",
+#if !ESCAPED_TOKENS_ONLY_NICE_CHARS
+		"\n",
+		"\f",
+		":",
+		"",
+#endif
+	};
+
+	return _escaped_tokens_create_random_word_full (tokens, G_N_ELEMENTS (tokens), nmtst_get_rand_word_length (NULL));
+}
+
+static void
+_escaped_tokens_str_append_delimiter (GString *str,
+                                      gboolean strict,
+                                      gboolean needs_delimiter)
+{
+	guint len = nmtst_get_rand_word_length (NULL) / 10u;
+	char *s;
+
+again:
+	if (!strict) {
+		g_string_append (str, (s = _escaped_tokens_create_random_whitespace ()));
+		nm_clear_g_free (&s);
+	}
+
+	if (needs_delimiter)
+		g_string_append_c (str, ',');
+
+	if (!strict) {
+		g_string_append (str, (s = _escaped_tokens_create_random_whitespace ()));
+		nm_clear_g_free (&s);
+		if (len-- > 0) {
+			needs_delimiter = TRUE;
+			goto again;
+		}
+	}
+}
+
+static void
+_escaped_tokens_split (char *str, const char **out_key, const char **out_val)
+{
+	const char *key;
+	const char *val;
+	gsize len = strlen (str);
+
+	g_assert (str);
+
+	nm_utils_escaped_tokens_options_split (str, &key, &val);
+	g_assert (key);
+	g_assert (key == str);
+	if (val) {
+		g_assert (val > str);
+		g_assert (val > key);
+		g_assert (val <= &str[len]);
+	}
+	NM_SET_OUT (out_key, key);
+	NM_SET_OUT (out_val, val);
+}
+
+static void
+_escaped_tokens_combine (GString *combined,
+                         const char *key,
+                         const char *val,
+                         gboolean strict,
+                         gboolean allow_append_delimiter_before,
+                         gboolean needs_delimiter_after)
+{
+	gs_free char *escaped_key = NULL;
+	gs_free char *escaped_val = NULL;
+
+	if (allow_append_delimiter_before)
+		_escaped_tokens_str_append_delimiter (combined, strict, FALSE);
+	g_string_append (combined, nm_utils_escaped_tokens_options_escape_key (key, &escaped_key));
+	if (val) {
+		char *s;
+
+		if (!strict) {
+			g_string_append (combined, (s = _escaped_tokens_create_random_whitespace ()));
+			nm_clear_g_free (&s);
+		}
+		g_string_append_c (combined, '=');
+		if (!strict) {
+			g_string_append (combined, (s = _escaped_tokens_create_random_whitespace ()));
+			nm_clear_g_free (&s);
+		}
+		g_string_append (combined, nm_utils_escaped_tokens_options_escape_val (val, &escaped_val));
+	}
+	_escaped_tokens_str_append_delimiter (combined, strict, needs_delimiter_after);
+}
+
+static void
+_escaped_tokens_check_one_impl (const char *expected_key,
+                                const char *expected_val,
+                                const char *expected_combination,
+                                const char *const*other,
+                                gsize n_other)
+{
+	nm_auto_free_gstring GString *combined = g_string_new (NULL);
+	gsize i;
+
+	g_assert (expected_key);
+	g_assert (expected_combination);
+	g_assert (other);
+
+	_escaped_tokens_combine (combined,
+	                         expected_key,
+	                         expected_val,
+	                         TRUE,
+	                         TRUE,
+	                         FALSE);
+
+	g_assert_cmpstr (combined->str, ==, expected_combination);
+
+	for (i = 0; i < n_other + 2u; i++) {
+		nm_auto_free_gstring GString *str0 = NULL;
+		gs_free const char **strv_split = NULL;
+		gs_free char *strv_split0 = NULL;
+		const char *comb;
+		const char *key;
+		const char *val;
+
+		if (i == 0)
+			comb = expected_combination;
+		else if (i == 1) {
+			_escaped_tokens_combine (nm_gstring_prepare (&str0),
+			                         expected_key,
+			                         expected_val,
+			                         FALSE,
+			                         TRUE,
+			                         FALSE);
+			comb = str0->str;
+		} else
+			comb = other[i - 2];
+
+		strv_split = nm_utils_escaped_tokens_options_split_list (comb);
+		if (!strv_split) {
+			g_assert_cmpstr (expected_key, ==, "");
+			g_assert_cmpstr (expected_val, ==, NULL);
+			continue;
+		}
+		g_assert (expected_val || expected_key[0]);
+
+		g_assert_cmpuint (NM_PTRARRAY_LEN (strv_split), ==, 1u);
+
+		strv_split0 = g_strdup (strv_split[0]);
+
+		_escaped_tokens_split (strv_split0, &key, &val);
+		g_assert_cmpstr (key, ==, expected_key);
+		g_assert_cmpstr (val, ==, expected_val);
+	}
+}
+
+#define _escaped_tokens_check_one(expected_key, expected_val, expected_combination, ...) \
+	_escaped_tokens_check_one_impl (expected_key, expected_val, expected_combination, NM_MAKE_STRV (__VA_ARGS__), NM_NARG (__VA_ARGS__))
+
+static void
+test_nm_utils_escaped_tokens (void)
+{
+	int i_run;
+
+	for (i_run = 0; i_run < 1000; i_run++) {
+		const guint num_options = nmtst_get_rand_word_length (NULL);
+		gs_unref_ptrarray GPtrArray *options = g_ptr_array_new_with_free_func (g_free);
+		nm_auto_free_gstring GString *combined = g_string_new (NULL);
+		gs_free const char **strv_split = NULL;
+		guint i_option;
+		guint i;
+
+		/* Generate a list of random words for option key-value pairs. */
+		for (i_option = 0; i_option < 2u * num_options; i_option++) {
+			char *word = NULL;
+
+			if (   i_option % 2u == 1
+			    && nmtst_get_rand_uint32 () % 5 == 0
+			    && strlen (options->pdata[options->len - 1]) > 0u) {
+				/* For some options, leave the value unset and only generate a key.
+				 *
+				 * If key is "", then we cannot do that, because the test below would try
+				 * to append "" to the combined list, which the parser then would drop.
+				 * Only test omitting the value, if strlen() of the key is positive. */
+			} else
+				word = _escaped_tokens_create_random_word ();
+			g_ptr_array_add (options, word);
+		}
+
+		/* Combine the options in one comma separated list, with proper escaping. */
+		for (i_option = 0; i_option < num_options; i_option++) {
+			_escaped_tokens_combine (combined,
+			                         options->pdata[2u*i_option + 0u],
+			                         options->pdata[2u*i_option + 1u],
+			                         FALSE,
+			                         i_option == 0,
+			                         i_option != num_options - 1);
+		}
+
+		/* ensure that we can split and parse the options without difference. */
+		strv_split = nm_utils_escaped_tokens_options_split_list (combined->str);
+		for (i_option = 0; i_option < num_options; i_option++) {
+			const char *expected_key = options->pdata[2u*i_option + 0u];
+			const char *expected_val = options->pdata[2u*i_option + 1u];
+			gs_free char *s_split = i_option < NM_PTRARRAY_LEN (strv_split) ? g_strdup (strv_split[i_option]) : NULL;
+			const char *key = NULL;
+			const char *val = NULL;
+
+			if (s_split)
+				_escaped_tokens_split (s_split, &key, &val);
+
+			if (   !nm_streq0 (key, expected_key)
+			    || !nm_streq0 (val, expected_val)) {
+				g_print (">>> ASSERTION IS ABOUT TO FAIL for item %5d of %5d\n", i_option, num_options);
+				g_print (">>> combined =  \"%s\"\n", combined->str);
+				g_print (">>> %c   parsed[%5d].key = \"%s\"\n", nm_streq (key, expected_key) ? ' ' : 'X', i_option, key);
+				g_print (">>> %c   parsed[%5d].val = %s%s%s\n", nm_streq0 (val, expected_val) ? ' ' : 'X', i_option, NM_PRINT_FMT_QUOTE_STRING (val));
+				for (i = 0; i < num_options; i++) {
+					g_print (">>> %c original[%5d].key = \"%s\"\n", i == i_option ? '*' : ' ', i, (char *) options->pdata[2u*i + 0u]);
+					g_print (">>> %c original[%5d].val = %s%s%s\n", i == i_option ? '*' : ' ', i, NM_PRINT_FMT_QUOTE_STRING ((char *) options->pdata[2u*i + 1u]));
+				}
+				for (i = 0; i < NM_PTRARRAY_LEN (strv_split); i++)
+					g_print (">>>      split[%5d]     = \"%s\"\n", i, strv_split[i]);
+			}
+
+			g_assert_cmpstr (key, ==, expected_key);
+			g_assert_cmpstr (val, ==, expected_val);
+		}
+		g_assert_cmpint (NM_PTRARRAY_LEN (strv_split), ==, num_options);
+
+		/* Above we show a full round-trip of random option key-value pairs, that they can
+		 * without loss escape, concatenate, split-list, and split. This proofed that every
+		 * option key-value pair can be represented as a combined string and parsed back.
+		 *
+		 * Now, just check that we can also parse arbitrary random words in nm_utils_escaped_tokens_options_split().
+		 * split() is a non-injective surjective function. As we check the round-trip above for random words, where
+		 * options-split() is the last step, we show that every random word can be the output of the function
+		 * (which shows, the surjective part).
+		 *
+		 * But multiple random input arguments, may map to the same output argument (non-injective).
+		 * Just test whether we can handle random input words without crashing. For that, just use the
+		 * above generate list of random words.
+		 */
+		for (i = 0; i < 1u + 2u * i_option; i++) {
+			gs_free char *str = NULL;
+			const char *cstr;
+
+			if (i == 0)
+				cstr = combined->str;
+			else
+				cstr = options->pdata[i - 1u];
+			if (!cstr)
+				continue;
+
+			str = g_strdup (cstr);
+			_escaped_tokens_split (str, NULL, NULL);
+		}
+	}
+
+	_escaped_tokens_check_one ("", NULL, "");
+	_escaped_tokens_check_one ("", "", "=", " =");
+	_escaped_tokens_check_one ("a", "b", "a=b", "a = b");
+	_escaped_tokens_check_one ("a\\=", "b\\=", "a\\\\\\==b\\\\=", "a\\\\\\==b\\\\\\=");
+	_escaped_tokens_check_one ("\\=", "\\=", "\\\\\\==\\\\=", "\\\\\\==\\\\\\=");
+	_escaped_tokens_check_one (" ", "bb=", "\\ =bb=", "\\ =bb\\=");
+	_escaped_tokens_check_one (" ", "bb\\=", "\\ =bb\\\\=", "\\ =bb\\\\\\=");
+	_escaped_tokens_check_one ("a b", "a  b", "a b=a  b");
+	_escaped_tokens_check_one ("a b", "a  b", "a b=a  b");
+	_escaped_tokens_check_one ("a = b", "a = b", "a \\= b=a = b", "a \\= b=a \\= b");
+}
+
+/*****************************************************************************/
+
 typedef struct {
 	int val;
 	CList lst;
@@ -1173,13 +1499,11 @@ test_setting_vpn_items (void)
 	nm_setting_vpn_add_data_item (s_vpn, "", "");
 	g_test_assert_expected_messages ();
 
-	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (item && item[0]));
-	nm_setting_vpn_add_data_item (s_vpn, "foobar1", NULL);
-	g_test_assert_expected_messages ();
-
-	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (item && item[0]));
 	nm_setting_vpn_add_data_item (s_vpn, "foobar1", "");
-	g_test_assert_expected_messages ();
+	g_assert_cmpstr (nm_setting_vpn_get_data_item (s_vpn, "foobar1"), ==, "");
+
+	nm_setting_vpn_add_data_item (s_vpn, "foobar1", NULL);
+	g_assert_cmpstr (nm_setting_vpn_get_data_item (s_vpn, "foobar1"), ==, NULL);
 
 	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0]));
 	nm_setting_vpn_add_data_item (s_vpn, NULL, "blahblah1");
@@ -1200,13 +1524,9 @@ test_setting_vpn_items (void)
 	nm_setting_vpn_add_secret (s_vpn, "", "");
 	g_test_assert_expected_messages ();
 
-	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (secret && secret[0]));
-	nm_setting_vpn_add_secret (s_vpn, "foobar1", NULL);
-	g_test_assert_expected_messages ();
-
-	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (secret && secret[0]));
 	nm_setting_vpn_add_secret (s_vpn, "foobar1", "");
-	g_test_assert_expected_messages ();
+
+	nm_setting_vpn_add_secret (s_vpn, "foobar1", NULL);
 
 	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (key && key[0]));
 	nm_setting_vpn_add_secret (s_vpn, NULL, "blahblah1");
@@ -3494,14 +3814,14 @@ test_setting_compare_addresses (void)
 	nm_ip_address_unref (a);
 
 	if (nmtst_get_rand_uint32 () % 2)
-		NMTST_SWAP (s1, s2);
+		NM_SWAP (s1, s2);
 
 	success = nm_setting_compare (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT);
 	g_assert (!success);
 
 	success = nm_setting_diff (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT, FALSE, &result);
 	g_assert (!success);
-	g_clear_pointer (&result, g_hash_table_unref);
+	nm_clear_pointer (&result, g_hash_table_unref);
 }
 
 static void
@@ -3526,14 +3846,14 @@ test_setting_compare_routes (void)
 	nm_ip_route_unref (r);
 
 	if (nmtst_get_rand_uint32 () % 2)
-		NMTST_SWAP (s1, s2);
+		NM_SWAP (s1, s2);
 
 	success = nm_setting_compare (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT);
 	g_assert (!success);
 
 	success = nm_setting_diff (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT, FALSE, &result);
 	g_assert (!success);
-	g_clear_pointer (&result, g_hash_table_unref);
+	nm_clear_pointer (&result, g_hash_table_unref);
 }
 
 static void
@@ -3551,7 +3871,7 @@ test_setting_compare_wired_cloned_mac_address (void)
 	g_assert_cmpstr ("stable", ==, nm_setting_wired_get_cloned_mac_address ((NMSettingWired *) old));
 	g_object_get (old, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, &str1, NULL);
 	g_assert_cmpstr ("stable", ==, str1);
-	g_clear_pointer (&str1, g_free);
+	nm_clear_g_free (&str1);
 
 	new = nm_setting_duplicate (old);
 	g_object_set (new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, "11:22:33:44:55:66", NULL);
@@ -3559,7 +3879,7 @@ test_setting_compare_wired_cloned_mac_address (void)
 	g_assert_cmpstr ("11:22:33:44:55:66", ==, nm_setting_wired_get_cloned_mac_address ((NMSettingWired *) new));
 	g_object_get (new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, &str1, NULL);
 	g_assert_cmpstr ("11:22:33:44:55:66", ==, str1);
-	g_clear_pointer (&str1, g_free);
+	nm_clear_g_free (&str1);
 
 	success = nm_setting_compare (old, new, NM_SETTING_COMPARE_FLAG_EXACT);
 	g_assert (!success);
@@ -3571,7 +3891,7 @@ test_setting_compare_wired_cloned_mac_address (void)
 	g_assert_cmpstr ("stable-bia", ==, nm_setting_wired_get_cloned_mac_address ((NMSettingWired *) new));
 	g_object_get (new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, &str1, NULL);
 	g_assert_cmpstr ("stable-bia", ==, str1);
-	g_clear_pointer (&str1, g_free);
+	nm_clear_g_free (&str1);
 
 	success = nm_setting_compare (old, new, NM_SETTING_COMPARE_FLAG_EXACT);
 	g_assert (!success);
@@ -3593,7 +3913,7 @@ test_setting_compare_wireless_cloned_mac_address (void)
 	g_assert_cmpstr ("stable", ==, nm_setting_wireless_get_cloned_mac_address ((NMSettingWireless *) old));
 	g_object_get (old, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, &str1, NULL);
 	g_assert_cmpstr ("stable", ==, str1);
-	g_clear_pointer (&str1, g_free);
+	nm_clear_g_free (&str1);
 
 	new = nm_setting_duplicate (old);
 	g_object_set (new, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, "11:22:33:44:55:66", NULL);
@@ -3601,7 +3921,7 @@ test_setting_compare_wireless_cloned_mac_address (void)
 	g_assert_cmpstr ("11:22:33:44:55:66", ==, nm_setting_wireless_get_cloned_mac_address ((NMSettingWireless *) new));
 	g_object_get (new, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, &str1, NULL);
 	g_assert_cmpstr ("11:22:33:44:55:66", ==, str1);
-	g_clear_pointer (&str1, g_free);
+	nm_clear_g_free (&str1);
 
 	success = nm_setting_compare (old, new, NM_SETTING_COMPARE_FLAG_EXACT);
 	g_assert (!success);
@@ -4648,7 +4968,7 @@ test_connection_normalize_virtual_iface_name (void)
 	g_variant_unref (setting_dict);
 	g_variant_unref (var);
 
-	/* If vlan.interface-name is invalid, deserialization will fail. */
+	/* If vlan.interface-name will be ignored. */
 	NMTST_VARIANT_EDITOR (connection_dict,
 	                      NMTST_VARIANT_CHANGE_PROPERTY (NM_SETTING_VLAN_SETTING_NAME,
 	                                                     "interface-name",
@@ -4657,8 +4977,9 @@ test_connection_normalize_virtual_iface_name (void)
 	                      );
 
 	con = _connection_new_from_dbus (connection_dict, &error);
-	g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY);
-	g_clear_error (&error);
+	nmtst_assert_success (con, error);
+	g_assert_cmpstr (nm_connection_get_interface_name (con), ==, IFACE_NAME);
+	g_clear_object (&con);
 
 	/* If vlan.interface-name is valid, but doesn't match, it will be ignored. */
 	NMTST_VARIANT_EDITOR (connection_dict,
@@ -6820,7 +7141,7 @@ _team_config_equal_check (const char *conf1,
 	gboolean is_same;
 
 	if (nmtst_get_rand_bool ())
-		NMTST_SWAP (conf1, conf2);
+		NM_SWAP (conf1, conf2);
 
 	if (!nm_streq0 (conf1, conf2)) {
 		_team_config_equal_check (conf1, conf1, port_config, TRUE);
@@ -7545,10 +7866,11 @@ _do_test_utils_str_utf8safe (const char *str, gsize str_len, const char *expecte
 	gs_free char *str_free_7 = NULL;
 	gs_free char *str_free_8 = NULL;
 	gboolean str_has_nul = FALSE;
+#define RND_FLAG ((nmtst_get_rand_bool ()) ? NM_UTILS_STR_UTF8_SAFE_FLAG_NONE : NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET)
 
-	buf_safe = nm_utils_buf_utf8safe_escape (str, str_len, flags, &str_free_1);
+	buf_safe = nm_utils_buf_utf8safe_escape (str, str_len, flags | RND_FLAG, &str_free_1);
 
-	str_safe = nm_utils_str_utf8safe_escape (str, flags, &str_free_2);
+	str_safe = nm_utils_str_utf8safe_escape (str, flags | RND_FLAG, &str_free_2);
 
 	if (str_len == 0) {
 		g_assert (buf_safe == NULL);
@@ -7568,7 +7890,7 @@ _do_test_utils_str_utf8safe (const char *str, gsize str_len, const char *expecte
 	} else
 		str_has_nul = TRUE;
 
-	str_free_3 = nm_utils_str_utf8safe_escape_cp (str, flags);
+	str_free_3 = nm_utils_str_utf8safe_escape_cp (str, flags | RND_FLAG);
 	g_assert_cmpstr (str_free_3, ==, str_safe);
 	g_assert ((!str && !str_free_3) || (str != str_free_3));
 
@@ -8201,8 +8523,12 @@ _test_integrate_cb_idle_2 (gpointer user_data)
 	g_assert (d->extra_sources[0]);
 	g_assert (!d->extra_sources[1]);
 
-	extra_source = g_unix_fd_source_new (d->fd_2, G_IO_IN);
-	g_source_set_callback (extra_source, G_SOURCE_FUNC (_test_integrate_cb_fd_2), d, NULL);
+	extra_source = nm_g_unix_fd_source_new (d->fd_2,
+	                                        G_IO_IN,
+	                                        G_PRIORITY_DEFAULT,
+	                                        _test_integrate_cb_fd_2,
+	                                        d,
+	                                        NULL);
 	g_source_attach (extra_source, d->c2);
 
 	d->extra_sources[1] = extra_source;
@@ -8269,7 +8595,7 @@ test_integrate_maincontext (gconstpointer test_data)
 		g_source_set_callback (idle_source_1, _test_integrate_maincontext_cb_idle1, &count, NULL);
 		g_source_attach (idle_source_1, c2);
 
-		nmtst_main_context_iterate_until (c1, 2000, count == 5);
+		nmtst_main_context_iterate_until_assert (c1, 2000, count == 5);
 	}
 
 	if (TEST_IDX == 2) {
@@ -8296,8 +8622,12 @@ test_integrate_maincontext (gconstpointer test_data)
 
 		fd_1 = open ("/dev/null", O_RDONLY | O_CLOEXEC);
 		g_assert (fd_1 >= 0);
-		fd_source_1 = g_unix_fd_source_new (fd_1, G_IO_IN);
-		g_source_set_callback (fd_source_1, G_SOURCE_FUNC (_test_integrate_cb_fd_1), &d, NULL);
+		fd_source_1 = nm_g_unix_fd_source_new (fd_1,
+		                                       G_IO_IN,
+		                                       G_PRIORITY_DEFAULT,
+		                                       _test_integrate_cb_fd_1,
+		                                       &d,
+		                                       NULL);
 		g_source_attach (fd_source_1, c2);
 
 		fd_2 = open ("/dev/null", O_RDONLY | O_CLOEXEC);
@@ -8321,6 +8651,33 @@ test_integrate_maincontext (gconstpointer test_data)
 	}
 }
 
+/*****************************************************************************/
+
+static void
+test_nm_ip_addr_zero (void)
+{
+	in_addr_t a4 = nmtst_inet4_from_string ("0.0.0.0");
+	struct in6_addr a6 = *nmtst_inet6_from_string ("::");
+	char buf[NM_UTILS_INET_ADDRSTRLEN];
+	NMIPAddr a = NM_IP_ADDR_INIT;
+
+	g_assert (memcmp (&a, &nm_ip_addr_zero, sizeof (a)) == 0);
+
+	g_assert (IN6_IS_ADDR_UNSPECIFIED (&nm_ip_addr_zero.addr6));
+	g_assert (memcmp (&nm_ip_addr_zero.addr6, &in6addr_any, sizeof (in6addr_any)) == 0);
+
+	g_assert (memcmp (&nm_ip_addr_zero, &a4, sizeof (a4)) == 0);
+	g_assert (memcmp (&nm_ip_addr_zero, &a6, sizeof (a6)) == 0);
+
+	g_assert_cmpstr (_nm_utils_inet4_ntop (nm_ip_addr_zero.addr4, buf), ==, "0.0.0.0");
+	g_assert_cmpstr (_nm_utils_inet6_ntop (&nm_ip_addr_zero.addr6, buf), ==, "::");
+
+	g_assert_cmpstr (nm_utils_inet_ntop (AF_INET, &nm_ip_addr_zero, buf), ==, "0.0.0.0");
+	g_assert_cmpstr (nm_utils_inet_ntop (AF_INET6, &nm_ip_addr_zero, buf), ==, "::");
+
+	G_STATIC_ASSERT_EXPR (sizeof (a) == sizeof (a.array));
+}
+
 static void
 test_connection_ovs_ifname (gconstpointer test_data)
 {
@@ -8523,6 +8880,7 @@ int main (int argc, char **argv)
 	g_test_add_func ("/core/general/test_dedup_multi", test_dedup_multi);
 	g_test_add_func ("/core/general/test_utils_str_utf8safe", test_utils_str_utf8safe);
 	g_test_add_func ("/core/general/test_nm_utils_strsplit_set", test_nm_utils_strsplit_set);
+	g_test_add_func ("/core/general/test_nm_utils_escaped_tokens", test_nm_utils_escaped_tokens);
 	g_test_add_func ("/core/general/test_nm_in_set", test_nm_in_set);
 	g_test_add_func ("/core/general/test_nm_in_strset", test_nm_in_strset);
 	g_test_add_func ("/core/general/test_setting_vpn_items", test_setting_vpn_items);
@@ -8681,5 +9039,7 @@ int main (int argc, char **argv)
 	g_test_add_data_func ("/core/general/test_integrate_maincontext/1", GUINT_TO_POINTER (1), test_integrate_maincontext);
 	g_test_add_data_func ("/core/general/test_integrate_maincontext/2", GUINT_TO_POINTER (2), test_integrate_maincontext);
 
+	g_test_add_func ("/core/general/test_nm_ip_addr_zero", test_nm_ip_addr_zero);
+
 	return g_test_run ();
 }
diff --git a/libnm-core/tests/test-keyfile.c b/libnm-core/tests/test-keyfile.c
index 574d671f..47a27571 100644
--- a/libnm-core/tests/test-keyfile.c
+++ b/libnm-core/tests/test-keyfile.c
@@ -5,8 +5,8 @@
 
 #include "nm-default.h"
 
-#include "nm-keyfile-utils.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-utils.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 #include "nm-simple-connection.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-wired.h"
@@ -99,7 +99,7 @@ test_encode_key (void)
 		GKeyFile **_keyfile = (keyfile); \
 		\
 		g_clear_object (_con); \
-		g_clear_pointer (_keyfile, g_key_file_unref); \
+		nm_clear_pointer (_keyfile, g_key_file_unref); \
 	} G_STMT_END
 
 static void
diff --git a/libnm-core/tests/test-setting.c b/libnm-core/tests/test-setting.c
index 5b2c7077..cb8f41fb 100644
--- a/libnm-core/tests/test-setting.c
+++ b/libnm-core/tests/test-setting.c
@@ -24,7 +24,7 @@
 #include "nm-simple-connection.h"
 #include "nm-setting-connection.h"
 #include "nm-errors.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 
 #include "nm-utils/nm-test-utils.h"
 
@@ -500,8 +500,6 @@ create_bond_connection (NMConnection **con, NMSettingBond **s_bond)
 	                                        NULL,
 	                                        NM_SETTING_BOND_SETTING_NAME,
 	                                        &s_con);
-	g_assert (*con);
-	g_assert (s_con);
 
 	g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "bond0", NULL);
 
@@ -512,28 +510,25 @@ create_bond_connection (NMConnection **con, NMSettingBond **s_bond)
 }
 
 #define test_verify_options(exp, ...) \
-	_test_verify_options (NM_MAKE_STRV (__VA_ARGS__), exp)
+	_test_verify_options (exp, NM_MAKE_STRV (__VA_ARGS__))
 
 static void
-_test_verify_options (const char *const *options,
-                      gboolean expected_result)
+_test_verify_options (gboolean expected_result,
+                      const char *const *options)
 {
 	gs_unref_object NMConnection *con = NULL;
 	NMSettingBond *s_bond;
-	GError *error = NULL;
-	gboolean success;
 	const char *const *option;
 
+	g_assert (NM_PTRARRAY_LEN (options) % 2 == 0);
+
 	create_bond_connection (&con, &s_bond);
 
-	for (option = options; option[0] && option[1]; option += 2)
+	for (option = options; option[0]; option += 2)
 		g_assert (nm_setting_bond_add_option (s_bond, option[0], option[1]));
 
 	if (expected_result) {
 		nmtst_assert_connection_verifies_and_normalizable (con);
-		nmtst_connection_normalize (con);
-		success = nm_setting_verify ((NMSetting *) s_bond, con, &error);
-		nmtst_assert_success (success, error);
 	} else {
 		nmtst_assert_connection_unnormalizable (con,
 		                                        NM_CONNECTION_ERROR,
@@ -595,6 +590,16 @@ test_bond_verify (void)
 	                     "mode", "0",
 	                     "downdelay", "0",
 	                     "updelay", "0");
+	test_verify_options (TRUE,
+	                     "mode", "0",
+	                     "miimon", "100",
+	                     "arp_ip_target", "1.1.1.1",
+	                     "arp_interval", "200");
+	test_verify_options (TRUE,
+	                     "mode", "0",
+	                     "downdelay", "100",
+	                     "arp_ip_target", "1.1.1.1",
+	                     "arp_interval", "200");
 }
 
 static void
@@ -631,21 +636,23 @@ test_bond_compare (void)
 	                           ((const char *[]){ "mode", "balance-rr", "miimon", "1", NULL }),
 	                           ((const char *[]){ "mode", "balance-rr", "miimon", "2", NULL }));
 
-	/* ignore default values */
-	test_bond_compare_options (TRUE,
+	test_bond_compare_options (FALSE,
 	                           ((const char *[]){ "miimon", "1", NULL }),
 	                           ((const char *[]){ "miimon", "1", "updelay", "0", NULL }));
 
-	/* special handling of num_grat_arp, num_unsol_na */
 	test_bond_compare_options (FALSE,
 	                           ((const char *[]){ "num_grat_arp", "2", NULL }),
 	                           ((const char *[]){ "num_grat_arp", "1", NULL }));
-	test_bond_compare_options (TRUE,
+	test_bond_compare_options (FALSE,
 	                           ((const char *[]){ "num_grat_arp", "3", NULL }),
 	                           ((const char *[]){ "num_unsol_na", "3", NULL }));
-	test_bond_compare_options (TRUE,
+	test_bond_compare_options (FALSE,
 	                           ((const char *[]){ "num_grat_arp", "4", NULL }),
 	                           ((const char *[]){ "num_unsol_na", "4", "num_grat_arp", "4", NULL }));
+
+	test_bond_compare_options (FALSE,
+	                           ((const char *[]){ "mode", "balance-rr", "miimon", "100", NULL }),
+	                           ((const char *[]){ "mode", "balance-rr", NULL }));
 }
 
 static void
@@ -1840,7 +1847,7 @@ test_bridge_vlans (void)
 	str = nm_bridge_vlan_to_str (v1, &error);
 	nmtst_assert_success (str, error);
 	g_assert_cmpstr (str, ==, "10 pvid");
-	g_clear_pointer (&str, g_free);
+	nm_clear_g_free (&str);
 
 	v2 = nm_bridge_vlan_from_str ("  10  pvid  ", &error);
 	nmtst_assert_success (v2, error);
@@ -1862,6 +1869,137 @@ test_bridge_vlans (void)
 	nm_bridge_vlan_unref (v2);
 }
 
+static void
+create_bridge_connection (NMConnection **con, NMSettingBridge **s_bridge)
+{
+	NMSettingConnection *s_con;
+
+	g_assert (con);
+	g_assert (s_bridge);
+
+	*con = nmtst_create_minimal_connection ("bridge",
+	                                        NULL,
+	                                        NM_SETTING_BOND_SETTING_NAME,
+	                                        &s_con);
+
+	g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "bridge0", NULL);
+
+	*s_bridge = (NMSettingBridge *) nm_setting_bridge_new ();
+	g_assert (*s_bridge);
+
+	nm_connection_add_setting (*con, NM_SETTING (*s_bridge));
+}
+
+#define test_verify_options_bridge(exp, ...) \
+	_test_verify_options_bridge (exp, NM_MAKE_STRV (__VA_ARGS__))
+
+static void
+_test_verify_options_bridge (gboolean expected_result,
+                             const char *const *options)
+{
+	gs_unref_object NMConnection *con = NULL;
+	NMSettingBridge *s_bridge;
+	const char *const *option;
+
+	g_assert (NM_PTRARRAY_LEN (options) % 2 == 0);
+
+	create_bridge_connection (&con, &s_bridge);
+
+	for (option = options; option[0]; option += 2) {
+		const char *option_key = option[0];
+		const char *option_val = option[1];
+		GParamSpec *pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (s_bridge), option_key);
+
+		g_assert (pspec);
+		g_assert (option_val);
+
+		switch (G_PARAM_SPEC_VALUE_TYPE (pspec)) {
+		case G_TYPE_UINT: {
+				guint uvalue;
+
+				uvalue = _nm_utils_ascii_str_to_uint64 (option_val, 10, 0, G_MAXUINT, 0);
+				g_object_set (s_bridge, option_key, uvalue, NULL);
+			}
+			break;
+		case G_TYPE_BOOLEAN: {
+				gboolean bvalue;
+
+				bvalue = _nm_utils_ascii_str_to_bool (option_val, FALSE);
+				g_object_set (s_bridge, option_key, bvalue, NULL);
+			}
+			break;
+		case G_TYPE_STRING:
+			g_object_set (s_bridge, option_key, option_val, NULL);
+			break;
+		default:
+			g_assert_not_reached();
+			break;
+		}
+	}
+
+	if (expected_result)
+		nmtst_assert_connection_verifies_and_normalizable (con);
+	else {
+		nmtst_assert_connection_unnormalizable (con,
+		                                        NM_CONNECTION_ERROR,
+		                                        NM_CONNECTION_ERROR_INVALID_PROPERTY);
+	}
+}
+
+static void
+test_bridge_verify (void)
+{
+	/* group-address */
+	test_verify_options_bridge (FALSE,
+	                            "group-address", "nonsense");
+	test_verify_options_bridge (FALSE,
+	                            "group-address", "FF:FF:FF:FF:FF:FF");
+	test_verify_options_bridge (FALSE,
+	                            "group-address", "01:02:03:04:05:06");
+	test_verify_options_bridge (TRUE,
+	                            "group-address", "01:80:C2:00:00:00");
+	test_verify_options_bridge (FALSE,
+	                            "group-address", "01:80:C2:00:00:02");
+	test_verify_options_bridge (FALSE,
+	                            "group-address", "01:80:C2:00:00:03");
+	test_verify_options_bridge (TRUE,
+	                            "group-address", "01:80:C2:00:00:00");
+	test_verify_options_bridge (TRUE,
+	                            "group-address", "01:80:C2:00:00:0A");
+	/* vlan-protocol */
+	test_verify_options_bridge (FALSE,
+	                            "vlan-protocol", "nonsense124");
+	test_verify_options_bridge (FALSE,
+	                            "vlan-protocol", "802.11");
+	test_verify_options_bridge (FALSE,
+	                            "vlan-protocol", "802.1Q1");
+	test_verify_options_bridge (TRUE,
+	                            "vlan-protocol", "802.1Q");
+	test_verify_options_bridge (TRUE,
+	                            "vlan-protocol", "802.1ad");
+	/* multicast-router */
+	test_verify_options_bridge (FALSE,
+	                            "multicast-router",   "nonsense");
+	test_verify_options_bridge (FALSE,
+	                            "multicast-snooping", "no",
+	                            "multicast-router",   "auto");
+	test_verify_options_bridge (FALSE,
+	                            "multicast-snooping", "no",
+	                            "multicast-router",   "enabled");
+	test_verify_options_bridge (TRUE,
+	                            "multicast-snooping", "no",
+	                            "multicast-router",   "disabled");
+	test_verify_options_bridge (TRUE,
+	                            "multicast-snooping", "yes",
+	                            "multicast-router",   "enabled");
+	test_verify_options_bridge (TRUE,
+	                            "multicast-snooping", "yes",
+	                            "multicast-router",   "auto");
+	test_verify_options_bridge (TRUE,
+	                            "multicast-snooping", "yes",
+	                            "multicast-router",   "disabled");
+}
+
 /*****************************************************************************/
 
 static void
@@ -3641,6 +3779,7 @@ main (int argc, char **argv)
 	g_test_add_func ("/libnm/settings/tc_config/dbus", test_tc_config_dbus);
 
 	g_test_add_func ("/libnm/settings/bridge/vlans", test_bridge_vlans);
+	g_test_add_func ("/libnm/settings/bridge/verify", test_bridge_verify);
 
 	g_test_add_func ("/libnm/settings/team/sync_runner_from_config_roundrobin",
 	                 test_runner_roundrobin_sync_from_config);