about summary refs log tree commit diff
path: root/libnm-core
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2019-07-31 10:51:42 +0200
committerMichael Biebl <biebl@debian.org>2019-07-31 10:51:42 +0200
commit2e5fa45ddfbb5cffa1e78221f1cea706e2f298af (patch)
tree86f69d36c56de3074280456eddc854a780b8e04b /libnm-core
parent85563b7fc7ec2cd21e38debb9b28db342e2e8e7c (diff)
New upstream version 1.19.90 upstream/1.19.90
Diffstat (limited to 'libnm-core')
-rw-r--r--libnm-core/meson.build10
-rw-r--r--libnm-core/nm-connection-private.h1
-rw-r--r--libnm-core/nm-connection.c401
-rw-r--r--libnm-core/nm-connection.h11
-rw-r--r--libnm-core/nm-core-enum-types.c.template1
-rw-r--r--libnm-core/nm-core-internal.h171
-rw-r--r--libnm-core/nm-core-types-internal.h2
-rw-r--r--libnm-core/nm-core-types.h2
-rw-r--r--libnm-core/nm-crypto-gnutls.c1
-rw-r--r--libnm-core/nm-crypto-impl.h2
-rw-r--r--libnm-core/nm-crypto-nss.c2
-rw-r--r--libnm-core/nm-crypto.c73
-rw-r--r--libnm-core/nm-crypto.h2
-rw-r--r--libnm-core/nm-dbus-interface.h72
-rw-r--r--libnm-core/nm-dbus-types.xml63
-rw-r--r--libnm-core/nm-dbus-utils.c1
-rw-r--r--libnm-core/nm-errors.c26
-rw-r--r--libnm-core/nm-errors.h1
-rw-r--r--libnm-core/nm-keyfile-internal.h16
-rw-r--r--libnm-core/nm-keyfile-utils.c40
-rw-r--r--libnm-core/nm-keyfile-utils.h1
-rw-r--r--libnm-core/nm-keyfile.c148
-rw-r--r--libnm-core/nm-property-compare.c2
-rw-r--r--libnm-core/nm-property-compare.h2
-rw-r--r--libnm-core/nm-setting-8021x.c6
-rw-r--r--libnm-core/nm-setting-8021x.h2
-rw-r--r--libnm-core/nm-setting-adsl.c2
-rw-r--r--libnm-core/nm-setting-adsl.h2
-rw-r--r--libnm-core/nm-setting-bluetooth.c2
-rw-r--r--libnm-core/nm-setting-bluetooth.h2
-rw-r--r--libnm-core/nm-setting-bond.c25
-rw-r--r--libnm-core/nm-setting-bond.h2
-rw-r--r--libnm-core/nm-setting-bridge-port.c20
-rw-r--r--libnm-core/nm-setting-bridge-port.h2
-rw-r--r--libnm-core/nm-setting-bridge.c20
-rw-r--r--libnm-core/nm-setting-bridge.h2
-rw-r--r--libnm-core/nm-setting-cdma.c2
-rw-r--r--libnm-core/nm-setting-cdma.h2
-rw-r--r--libnm-core/nm-setting-connection.c113
-rw-r--r--libnm-core/nm-setting-connection.h6
-rw-r--r--libnm-core/nm-setting-dcb.c2
-rw-r--r--libnm-core/nm-setting-dcb.h2
-rw-r--r--libnm-core/nm-setting-dummy.c1
-rw-r--r--libnm-core/nm-setting-dummy.h2
-rw-r--r--libnm-core/nm-setting-ethtool.c44
-rw-r--r--libnm-core/nm-setting-ethtool.h7
-rw-r--r--libnm-core/nm-setting-generic.c2
-rw-r--r--libnm-core/nm-setting-generic.h2
-rw-r--r--libnm-core/nm-setting-gsm.c2
-rw-r--r--libnm-core/nm-setting-gsm.h2
-rw-r--r--libnm-core/nm-setting-infiniband.c2
-rw-r--r--libnm-core/nm-setting-infiniband.h2
-rw-r--r--libnm-core/nm-setting-ip-config.c314
-rw-r--r--libnm-core/nm-setting-ip-config.h7
-rw-r--r--libnm-core/nm-setting-ip-tunnel.c1
-rw-r--r--libnm-core/nm-setting-ip-tunnel.h1
-rw-r--r--libnm-core/nm-setting-ip4-config.c47
-rw-r--r--libnm-core/nm-setting-ip4-config.h2
-rw-r--r--libnm-core/nm-setting-ip6-config.c77
-rw-r--r--libnm-core/nm-setting-ip6-config.h9
-rw-r--r--libnm-core/nm-setting-macsec.c1
-rw-r--r--libnm-core/nm-setting-macsec.h2
-rw-r--r--libnm-core/nm-setting-macvlan.c2
-rw-r--r--libnm-core/nm-setting-macvlan.h2
-rw-r--r--libnm-core/nm-setting-match.c1
-rw-r--r--libnm-core/nm-setting-match.h1
-rw-r--r--libnm-core/nm-setting-olpc-mesh.c1
-rw-r--r--libnm-core/nm-setting-olpc-mesh.h1
-rw-r--r--libnm-core/nm-setting-ovs-bridge.c51
-rw-r--r--libnm-core/nm-setting-ovs-bridge.h3
-rw-r--r--libnm-core/nm-setting-ovs-dpdk.c172
-rw-r--r--libnm-core/nm-setting-ovs-dpdk.h54
-rw-r--r--libnm-core/nm-setting-ovs-interface.c44
-rw-r--r--libnm-core/nm-setting-ovs-patch.c1
-rw-r--r--libnm-core/nm-setting-ovs-port.c1
-rw-r--r--libnm-core/nm-setting-ppp.c2
-rw-r--r--libnm-core/nm-setting-ppp.h2
-rw-r--r--libnm-core/nm-setting-pppoe.c2
-rw-r--r--libnm-core/nm-setting-pppoe.h2
-rw-r--r--libnm-core/nm-setting-private.h21
-rw-r--r--libnm-core/nm-setting-proxy.c2
-rw-r--r--libnm-core/nm-setting-proxy.h2
-rw-r--r--libnm-core/nm-setting-serial.c2
-rw-r--r--libnm-core/nm-setting-serial.h2
-rw-r--r--libnm-core/nm-setting-sriov.c45
-rw-r--r--libnm-core/nm-setting-sriov.h1
-rw-r--r--libnm-core/nm-setting-tc-config.c56
-rw-r--r--libnm-core/nm-setting-team-port.c414
-rw-r--r--libnm-core/nm-setting-team-port.h1
-rw-r--r--libnm-core/nm-setting-team.c1084
-rw-r--r--libnm-core/nm-setting-team.h26
-rw-r--r--libnm-core/nm-setting-tun.c2
-rw-r--r--libnm-core/nm-setting-tun.h2
-rw-r--r--libnm-core/nm-setting-user.c20
-rw-r--r--libnm-core/nm-setting-user.h2
-rw-r--r--libnm-core/nm-setting-vlan.c11
-rw-r--r--libnm-core/nm-setting-vlan.h2
-rw-r--r--libnm-core/nm-setting-vpn.c15
-rw-r--r--libnm-core/nm-setting-vpn.h2
-rw-r--r--libnm-core/nm-setting-vxlan.c2
-rw-r--r--libnm-core/nm-setting-vxlan.h2
-rw-r--r--libnm-core/nm-setting-wimax.c2
-rw-r--r--libnm-core/nm-setting-wimax.h2
-rw-r--r--libnm-core/nm-setting-wired.c380
-rw-r--r--libnm-core/nm-setting-wired.h2
-rw-r--r--libnm-core/nm-setting-wireguard.c120
-rw-r--r--libnm-core/nm-setting-wireguard.h8
-rw-r--r--libnm-core/nm-setting-wireless-security.c36
-rw-r--r--libnm-core/nm-setting-wireless-security.h2
-rw-r--r--libnm-core/nm-setting-wireless.c190
-rw-r--r--libnm-core/nm-setting-wireless.h9
-rw-r--r--libnm-core/nm-setting.c559
-rw-r--r--libnm-core/nm-setting.h18
-rw-r--r--libnm-core/nm-simple-connection.c5
-rw-r--r--libnm-core/nm-simple-connection.h1
-rw-r--r--libnm-core/nm-team-utils.c2542
-rw-r--r--libnm-core/nm-team-utils.h307
-rw-r--r--libnm-core/nm-utils-private.h142
-rw-r--r--libnm-core/nm-utils.c1236
-rw-r--r--libnm-core/nm-utils.h1
-rw-r--r--libnm-core/nm-version.h15
-rw-r--r--libnm-core/nm-vpn-dbus-interface.h1
-rw-r--r--libnm-core/nm-vpn-editor-plugin.c1
-rw-r--r--libnm-core/nm-vpn-editor-plugin.h1
-rw-r--r--libnm-core/nm-vpn-plugin-info.c1
-rw-r--r--libnm-core/nm-vpn-plugin-info.h1
-rw-r--r--libnm-core/tests/certs/test-tpm2wrapped-key.pem14
-rw-r--r--libnm-core/tests/test-compare.c1
-rw-r--r--libnm-core/tests/test-crypto.c2
-rw-r--r--libnm-core/tests/test-general-enums.h1
-rw-r--r--libnm-core/tests/test-general.c110
-rw-r--r--libnm-core/tests/test-keyfile.c28
-rw-r--r--libnm-core/tests/test-secrets.c3
-rw-r--r--libnm-core/tests/test-setting.c462
-rw-r--r--libnm-core/tests/test-settings-defaults.c1
135 files changed, 6855 insertions, 3199 deletions
diff --git a/libnm-core/meson.build b/libnm-core/meson.build
index bc2d087a..9637afaa 100644
--- a/libnm-core/meson.build
+++ b/libnm-core/meson.build
@@ -30,6 +30,7 @@ libnm_core_headers = files(
   'nm-setting-olpc-mesh.h',
   'nm-setting-ovs-bridge.h',
   'nm-setting-ovs-interface.h',
+  'nm-setting-ovs-dpdk.h',
   'nm-setting-ovs-patch.h',
   'nm-setting-ovs-port.h',
   'nm-setting-ppp.h',
@@ -87,6 +88,7 @@ libnm_core_settings_sources = files(
   'nm-setting-olpc-mesh.c',
   'nm-setting-ovs-bridge.c',
   'nm-setting-ovs-interface.c',
+  'nm-setting-ovs-dpdk.c',
   'nm-setting-ovs-patch.c',
   'nm-setting-ovs-port.c',
   'nm-setting-ppp.c',
@@ -112,15 +114,16 @@ libnm_core_settings_sources = files(
 )
 
 libnm_core_sources = libnm_core_settings_sources + files(
-  'nm-crypto.c',
   'nm-connection.c',
+  'nm-crypto.c',
   'nm-dbus-utils.c',
   'nm-errors.c',
-  'nm-keyfile.c',
   'nm-keyfile-utils.c',
+  'nm-keyfile.c',
   'nm-property-compare.c',
   'nm-setting.c',
   'nm-simple-connection.c',
+  'nm-team-utils.c',
   'nm-utils.c',
   'nm-vpn-editor-plugin.c',
   'nm-vpn-plugin-info.c',
@@ -256,7 +259,7 @@ libnm_core_dep = declare_dependency(
 
 shared_nm_libnm_core_aux = static_library(
     'nm-libnm-core-aux',
-    sources: files('../shared/nm-libnm-core-aux/nm-dispatcher-api.h'),
+    sources: files('../shared/nm-libnm-core-aux/nm-libnm-core-aux.c'),
     c_args: [
         '-DG_LOG_DOMAIN="@0@"'.format(libnm_name),
         '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_WITH_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB)',
@@ -270,6 +273,7 @@ shared_nm_libnm_core_aux = static_library(
         glib_dep,
         shared_c_siphash_dep,
         shared_nm_glib_aux_dep,
+        libnm_core_dep,
     ],
 )
 
diff --git a/libnm-core/nm-connection-private.h b/libnm-core/nm-connection-private.h
index 3ff7c57d..6c747c76 100644
--- a/libnm-core/nm-connection-private.h
+++ b/libnm-core/nm-connection-private.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-connection.c b/libnm-core/nm-connection.c
index 3182e346..2532b64a 100644
--- a/libnm-core/nm-connection.c
+++ b/libnm-core/nm-connection.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -581,7 +579,8 @@ nm_connection_compare (NMConnection *a,
 	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &src)) {
 		NMSetting *cmp = nm_connection_get_setting (b, G_OBJECT_TYPE (src));
 
-		if (!cmp || !nm_setting_compare (src, cmp, flags))
+		if (   !cmp
+		    || !_nm_setting_compare (a, src, b, cmp, flags))
 			return FALSE;
 	}
 
@@ -614,7 +613,7 @@ diff_one_connection (NMConnection *a,
 		if (results)
 			new_results = FALSE;
 
-		if (!nm_setting_diff (a_setting, b_setting, flags, invert_results, &results))
+		if (!_nm_setting_diff (a, a_setting, b, b_setting, flags, invert_results, &results))
 			diff_found = TRUE;
 
 		if (new_results && results)
@@ -1024,8 +1023,9 @@ _normalize_ip_config (NMConnection *self, GHashTable *parameters)
 				changed = TRUE;
 			}
 
-			if (   nm_streq0 (nm_setting_ip_config_get_method (s_ip6),
-			                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE)
+			if (   NM_IN_STRSET (nm_setting_ip_config_get_method (s_ip6),
+			                     NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+			                     NM_SETTING_IP6_CONFIG_METHOD_DISABLED)
 			    && !nm_setting_ip_config_get_may_fail (s_ip6)) {
 				g_object_set (s_ip6, NM_SETTING_IP_CONFIG_MAY_FAIL, TRUE, NULL);
 				changed = TRUE;
@@ -1585,63 +1585,29 @@ nm_connection_verify_secrets (NMConnection *connection, GError **error)
 	return TRUE;
 }
 
-/**
- * nm_connection_normalize:
- * @connection: the #NMConnection to normalize
- * @parameters: (allow-none) (element-type utf8 gpointer): a #GHashTable with
- * normalization parameters to allow customization of the normalization by providing
- * specific arguments. Unknown arguments will be ignored and the default will be
- * used. The keys must be strings compared with g_str_equal() function.
- * The values are opaque and depend on the parameter name.
- * @modified: (out) (allow-none): outputs whether any settings were modified.
- * @error: location to store error, or %NULL. Contains the reason,
- * why the connection is invalid, if the function returns an error.
- *
- * Does some basic normalization and fixup of well known inconsistencies
- * and deprecated fields. If the connection was modified in any way,
- * the output parameter @modified is set %TRUE.
- *
- * Finally the connection will be verified and %TRUE returns if the connection
- * is valid. As this function only performs some specific normalization steps
- * it cannot repair all connections. If the connection has errors that
- * cannot be normalized, the connection will not be modified.
- *
- * Returns: %TRUE if the connection is valid, %FALSE if it is not
- **/
-gboolean
-nm_connection_normalize (NMConnection *connection,
-                         GHashTable *parameters,
-                         gboolean *modified,
-                         GError **error)
+static gboolean
+_connection_normalize (NMConnection *connection,
+                       GHashTable *parameters,
+                       gboolean *modified,
+                       GError **error)
 {
 	NMSettingVerifyResult success;
-	gboolean was_modified = FALSE;
-	GError *normalizable_error = NULL;
-
-	success = _nm_connection_verify (connection, &normalizable_error);
-
-	if (success == NM_SETTING_VERIFY_ERROR ||
-	    success == NM_SETTING_VERIFY_SUCCESS) {
-		if (normalizable_error)
-			g_propagate_error (error, normalizable_error);
-		if (modified)
-			*modified = FALSE;
-		if (success == NM_SETTING_VERIFY_ERROR && error && !*error) {
-			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     NM_CONNECTION_ERROR_FAILED,
-			                     _("Unexpected failure to verify the connection"));
-			g_return_val_if_reached (FALSE);
-		}
-		return success == NM_SETTING_VERIFY_SUCCESS;
-	}
-	g_assert (success == NM_SETTING_VERIFY_NORMALIZABLE || success == NM_SETTING_VERIFY_NORMALIZABLE_ERROR);
-	g_clear_error (&normalizable_error);
+	gboolean was_modified;
+
+#if NM_MORE_ASSERTS > 10
+	/* only call this _nm_connection_verify() confirms that the connection
+	 * requires normalization and is normalizable. */
+	nm_assert (NM_IN_SET (_nm_connection_verify (connection, NULL),
+	                      NM_SETTING_VERIFY_NORMALIZABLE,
+	                      NM_SETTING_VERIFY_NORMALIZABLE_ERROR));
+#endif
 
 	/* Try to perform all kind of normalizations on the settings to fix it.
 	 * We only do this, after verifying that the connection contains no un-normalizable
 	 * errors, because in that case we rather fail without touching the settings. */
 
+	was_modified = FALSE;
+
 	was_modified |= _normalize_connection_uuid (connection);
 	was_modified |= _normalize_connection_type (connection);
 	was_modified |= _normalize_connection_slave_type (connection);
@@ -1663,11 +1629,12 @@ nm_connection_normalize (NMConnection *connection,
 	was_modified |= _normalize_bridge_vlan_order (connection, parameters);
 	was_modified |= _normalize_bridge_port_vlan_order (connection, parameters);
 
-	/* Verify anew. */
+	was_modified = !!was_modified;
+
+	/* Verify anew */
 	success = _nm_connection_verify (connection, error);
 
-	if (modified)
-		*modified = was_modified;
+	NM_SET_OUT (modified, was_modified);
 
 	if (success != NM_SETTING_VERIFY_SUCCESS) {
 		/* we would expect, that after normalization, the connection can be verified.
@@ -1690,6 +1657,194 @@ nm_connection_normalize (NMConnection *connection,
 }
 
 /**
+ * nm_connection_normalize:
+ * @connection: the #NMConnection to normalize
+ * @parameters: (allow-none) (element-type utf8 gpointer): a #GHashTable with
+ * normalization parameters to allow customization of the normalization by providing
+ * specific arguments. Unknown arguments will be ignored and the default will be
+ * used. The keys must be strings compared with g_str_equal() function.
+ * The values are opaque and depend on the parameter name.
+ * @modified: (out) (allow-none): outputs whether any settings were modified.
+ * @error: location to store error, or %NULL. Contains the reason,
+ * why the connection is invalid, if the function returns an error.
+ *
+ * Does some basic normalization and fixup of well known inconsistencies
+ * and deprecated fields. If the connection was modified in any way,
+ * the output parameter @modified is set %TRUE.
+ *
+ * Finally the connection will be verified and %TRUE returns if the connection
+ * is valid. As this function only performs some specific normalization steps
+ * it cannot repair all connections. If the connection has errors that
+ * cannot be normalized, the connection will not be modified.
+ *
+ * Returns: %TRUE if the connection is valid, %FALSE if it is not
+ **/
+gboolean
+nm_connection_normalize (NMConnection *connection,
+                         GHashTable *parameters,
+                         gboolean *modified,
+                         GError **error)
+{
+	NMSettingVerifyResult success;
+	gs_free_error GError *normalizable_error = NULL;
+
+	success = _nm_connection_verify (connection, &normalizable_error);
+
+	if (!NM_IN_SET (success,
+	                NM_SETTING_VERIFY_NORMALIZABLE,
+	                NM_SETTING_VERIFY_NORMALIZABLE_ERROR)) {
+		if (normalizable_error) {
+			nm_assert (success == NM_SETTING_VERIFY_ERROR);
+			g_propagate_error (error, g_steal_pointer (&normalizable_error));
+		} else
+			nm_assert (success == NM_SETTING_VERIFY_SUCCESS);
+
+		NM_SET_OUT (modified, FALSE);
+
+		if (success != NM_SETTING_VERIFY_SUCCESS) {
+			if (   error
+			    && !*error) {
+				g_set_error_literal (error,
+				                     NM_CONNECTION_ERROR,
+				                     NM_CONNECTION_ERROR_FAILED,
+				                     _("Unexpected failure to verify the connection"));
+				return FALSE;
+			}
+			return FALSE;
+		}
+
+		if (error && *error)
+			return FALSE;
+		return TRUE;
+	}
+
+	return _connection_normalize (connection, parameters, modified, error);
+}
+
+gboolean
+_nm_connection_ensure_normalized (NMConnection *connection,
+                                  gboolean allow_modify,
+                                  const char *expected_uuid,
+                                  gboolean coerce_uuid,
+                                  NMConnection **out_connection_clone,
+                                  GError **error)
+{
+	gs_unref_object NMConnection *connection_clone = NULL;
+	gs_free_error GError *local = NULL;
+	NMSettingVerifyResult vresult;
+
+	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (!out_connection_clone || !*out_connection_clone);
+	nm_assert (!expected_uuid || nm_utils_is_uuid (expected_uuid));
+
+	if (expected_uuid) {
+		if (nm_streq0 (expected_uuid, nm_connection_get_uuid (connection)))
+			expected_uuid = NULL;
+		else if (   !coerce_uuid
+		         || (!allow_modify && !out_connection_clone)) {
+			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("unexpected uuid %s instead of %s"),
+			             nm_connection_get_uuid (connection),
+			             expected_uuid);
+			return FALSE;
+		}
+	}
+
+	vresult = _nm_connection_verify (connection, &local);
+	if (vresult != NM_SETTING_VERIFY_SUCCESS) {
+		if (!NM_IN_SET (vresult, NM_SETTING_VERIFY_NORMALIZABLE,
+		                         NM_SETTING_VERIFY_NORMALIZABLE_ERROR)) {
+			g_propagate_error (error, g_steal_pointer (&local));
+			return FALSE;
+		}
+		if (!allow_modify) {
+			if (!out_connection_clone) {
+				/* even NM_SETTING_VERIFY_NORMALIZABLE is treated as an error. We could normalize,
+				 * but are not allowed to (and no out argument is provided for cloning).  */
+				g_propagate_error (error, g_steal_pointer (&local));
+				return FALSE;
+			}
+			connection_clone = nm_simple_connection_new_clone (connection);
+			connection = connection_clone;
+		}
+		if (!_connection_normalize (connection, NULL, NULL, error))
+			g_return_val_if_reached (FALSE);
+	}
+
+	if (expected_uuid) {
+		NMSettingConnection *s_con;
+
+		if (   !allow_modify
+		    && !connection_clone) {
+			nm_assert (out_connection_clone);
+			connection_clone = nm_simple_connection_new_clone (connection);
+			connection = connection_clone;
+		}
+		s_con = nm_connection_get_setting_connection (connection);
+		g_object_set (s_con,
+		              NM_SETTING_CONNECTION_UUID,
+		              expected_uuid,
+		              NULL);
+	}
+
+	NM_SET_OUT (out_connection_clone, g_steal_pointer (&connection_clone));
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+#if NM_MORE_ASSERTS
+static void
+_nmtst_connection_unchanging_changed_cb (NMConnection *connection, gpointer user_data)
+{
+	nm_assert_not_reached ();
+}
+
+static void
+_nmtst_connection_unchanging_secrets_updated_cb (NMConnection *connection, const char *setting_name, gpointer user_data)
+{
+	nm_assert_not_reached ();
+}
+
+const char _nmtst_connection_unchanging_user_data = 0;
+
+void
+nmtst_connection_assert_unchanging (NMConnection *connection)
+{
+	if (!connection)
+		return;
+
+	nm_assert (NM_IS_CONNECTION (connection));
+
+	if (g_signal_handler_find (connection,
+	                           G_SIGNAL_MATCH_DATA,
+	                           0,
+	                           0,
+	                           NULL,
+	                           NULL,
+	                           (gpointer) &_nmtst_connection_unchanging_user_data) != 0) {
+		/* avoid connecting the assertion handler multiple times. */
+		return;
+	}
+
+	g_signal_connect (connection,
+	                  NM_CONNECTION_CHANGED,
+	                  G_CALLBACK (_nmtst_connection_unchanging_changed_cb),
+	                  (gpointer) &_nmtst_connection_unchanging_user_data);
+	g_signal_connect (connection,
+	                  NM_CONNECTION_SECRETS_CLEARED,
+	                  G_CALLBACK (_nmtst_connection_unchanging_changed_cb),
+	                  (gpointer) &_nmtst_connection_unchanging_user_data);
+	g_signal_connect (connection,
+	                  NM_CONNECTION_SECRETS_UPDATED,
+	                  G_CALLBACK (_nmtst_connection_unchanging_secrets_updated_cb),
+	                  (gpointer) &_nmtst_connection_unchanging_user_data);
+}
+#endif
+
+/*****************************************************************************/
+
+/**
  * nm_connection_update_secrets:
  * @connection: the #NMConnection
  * @setting_name: the setting object name to which the secrets apply
@@ -1714,7 +1869,8 @@ nm_connection_update_secrets (NMConnection *connection,
                               GError **error)
 {
 	NMSetting *setting;
-	gboolean success = TRUE, updated = FALSE;
+	gboolean success = TRUE;
+	gboolean updated = FALSE;
 	GVariant *setting_dict = NULL;
 	GVariantIter iter;
 	const char *key;
@@ -1722,13 +1878,13 @@ nm_connection_update_secrets (NMConnection *connection,
 	int success_detail;
 
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
-	g_return_val_if_fail (   g_variant_is_of_type (secrets, NM_VARIANT_TYPE_SETTING)
-	                      || g_variant_is_of_type (secrets, NM_VARIANT_TYPE_CONNECTION), FALSE);
-	if (error)
-		g_return_val_if_fail (*error == NULL, FALSE);
 
 	full_connection = g_variant_is_of_type (secrets, NM_VARIANT_TYPE_CONNECTION);
-	g_return_val_if_fail (setting_name != NULL || full_connection, FALSE);
+
+	g_return_val_if_fail (   full_connection
+	                      || g_variant_is_of_type (secrets, NM_VARIANT_TYPE_SETTING), FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+	g_return_val_if_fail (setting_name || full_connection, FALSE);
 
 	/* Empty @secrets means success */
 	if (g_variant_n_children (secrets) == 0)
@@ -1763,8 +1919,10 @@ nm_connection_update_secrets (NMConnection *connection,
 
 		g_clear_pointer (&setting_dict, g_variant_unref);
 
-		if (success_detail == NM_SETTING_UPDATE_SECRET_ERROR)
+		if (success_detail == NM_SETTING_UPDATE_SECRET_ERROR) {
+			nm_assert (!error || *error);
 			return FALSE;
+		}
 		if (success_detail == NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED)
 			updated = TRUE;
 	} else {
@@ -1784,17 +1942,27 @@ nm_connection_update_secrets (NMConnection *connection,
 		/* Update each setting with any secrets from the connection dictionary */
 		g_variant_iter_init (&iter, secrets);
 		while (g_variant_iter_next (&iter, "{&s@a{sv}}", &key, &setting_dict)) {
+			gs_free_error GError *local = NULL;
+
 			/* Update the secrets for this setting */
 			setting = nm_connection_get_setting_by_name (connection, key);
 
 			g_signal_handlers_block_by_func (setting, (GCallback) setting_changed_cb, connection);
-			success_detail = _nm_setting_update_secrets (setting, setting_dict, error);
+			success_detail = _nm_setting_update_secrets (setting, setting_dict, error ? &local : NULL);
 			g_signal_handlers_unblock_by_func (setting, (GCallback) setting_changed_cb, connection);
 
 			g_variant_unref (setting_dict);
 
 			if (success_detail == NM_SETTING_UPDATE_SECRET_ERROR) {
-				success = FALSE;
+				if (success) {
+					if (error) {
+						nm_assert (local);
+						g_propagate_error (error, g_steal_pointer (&local));
+						error = NULL;
+					} else
+						nm_assert (!local);
+					success = FALSE;
+				}
 				break;
 			}
 			if (success_detail == NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED)
@@ -1911,6 +2079,52 @@ nm_connection_clear_secrets_with_flags (NMConnection *connection,
 	g_signal_emit (connection, signals[SECRETS_CLEARED], 0);
 }
 
+static gboolean
+_clear_secrets_by_secret_flags_cb (NMSetting *setting,
+                                   const char *secret,
+                                   NMSettingSecretFlags flags,
+                                   gpointer user_data)
+{
+	NMSettingSecretFlags filter_flags = GPOINTER_TO_UINT (user_data);
+	gboolean remove_secret;
+
+	if (filter_flags == NM_SETTING_SECRET_FLAG_NONE) {
+		/* Can't use bitops with SECRET_FLAG_NONE so handle that specifically */
+		remove_secret = (flags != NM_SETTING_SECRET_FLAG_NONE);
+	} else {
+		/* Otherwise if the secret has at least one of the desired flags keep it */
+		remove_secret = !NM_FLAGS_ANY (flags, filter_flags);
+	}
+
+	return remove_secret;
+}
+
+/**
+ * _nm_connection_clear_secrets_by_secret_flags:
+ * @self: the #NMConnection to filter (will be modified)
+ * @filter_flags: the secret flags to control whether to drop/remove
+ *   a secret or to keep it. The meaning of the filter flags is to
+ *   preseve the secrets. The secrets that have matching (see below)
+ *   flags are kept, the others are dropped.
+ *
+ * Removes/drops secrets from @self according to @filter_flags.
+ * If @filter_flags is %NM_SETTING_SECRET_NONE, then only secrets that
+ * have %NM_SETTING_SECRET_NONE flags are kept.
+ * Otherwise, only secrets with secret flags are kept that have at least
+ * one of the filter flags.
+ */
+void
+_nm_connection_clear_secrets_by_secret_flags (NMConnection *self,
+                                              NMSettingSecretFlags filter_flags)
+{
+	nm_connection_clear_secrets_with_flags (self,
+	                                        _clear_secrets_by_secret_flags_cb,
+	                                        GUINT_TO_POINTER (filter_flags));
+}
+
+/*****************************************************************************/
+
+
 /*****************************************************************************/
 
 /* Returns always a non-NULL, floating variant that must
@@ -2029,6 +2243,14 @@ GVariant *
 nm_connection_to_dbus (NMConnection *connection,
                        NMConnectionSerializationFlags flags)
 {
+	return nm_connection_to_dbus_full (connection, flags, NULL);
+}
+
+GVariant *
+nm_connection_to_dbus_full (NMConnection *connection,
+                            NMConnectionSerializationFlags flags,
+                            const NMConnectionSerializationOptions *options)
+{
 	NMConnectionPrivate *priv;
 	GVariantBuilder builder;
 	GHashTableIter iter;
@@ -2041,11 +2263,14 @@ nm_connection_to_dbus (NMConnection *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);
 
-		setting_dict = _nm_setting_to_dbus (setting, connection, flags);
+		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);
 	}
@@ -3158,13 +3383,13 @@ nm_connection_default_init (NMConnectionInterface *iface)
 	 */
 	signals[SECRETS_UPDATED] =
 	    g_signal_new (NM_CONNECTION_SECRETS_UPDATED,
-	                 NM_TYPE_CONNECTION,
-	                 G_SIGNAL_RUN_FIRST,
-	                 G_STRUCT_OFFSET (NMConnectionInterface, secrets_updated),
-	                 NULL, NULL,
-	                 g_cclosure_marshal_VOID__STRING,
-	                 G_TYPE_NONE, 1,
-	                 G_TYPE_STRING);
+	                  NM_TYPE_CONNECTION,
+	                  G_SIGNAL_RUN_FIRST,
+	                  G_STRUCT_OFFSET (NMConnectionInterface, secrets_updated),
+	                  NULL, NULL,
+	                  g_cclosure_marshal_VOID__STRING,
+	                  G_TYPE_NONE, 1,
+	                  G_TYPE_STRING);
 
 	/**
 	 * NMConnection::secrets-cleared:
@@ -3175,12 +3400,12 @@ nm_connection_default_init (NMConnectionInterface *iface)
 	 */
 	signals[SECRETS_CLEARED] =
 	    g_signal_new (NM_CONNECTION_SECRETS_CLEARED,
-	                 NM_TYPE_CONNECTION,
-	                 G_SIGNAL_RUN_FIRST,
-	                 G_STRUCT_OFFSET (NMConnectionInterface, secrets_cleared),
-	                 NULL, NULL,
-	                 g_cclosure_marshal_VOID__VOID,
-	                 G_TYPE_NONE, 0);
+	                  NM_TYPE_CONNECTION,
+	                  G_SIGNAL_RUN_FIRST,
+	                  G_STRUCT_OFFSET (NMConnectionInterface, secrets_cleared),
+	                  NULL, NULL,
+	                  g_cclosure_marshal_VOID__VOID,
+	                  G_TYPE_NONE, 0);
 
 	/**
 	 * NMConnection::changed:
@@ -3192,10 +3417,10 @@ nm_connection_default_init (NMConnectionInterface *iface)
 	 */
 	signals[CHANGED] =
 	    g_signal_new (NM_CONNECTION_CHANGED,
-	                 NM_TYPE_CONNECTION,
-	                 G_SIGNAL_RUN_FIRST,
-	                 G_STRUCT_OFFSET (NMConnectionInterface, changed),
-	                 NULL, NULL,
-	                 g_cclosure_marshal_VOID__VOID,
-	                 G_TYPE_NONE, 0);
+	                  NM_TYPE_CONNECTION,
+	                  G_SIGNAL_RUN_FIRST,
+	                  G_STRUCT_OFFSET (NMConnectionInterface, changed),
+	                  NULL, NULL,
+	                  g_cclosure_marshal_VOID__VOID,
+	                  G_TYPE_NONE, 0);
 }
diff --git a/libnm-core/nm-connection.h b/libnm-core/nm-connection.h
index 8d64a4ce..4399ad67 100644
--- a/libnm-core/nm-connection.h
+++ b/libnm-core/nm-connection.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -113,14 +111,17 @@ NMSetting    *nm_connection_get_setting_by_name (NMConnection *connection,
  * @NM_CONNECTION_SERIALIZE_ALL: serialize all properties (including secrets)
  * @NM_CONNECTION_SERIALIZE_NO_SECRETS: do not include secrets
  * @NM_CONNECTION_SERIALIZE_ONLY_SECRETS: only serialize secrets
+ * @NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED: if set, only secrets that
+ *   are agent owned will be serialized.
  *
  * These flags determine which properties are serialized when calling when
  * calling nm_connection_to_dbus().
  **/
 typedef enum { /*< flags >*/
-	NM_CONNECTION_SERIALIZE_ALL = 0x00000000,
-	NM_CONNECTION_SERIALIZE_NO_SECRETS = 0x00000001,
-	NM_CONNECTION_SERIALIZE_ONLY_SECRETS = 0x00000002,
+	NM_CONNECTION_SERIALIZE_ALL                      = 0x00000000,
+	NM_CONNECTION_SERIALIZE_NO_SECRETS               = 0x00000001,
+	NM_CONNECTION_SERIALIZE_ONLY_SECRETS             = 0x00000002,
+	NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED = 0x00000004,
 } NMConnectionSerializationFlags;
 
 GVariant     *nm_connection_to_dbus       (NMConnection *connection,
diff --git a/libnm-core/nm-core-enum-types.c.template b/libnm-core/nm-core-enum-types.c.template
index 94744827..b6cb38ee 100644
--- a/libnm-core/nm-core-enum-types.c.template
+++ b/libnm-core/nm-core-enum-types.c.template
@@ -30,6 +30,7 @@
 #include "nm-setting-olpc-mesh.h"
 #include "nm-setting-ovs-bridge.h"
 #include "nm-setting-ovs-interface.h"
+#include "nm-setting-ovs-dpdk.h"
 #include "nm-setting-ovs-patch.h"
 #include "nm-setting-ovs-port.h"
 #include "nm-setting-ppp.h"
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h
index 3e33dd93..868f7312 100644
--- a/libnm-core/nm-core-internal.h
+++ b/libnm-core/nm-core-internal.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -62,6 +61,7 @@
 #include "nm-setting-olpc-mesh.h"
 #include "nm-setting-ovs-bridge.h"
 #include "nm-setting-ovs-interface.h"
+#include "nm-setting-ovs-dpdk.h"
 #include "nm-setting-ovs-patch.h"
 #include "nm-setting-ovs-port.h"
 #include "nm-setting-ppp.h"
@@ -164,6 +164,20 @@ gpointer _nm_connection_check_main_setting (NMConnection *connection,
                                             const char *setting_name,
                                             GError **error);
 
+typedef struct {
+	struct {
+		guint64 val;
+		bool has;
+	} timestamp;
+
+	const char **seen_bssids;
+
+} NMConnectionSerializationOptions;
+
+GVariant *nm_connection_to_dbus_full (NMConnection *connection,
+                                      NMConnectionSerializationFlags flags,
+                                      const NMConnectionSerializationOptions *options);
+
 typedef enum {
 	/* whether the connection has any secrets.
 	 *
@@ -202,8 +216,25 @@ typedef enum {
 
 NMSettingVerifyResult _nm_connection_verify (NMConnection *connection, GError **error);
 
+gboolean _nm_connection_ensure_normalized (NMConnection *connection,
+                                           gboolean allow_modify,
+                                           const char *expected_uuid,
+                                           gboolean coerce_uuid,
+                                           NMConnection **out_connection_clone,
+                                           GError **error);
+
 gboolean _nm_connection_remove_setting (NMConnection *connection, GType setting_type);
 
+#if NM_MORE_ASSERTS
+extern const char _nmtst_connection_unchanging_user_data;
+void nmtst_connection_assert_unchanging (NMConnection *connection);
+#else
+static inline void
+nmtst_connection_assert_unchanging (NMConnection *connection)
+{
+}
+#endif
+
 NMConnection *_nm_simple_connection_new_from_dbus (GVariant      *dict,
                                                    NMSettingParseFlags parse_flags,
                                                    GError       **error);
@@ -260,6 +291,8 @@ typedef gpointer (*NMUtilsCopyFunc) (gpointer);
 
 const char **_nm_ip_address_get_attribute_names (const NMIPAddress *addr, gboolean sorted, guint *out_length);
 
+void _nm_setting_wired_clear_s390_options (NMSettingWired *setting);
+
 gboolean _nm_ip_route_attribute_validate_all (const NMIPRoute *route);
 const char **_nm_ip_route_get_attribute_names (const NMIPRoute *route, gboolean sorted, guint *out_length);
 GHashTable *_nm_ip_route_get_attributes (NMIPRoute *route);
@@ -275,10 +308,10 @@ GPtrArray *_nm_utils_copy_object_array (const GPtrArray *array);
 gssize _nm_utils_ptrarray_find_first (gconstpointer *list, gssize len, gconstpointer needle);
 
 GSList *    _nm_utils_strv_to_slist (char **strv, gboolean deep_copy);
-char **     _nm_utils_slist_to_strv (GSList *slist, gboolean deep_copy);
+char **     _nm_utils_slist_to_strv (const GSList *slist, gboolean deep_copy);
 
 GPtrArray * _nm_utils_strv_to_ptrarray (char **strv);
-char **     _nm_utils_ptrarray_to_strv (GPtrArray *ptrarray);
+char **     _nm_utils_ptrarray_to_strv (const GPtrArray *ptrarray);
 
 gboolean _nm_utils_check_file (const char *filename,
                                gint64 check_owner,
@@ -443,6 +476,13 @@ gboolean _nm_utils_generate_mac_address_mask_parse (const char *value,
 
 /*****************************************************************************/
 
+static inline gpointer
+_nm_connection_get_setting (NMConnection *connection,
+                            GType type)
+{
+	return (gpointer) nm_connection_get_setting (connection, type);
+}
+
 NMSettingIPConfig *nm_connection_get_setting_ip_config (NMConnection *connection,
                                                         int addr_family);
 
@@ -532,20 +572,18 @@ gboolean _nm_utils_inet6_is_token (const struct in6_addr *in6addr);
 
 /*****************************************************************************/
 
-gboolean _nm_team_link_watchers_equal (GPtrArray *a, GPtrArray *b, gboolean ignore_order);
+NMTeamLinkWatcher *_nm_team_link_watcher_ref (NMTeamLinkWatcher *watcher);
+
+int nm_team_link_watcher_cmp (const NMTeamLinkWatcher *watcher, const NMTeamLinkWatcher *other);
 
-gboolean _nm_utils_team_config_equal (const char *conf1, const char *conf2, gboolean port);
-GValue *_nm_utils_team_config_get (const char *conf,
-                                   const char *key,
-                                   const char *key2,
-                                   const char *key3,
-                                   gboolean port_config);
+int nm_team_link_watchers_cmp (const NMTeamLinkWatcher *const*a,
+                               const NMTeamLinkWatcher *const*b,
+                               gsize len,
+                               gboolean ignore_order);
 
-gboolean _nm_utils_team_config_set (char **conf,
-                                    const char *key,
-                                    const char *key2,
-                                    const char *key3,
-                                    const GValue *value);
+gboolean nm_team_link_watchers_equal (const GPtrArray *a,
+                                      const GPtrArray *b,
+                                      gboolean ignore_order);
 
 /*****************************************************************************/
 
@@ -630,25 +668,26 @@ gboolean nm_ip_routing_rule_get_xifname_bin (const NMIPRoutingRule *self,
                                              gboolean iif /* or else oif */,
                                              char out_xifname[static 16]);
 
-#define NM_IP_ROUTING_RULE_ATTR_ACTION      "action"
-#define NM_IP_ROUTING_RULE_ATTR_DPORT_END   "dport-end"
-#define NM_IP_ROUTING_RULE_ATTR_DPORT_START "dport-start"
-#define NM_IP_ROUTING_RULE_ATTR_FAMILY      "family"
-#define NM_IP_ROUTING_RULE_ATTR_FROM        "from"
-#define NM_IP_ROUTING_RULE_ATTR_FROM_LEN    "from-len"
-#define NM_IP_ROUTING_RULE_ATTR_FWMARK      "fwmark"
-#define NM_IP_ROUTING_RULE_ATTR_FWMASK      "fwmask"
-#define NM_IP_ROUTING_RULE_ATTR_IIFNAME     "iifname"
-#define NM_IP_ROUTING_RULE_ATTR_INVERT      "invert"
-#define NM_IP_ROUTING_RULE_ATTR_IPPROTO     "ipproto"
-#define NM_IP_ROUTING_RULE_ATTR_OIFNAME     "oifname"
-#define NM_IP_ROUTING_RULE_ATTR_PRIORITY    "priority"
-#define NM_IP_ROUTING_RULE_ATTR_SPORT_END   "sport-end"
-#define NM_IP_ROUTING_RULE_ATTR_SPORT_START "sport-start"
-#define NM_IP_ROUTING_RULE_ATTR_TABLE       "table"
-#define NM_IP_ROUTING_RULE_ATTR_TO          "to"
-#define NM_IP_ROUTING_RULE_ATTR_TOS         "tos"
-#define NM_IP_ROUTING_RULE_ATTR_TO_LEN      "to-len"
+#define NM_IP_ROUTING_RULE_ATTR_ACTION                "action"
+#define NM_IP_ROUTING_RULE_ATTR_DPORT_END             "dport-end"
+#define NM_IP_ROUTING_RULE_ATTR_DPORT_START           "dport-start"
+#define NM_IP_ROUTING_RULE_ATTR_FAMILY                "family"
+#define NM_IP_ROUTING_RULE_ATTR_FROM                  "from"
+#define NM_IP_ROUTING_RULE_ATTR_FROM_LEN              "from-len"
+#define NM_IP_ROUTING_RULE_ATTR_FWMARK                "fwmark"
+#define NM_IP_ROUTING_RULE_ATTR_FWMASK                "fwmask"
+#define NM_IP_ROUTING_RULE_ATTR_IIFNAME               "iifname"
+#define NM_IP_ROUTING_RULE_ATTR_INVERT                "invert"
+#define NM_IP_ROUTING_RULE_ATTR_IPPROTO               "ipproto"
+#define NM_IP_ROUTING_RULE_ATTR_OIFNAME               "oifname"
+#define NM_IP_ROUTING_RULE_ATTR_PRIORITY              "priority"
+#define NM_IP_ROUTING_RULE_ATTR_SPORT_END             "sport-end"
+#define NM_IP_ROUTING_RULE_ATTR_SPORT_START           "sport-start"
+#define NM_IP_ROUTING_RULE_ATTR_SUPPRESS_PREFIXLENGTH "suppress-prefixlength"
+#define NM_IP_ROUTING_RULE_ATTR_TABLE                 "table"
+#define NM_IP_ROUTING_RULE_ATTR_TO                    "to"
+#define NM_IP_ROUTING_RULE_ATTR_TOS                   "tos"
+#define NM_IP_ROUTING_RULE_ATTR_TO_LEN                "to-len"
 
 NMIPRoutingRule *nm_ip_routing_rule_from_dbus (GVariant *variant,
                                                gboolean strict,
@@ -660,40 +699,41 @@ GVariant *nm_ip_routing_rule_to_dbus (const NMIPRoutingRule *self);
 typedef struct _NMSettInfoSetting  NMSettInfoSetting;
 typedef struct _NMSettInfoProperty NMSettInfoProperty;
 
-typedef GVariant *(*NMSettingPropertyGetFunc)           (NMSetting     *setting,
-                                                         const char    *property);
-typedef GVariant *(*NMSettingPropertySynthFunc)         (const NMSettInfoSetting *sett_info,
+typedef GVariant *(*NMSettInfoPropToDBusFcn)            (const NMSettInfoSetting *sett_info,
                                                          guint property_idx,
                                                          NMConnection  *connection,
                                                          NMSetting     *setting,
-                                                         NMConnectionSerializationFlags flags);
-typedef gboolean  (*NMSettingPropertySetFunc)           (NMSetting     *setting,
+                                                         NMConnectionSerializationFlags flags,
+                                                         const NMConnectionSerializationOptions *options);
+typedef gboolean  (*NMSettInfoPropFromDBusFcn)          (NMSetting     *setting,
                                                          GVariant      *connection_dict,
                                                          const char    *property,
                                                          GVariant      *value,
                                                          NMSettingParseFlags parse_flags,
                                                          GError       **error);
-typedef gboolean  (*NMSettingPropertyNotSetFunc)        (NMSetting     *setting,
+typedef gboolean  (*NMSettInfoPropMissingFromDBusFcn)   (NMSetting     *setting,
                                                          GVariant      *connection_dict,
                                                          const char    *property,
                                                          NMSettingParseFlags parse_flags,
                                                          GError       **error);
-typedef GVariant *(*NMSettingPropertyTransformToFunc)   (const GValue *from);
-typedef void      (*NMSettingPropertyTransformFromFunc) (GVariant *from,
-                                                          GValue *to);
+typedef GVariant *(*NMSettInfoPropGPropToDBusFcn)       (const GValue *from);
+typedef void      (*NMSettInfoPropGPropFromDBusFcn)     (GVariant *from,
+                                                         GValue *to);
 
 struct _NMSettInfoProperty {
 	const char *name;
 	GParamSpec *param_spec;
+
 	const GVariantType *dbus_type;
 
-	NMSettingPropertyGetFunc           get_func;
-	NMSettingPropertySynthFunc         synth_func;
-	NMSettingPropertySetFunc           set_func;
-	NMSettingPropertyNotSetFunc        not_set_func;
+	NMSettInfoPropToDBusFcn            to_dbus_fcn;
+	NMSettInfoPropFromDBusFcn          from_dbus_fcn;
+	NMSettInfoPropMissingFromDBusFcn   missing_from_dbus_fcn;
 
-	NMSettingPropertyTransformToFunc   to_dbus;
-	NMSettingPropertyTransformFromFunc from_dbus;
+	/* Simpler variants of @to_dbus_fcn/@from_dbus_fcn that operate solely
+	 * on the GValue value of the GObject property. */
+	NMSettInfoPropGPropToDBusFcn       gprop_to_dbus_fcn;
+	NMSettInfoPropGPropFromDBusFcn     gprop_from_dbus_fcn;
 };
 
 typedef struct {
@@ -767,6 +807,20 @@ _nm_setting_class_get_property_info (NMSettingClass *setting_class,
 
 /*****************************************************************************/
 
+gboolean _nm_setting_compare (NMConnection *con_a,
+                              NMSetting *set_a,
+                              NMConnection *con_b,
+                              NMSetting *set_b,
+                              NMSettingCompareFlags flags);
+
+gboolean _nm_setting_diff (NMConnection *con_a,
+                           NMSetting *set_a,
+                           NMConnection *con_b,
+                           NMSetting *set_b,
+                           NMSettingCompareFlags flags,
+                           gboolean invert_results,
+                           GHashTable **results);
+
 NMSetting8021xCKScheme _nm_setting_802_1x_cert_get_scheme (GBytes *bytes, GError **error);
 
 GBytes *_nm_setting_802_1x_cert_value_to_bytes (NMSetting8021xCKScheme scheme,
@@ -776,6 +830,21 @@ GBytes *_nm_setting_802_1x_cert_value_to_bytes (NMSetting8021xCKScheme scheme,
 
 /*****************************************************************************/
 
+static inline gboolean
+_nm_connection_serialize_secrets (NMConnectionSerializationFlags flags,
+                                  NMSettingSecretFlags secret_flags)
+{
+	if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_NO_SECRETS))
+		return FALSE;
+	if (   NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED)
+	    && !NM_FLAGS_HAS (secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED))
+		return FALSE;
+	return TRUE;
+}
+
+void _nm_connection_clear_secrets_by_secret_flags (NMConnection *self,
+                                                   NMSettingSecretFlags filter_flags);
+
 GVariant *_nm_connection_for_each_secret (NMConnection *self,
                                           GVariant *secrets,
                                           gboolean remove_non_secrets,
@@ -802,4 +871,8 @@ void _nm_bridge_vlan_str_append_rest (const NMBridgeVlan *vlan,
                                       GString *string,
                                       gboolean leading_space);
 
+gboolean nm_utils_connection_is_adhoc_wpa (NMConnection *connection);
+
+const char *nm_utils_wifi_freq_to_band (guint32 freq);
+
 #endif
diff --git a/libnm-core/nm-core-types-internal.h b/libnm-core/nm-core-types-internal.h
index 4d43aaf4..f4b3ae0c 100644
--- a/libnm-core/nm-core-types-internal.h
+++ b/libnm-core/nm-core-types-internal.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-core-types.h b/libnm-core/nm-core-types.h
index f20ffc09..fa8b4c41 100644
--- a/libnm-core/nm-core-types.h
+++ b/libnm-core/nm-core-types.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -53,6 +52,7 @@ typedef struct _NMSettingMacvlan          NMSettingMacvlan;
 typedef struct _NMSettingMatch            NMSettingMatch;
 typedef struct _NMSettingOlpcMesh         NMSettingOlpcMesh;
 typedef struct _NMSettingOvsBridge        NMSettingOvsBridge;
+typedef struct _NMSettingOvsDpdk          NMSettingOvsDpdk;
 typedef struct _NMSettingOvsInterface     NMSettingOvsInterface;
 typedef struct _NMSettingOvsPatch         NMSettingOvsPatch;
 typedef struct _NMSettingOvsPort          NMSettingOvsPort;
diff --git a/libnm-core/nm-crypto-gnutls.c b/libnm-core/nm-crypto-gnutls.c
index 6b2f7587..3288271d 100644
--- a/libnm-core/nm-crypto-gnutls.c
+++ b/libnm-core/nm-crypto-gnutls.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager Wireless Applet -- Display wireless access points and allow user control
  *
  * Dan Williams <dcbw@redhat.com>
diff --git a/libnm-core/nm-crypto-impl.h b/libnm-core/nm-crypto-impl.h
index 91865152..a745869e 100644
--- a/libnm-core/nm-crypto-impl.h
+++ b/libnm-core/nm-crypto-impl.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * Dan Williams <dcbw@redhat.com>
  *
diff --git a/libnm-core/nm-crypto-nss.c b/libnm-core/nm-crypto-nss.c
index 25cc7777..70d42682 100644
--- a/libnm-core/nm-crypto-nss.c
+++ b/libnm-core/nm-crypto-nss.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * Dan Williams <dcbw@redhat.com>
  *
diff --git a/libnm-core/nm-crypto.c b/libnm-core/nm-crypto.c
index c7142216..e0c3b7fd 100644
--- a/libnm-core/nm-crypto.c
+++ b/libnm-core/nm-crypto.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * Dan Williams <dcbw@redhat.com>
  *
@@ -51,6 +49,12 @@
 #define PEM_PKCS8_DEC_KEY_BEGIN "-----BEGIN PRIVATE KEY-----"
 #define PEM_PKCS8_DEC_KEY_END   "-----END PRIVATE KEY-----"
 
+#define PEM_TPM2_WRAPPED_KEY_BEGIN "-----BEGIN TSS2 PRIVATE KEY-----"
+#define PEM_TPM2_WRAPPED_KEY_END "-----END TSS2 PRIVATE KEY-----"
+
+#define PEM_TPM2_OLD_WRAPPED_KEY_BEGIN "-----BEGIN TSS2 KEY BLOB-----"
+#define PEM_TPM2_OLD_WRAPPED_KEY_END "-----END TSS2 KEY BLOB-----"
+
 /*****************************************************************************/
 
 static const NMCryptoCipherInfo cipher_infos[] = {
@@ -118,21 +122,23 @@ find_tag (const char *tag,
           gsize start_at,
           gsize *out_pos)
 {
-	gsize i, taglen;
-	gsize len = data_len - start_at;
+	const guint8 *p;
+	gsize taglen;
 
-	g_return_val_if_fail (out_pos != NULL, FALSE);
+	nm_assert (out_pos);
+	nm_assert (start_at <= data_len);
 
 	taglen = strlen (tag);
-	if (len >= taglen) {
-		for (i = 0; i < len - taglen + 1; i++) {
-			if (memcmp (data + start_at + i, tag, taglen) == 0) {
-				*out_pos = start_at + i;
-				return TRUE;
-			}
-		}
-	}
-	return FALSE;
+
+	p = memmem (&data[start_at], data_len - start_at, tag, taglen);
+	if (!p)
+		return FALSE;
+
+	*out_pos = p - data;
+
+	nm_assert (memcmp (&data[*out_pos], tag, taglen) == 0);
+
+	return TRUE;
 }
 
 #define DEK_INFO_TAG "DEK-Info: "
@@ -387,6 +393,43 @@ parse_pkcs8_key_file (const guint8 *data,
 }
 
 static gboolean
+parse_tpm2_wrapped_key_file (const guint8 *data,
+                             gsize data_len,
+                             gboolean *out_encrypted,
+                             GError **error)
+{
+	gsize start = 0, end = 0;
+	const char *start_tag = NULL, *end_tag = NULL;
+
+	nm_assert (out_encrypted);
+
+	if (find_tag (PEM_TPM2_WRAPPED_KEY_BEGIN, data, data_len, 0, &start)) {
+		start_tag = PEM_TPM2_WRAPPED_KEY_BEGIN;
+		end_tag = PEM_TPM2_WRAPPED_KEY_END;
+	} else if (find_tag (PEM_TPM2_OLD_WRAPPED_KEY_BEGIN, data, data_len, 0, &start)) {
+		start_tag = PEM_TPM2_OLD_WRAPPED_KEY_BEGIN;
+		end_tag = PEM_TPM2_OLD_WRAPPED_KEY_END;
+	} else {
+		g_set_error_literal (error, NM_CRYPTO_ERROR,
+		                     NM_CRYPTO_ERROR_INVALID_DATA,
+		                     _("Failed to find expected TSS start tag."));
+		return FALSE;
+	}
+
+	start += strlen (start_tag);
+	if (!find_tag (end_tag, data, data_len, start, &end)) {
+		g_set_error (error, NM_CRYPTO_ERROR,
+		             NM_CRYPTO_ERROR_INVALID_DATA,
+		             _("Failed to find expected TSS end tag '%s'."),
+		             end_tag);
+		return FALSE;
+	}
+
+	*out_encrypted = FALSE;
+	return TRUE;
+}
+
+static gboolean
 file_read_contents (const char *filename,
                     NMSecretPtr *out_contents,
                     GError **error)
@@ -824,6 +867,8 @@ nm_crypto_verify_private_key_data (const guint8 *data,
 			if (   !password
 			    || _nm_crypto_verify_pkcs8 (parsed.bin, parsed.len, is_encrypted, password, error))
 				format = NM_CRYPTO_FILE_FORMAT_RAW_KEY;
+		} else if (parse_tpm2_wrapped_key_file (data, data_len, &is_encrypted, NULL)) {
+			format = NM_CRYPTO_FILE_FORMAT_RAW_KEY;
 		} else {
 			NMCryptoCipherType cipher;
 			nm_auto_free_secret char *iv = NULL;
diff --git a/libnm-core/nm-crypto.h b/libnm-core/nm-crypto.h
index 54fbbc5f..4b43d153 100644
--- a/libnm-core/nm-crypto.h
+++ b/libnm-core/nm-crypto.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * Dan Williams <dcbw@redhat.com>
  *
diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h
index 2e127c40..7949fa2d 100644
--- a/libnm-core/nm-dbus-interface.h
+++ b/libnm-core/nm-dbus-interface.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -293,6 +292,7 @@ typedef enum { /*< flags >*/
  * @NM_WIFI_DEVICE_CAP_FREQ_VALID: device reports frequency capabilities
  * @NM_WIFI_DEVICE_CAP_FREQ_2GHZ: device supports 2.4GHz frequencies
  * @NM_WIFI_DEVICE_CAP_FREQ_5GHZ: device supports 5GHz frequencies
+ * @NM_WIFI_DEVICE_CAP_MESH: device supports acting as a mesh point
  *
  * 802.11 specific device encryption and authentication capabilities.
  **/
@@ -309,6 +309,7 @@ typedef enum { /*< flags >*/
 	NM_WIFI_DEVICE_CAP_FREQ_VALID    = 0x00000100,
 	NM_WIFI_DEVICE_CAP_FREQ_2GHZ     = 0x00000200,
 	NM_WIFI_DEVICE_CAP_FREQ_5GHZ     = 0x00000400,
+	NM_WIFI_DEVICE_CAP_MESH          = 0x00001000,
 } NMDeviceWifiCapabilities;
 
 /**
@@ -384,6 +385,7 @@ typedef enum { /*< underscore_name=nm_802_11_ap_security_flags, flags >*/
  *   provides connectivity to clients.
  * @NM_802_11_MODE_AP: the device is an access point/hotspot.  Not valid for
  *   access point objects; used only for hotspot mode on the local machine.
+ * @NM_802_11_MODE_MESH: the device is a 802.11s mesh point.
  *
  * Indicates the 802.11 mode an access point or device is currently in.
  **/
@@ -392,6 +394,7 @@ typedef enum { /*< underscore_name=nm_802_11_mode >*/
 	NM_802_11_MODE_ADHOC   = 1,
 	NM_802_11_MODE_INFRA   = 2,
 	NM_802_11_MODE_AP      = 3,
+	NM_802_11_MODE_MESH    = 4,
 } NM80211Mode;
 
 /**
@@ -1007,26 +1010,55 @@ typedef enum { /*< flags >*/
 } NMActivationStateFlags;
 
 /**
+ * NMSettingsAddConnection2Flags:
+ * @NM_SETTINGS_ADD_CONNECTION2_FLAG_NONE: an alias for numeric zero, no flags set.
+ * @NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK: to persist the connection to disk.
+ * @NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY: to make the connection in-memory only.
+ * @NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT: usually, when the connection
+ *   has autoconnect enabled and gets added, it becomes eligible to autoconnect
+ *   right away. Setting this flag, disables autoconnect until the connection
+ *   is manually activated.
+ *
+ * Numeric flags for the "flags" argument of AddConnection2() D-Bus API.
+ *
+ * Since: 1.20
+ */
+typedef enum { /*< flags >*/
+	NM_SETTINGS_ADD_CONNECTION2_FLAG_NONE              = 0,
+	NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK           = 0x1,
+	NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY         = 0x2,
+	NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT = 0x20,
+} NMSettingsAddConnection2Flags;
+
+/**
  * NMSettingsUpdate2Flags:
  * @NM_SETTINGS_UPDATE2_FLAG_NONE: an alias for numeric zero, no flags set.
  * @NM_SETTINGS_UPDATE2_FLAG_TO_DISK: to persist the connection to disk.
- * @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY: to make the connection in-memory only.
- *   If the connection was previously persistent, the corresponding file on disk
- *   is not deleted but merely the connection is decoupled from the file
- *   on disk. If you later delete an in-memory connection, the connection
- *   on disk will be deleted as well.
- * @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED: this is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY,
- *   but if the connection has a corresponding file on disk, the association between
- *   the connection and the file is forgotten but the file is not modified.
- *   The difference to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY is if you later
- *   save the connection again to disk, a new file name will be chosen without
- *   overwriting the remaining file on disk. Also, if you delete the connection
- *   later, the file on disk will not be deleted.
+ * @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY: makes the profile in-memory.
+ *   Note that such profiles are stored in keyfile format under /run.
+ *   If the file is already in-memory, the file in /run is updated in-place.
+ *   Otherwise, the previous storage for the profile is left unchanged
+ *   on disk, and the in-memory copy shadows it.
+ *   Note that the original filename of the previous persistent storage (if any)
+ *   is remembered. That means, when later persisting the profile again to disk,
+ *   the file on disk will be overwritten again.
+ *   Likewise, when finally deleting the profile, both the storage from /run
+ *   and persistent storage are deleted (or if the persistent storage does not
+ *   allow deletion, and nmmeta file is written to mark the UUID as deleted).
+ * @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED: this is almost the same as
+ *   @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, with one difference: when later deleting
+ *   the profile, the original profile will not be deleted. Instead a nmmeta
+ *   file is written to /run to indicate that the profile is gone.
+ *   Note that if such a nmmeta tombstone file exists and hides a file in persistant
+ *   storage, then when re-adding the profile with the same UUID, then the original
+ *   storage is taken over again.
  * @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY: this is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY,
- *   but if the connection has a corresponding file on disk, the file on
- *   disk will be deleted.
+ *   but if the connection has a corresponding file on persistent storage, the file
+ *   will be deleted right away. If the profile is later again persisted to disk,
+ *   a new, unused filename will be chosen.
  * @NM_SETTINGS_UPDATE2_FLAG_VOLATILE: This can be specified with either
- *   %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED or %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY.
+ *   %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED
+ *   or %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY.
  *   After making the connection in-memory only, the connection is marked
  *   as volatile. That means, if the connection is currently not active
  *   it will be deleted right away. Otherwise, it is marked to for deletion
@@ -1037,6 +1069,13 @@ typedef enum { /*< flags >*/
  *   has autoconnect enabled and is modified, it becomes eligible to autoconnect
  *   right away. Setting this flag, disables autoconnect until the connection
  *   is manually activated.
+ * @NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY: when a profile gets modified that is
+ *   currently active, then these changes don't take effect for the active
+ *   device unless the profile gets reactivated or the configuration reapplied.
+ *   There are two exceptions: by default "connection.zone" and "connection.metered"
+ *   properties take effect immediately. Specify this flag to prevent these
+ *   properties to take effect, so that the change is restricted to modify
+ *   the profile. Since: 1.20.
  *
  * Since: 1.12
  */
@@ -1048,6 +1087,7 @@ typedef enum { /*< flags >*/
 	NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY             = 0x8,
 	NM_SETTINGS_UPDATE2_FLAG_VOLATILE                   = 0x10,
 	NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT          = 0x20,
+	NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY                 = 0x40,
 } NMSettingsUpdate2Flags;
 
 /**
diff --git a/libnm-core/nm-dbus-types.xml b/libnm-core/nm-dbus-types.xml
index a65e1d08..ad199238 100644
--- a/libnm-core/nm-dbus-types.xml
+++ b/libnm-core/nm-dbus-types.xml
@@ -437,6 +437,11 @@
               <entry role="enum_member_value"><para>= <literal>0x00000400</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>device supports 5GHz frequencies</para><para></para></entry>
             </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_WIFI_DEVICE_CAP_MESH</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x00001000</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>device supports acting as a mesh point</para><para></para></entry>
+            </row>
           </tbody>
         </tgroup>
       </informaltable>
@@ -602,6 +607,11 @@
               <entry role="enum_member_value"><para>= <literal>3</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>the device is an access point/hotspot.  Not valid for access point objects; used only for hotspot mode on the local machine.</para><para></para></entry>
             </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_802_11_MODE_MESH</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>4</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>the device is a 802.11s mesh point.</para><para></para></entry>
+            </row>
           </tbody>
         </tgroup>
       </informaltable>
@@ -1748,6 +1758,46 @@
     </refsect3>
   </refsect2>
 
+  <refsect2 id="NMSettingsAddConnection2Flags" role="enum">
+    <title>enum NMSettingsAddConnection2Flags</title>
+    <indexterm zone="NMSettingsAddConnection2Flags">
+      <primary>NMSettingsAddConnection2Flags</primary>
+    </indexterm>
+    <para><para>Numeric flags for the "flags" argument of AddConnection2() D-Bus API.</para><para>Since: 1.20</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_SETTINGS_ADD_CONNECTION2_FLAG_NONE</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>an alias for numeric zero, no flags set.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x1</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>to persist the connection to disk.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x2</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>to make the connection in-memory only.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x20</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>usually, when the connection has autoconnect enabled and gets added, it becomes eligible to autoconnect right away. Setting this flag, disables autoconnect until the connection is manually activated.</para><para></para></entry>
+            </row>
+          </tbody>
+        </tgroup>
+      </informaltable>
+    </refsect3>
+  </refsect2>
+
   <refsect2 id="NMSettingsUpdate2Flags" role="enum">
     <title>enum NMSettingsUpdate2Flags</title>
     <indexterm zone="NMSettingsUpdate2Flags">
@@ -1775,28 +1825,33 @@
             <row role="constant">
               <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY</para><para></para></entry>
               <entry role="enum_member_value"><para>= <literal>0x2</literal></para><para></para></entry>
-              <entry role="enum_member_description"><para>to make the connection in-memory only. If the connection was previously persistent, the corresponding file on disk is not deleted but merely the connection is decoupled from the file on disk. If you later delete an in-memory connection, the connection on disk will be deleted as well.</para><para></para></entry>
+              <entry role="enum_member_description"><para>makes the profile in-memory. Note that such profiles are stored in keyfile format under /run. If the file is already in-memory, the file in /run is updated in-place. Otherwise, the previous storage for the profile is left unchanged on disk, and the in-memory copy shadows it. Note that the original filename of the previous persistent storage (if any) is remembered. That means, when later persisting the profile again to disk, the file on disk will be overwritten again. Likewise, when finally deleting the profile, both the storage from /run and persistent storage are deleted (or if the persistent storage does not allow deletion, and nmmeta file is written to mark the UUID as deleted).</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED</para><para></para></entry>
               <entry role="enum_member_value"><para>= <literal>0x4</literal></para><para></para></entry>
-              <entry role="enum_member_description"><para>this is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, but if the connection has a corresponding file on disk, the association between the connection and the file is forgotten but the file is not modified. The difference to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY is if you later save the connection again to disk, a new file name will be chosen without overwriting the remaining file on disk. Also, if you delete the connection later, the file on disk will not be deleted.</para><para></para></entry>
+              <entry role="enum_member_description"><para>this is almost the same as @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, with one difference: when later deleting the profile, the original profile will not be deleted. Instead a nmmeta file is written to /run to indicate that the profile is gone. Note that if such a nmmeta tombstone file exists and hides a file in persistant storage, then when re-adding the profile with the same UUID, then the original storage is taken over again.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY</para><para></para></entry>
               <entry role="enum_member_value"><para>= <literal>0x8</literal></para><para></para></entry>
-              <entry role="enum_member_description"><para>this is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, but if the connection has a corresponding file on disk, the file on disk will be deleted.</para><para></para></entry>
+              <entry role="enum_member_description"><para>this is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, but if the connection has a corresponding file on persistent storage, the file will be deleted right away. If the profile is later again persisted to disk, a new, unused filename will be chosen.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_VOLATILE</para><para></para></entry>
               <entry role="enum_member_value"><para>= <literal>0x10</literal></para><para></para></entry>
-              <entry role="enum_member_description"><para>This can be specified with either %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED or %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY. After making the connection in-memory only, the connection is marked as volatile. That means, if the connection is currently not active it will be deleted right away. Otherwise, it is marked to for deletion once the connection deactivates. A volatile connection cannot autoactivate again (because it's about to be deleted), but a manual activation will clear the volatile flag.</para><para></para></entry>
+              <entry role="enum_member_description"><para>This can be specified with either %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED or %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY. After making the connection in-memory only, the connection is marked as volatile. That means, if the connection is currently not active it will be deleted right away. Otherwise, it is marked to for deletion once the connection deactivates. A volatile connection cannot autoactivate again (because it's about to be deleted), but a manual activation will clear the volatile flag.</para><para></para></entry>
             </row>
             <row role="constant">
               <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT</para><para></para></entry>
               <entry role="enum_member_value"><para>= <literal>0x20</literal></para><para></para></entry>
               <entry role="enum_member_description"><para>usually, when the connection has autoconnect enabled and is modified, it becomes eligible to autoconnect right away. Setting this flag, disables autoconnect until the connection is manually activated.</para><para></para></entry>
             </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>0x40</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>when a profile gets modified that is currently active, then these changes don't take effect for the active device unless the profile gets reactivated or the configuration reapplied. There are two exceptions: by default "connection.zone" and "connection.metered" properties take effect immediately. Specify this flag to prevent these properties to take effect, so that the change is restricted to modify the profile. Since: 1.20.</para><para></para></entry>
+            </row>
           </tbody>
         </tgroup>
       </informaltable>
diff --git a/libnm-core/nm-dbus-utils.c b/libnm-core/nm-dbus-utils.c
index 8c455971..469773cf 100644
--- a/libnm-core/nm-dbus-utils.c
+++ b/libnm-core/nm-dbus-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-errors.c b/libnm-core/nm-errors.c
index beef95c7..d6aa1ed3 100644
--- a/libnm-core/nm-errors.c
+++ b/libnm-core/nm-errors.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -39,21 +38,19 @@ register_error_domain (GQuark domain,
                        const char *interface,
                        GType enum_type)
 {
-	GEnumClass *enum_class;
-	GEnumValue *e;
-	char *error_name;
-	int i;
+	nm_auto_unref_gtypeclass GEnumClass *enum_class = g_type_class_ref (enum_type);
+	guint i;
 
-	enum_class = g_type_class_ref (enum_type);
 	for (i = 0; i < enum_class->n_values; i++) {
-		e = &enum_class->values[i];
-		g_assert (strchr (e->value_nick, '-') == NULL);
-		error_name = g_strdup_printf ("%s.%s", interface, e->value_nick);
-		g_dbus_error_register_error (domain, e->value, error_name);
-		g_free (error_name);
-	}
+		const GEnumValue *e = &enum_class->values[i];
+		char error_name[200];
+
+		nm_assert (e && e->value_nick && !strchr (e->value_nick, '-'));
 
-	g_type_class_unref (enum_class);
+		nm_sprintf_buf (error_name, "%s.%s", interface, e->value_nick);
+		if (!g_dbus_error_register_error (domain, e->value, error_name))
+			nm_assert_not_reached ();
+	}
 }
 
 void
@@ -77,9 +74,6 @@ _nm_dbus_errors_init (void)
 	register_error_domain (NM_SETTINGS_ERROR,
 	                       NM_DBUS_INTERFACE_SETTINGS,
 	                       NM_TYPE_SETTINGS_ERROR);
-	register_error_domain (NM_SETTINGS_ERROR,
-	                       NM_DBUS_INTERFACE_SETTINGS,
-	                       NM_TYPE_SETTINGS_ERROR);
 	register_error_domain (NM_VPN_PLUGIN_ERROR,
 	                       NM_DBUS_VPN_ERROR_PREFIX,
 	                       NM_TYPE_VPN_PLUGIN_ERROR);
diff --git a/libnm-core/nm-errors.h b/libnm-core/nm-errors.h
index ef73790e..7350fe3b 100644
--- a/libnm-core/nm-errors.h
+++ b/libnm-core/nm-errors.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/libnm-core/nm-keyfile-internal.h b/libnm-core/nm-keyfile-internal.h
index 5487f59d..ced4ed97 100644
--- a/libnm-core/nm-keyfile-internal.h
+++ b/libnm-core/nm-keyfile-internal.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -163,6 +162,8 @@ GKeyFile *nm_keyfile_write (NMConnection *connection,
 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);
@@ -170,14 +171,21 @@ 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_NAME_RUN                 NMRUNDIR  "/system-connections"
 
 #define NM_KEYFILE_PATH_SUFFIX_NMCONNECTION      ".nmconnection"
 
-#define NM_KEYFILE_PATH_PREFIX_NMLOADED          ".loaded-"
+#define NM_KEYFILE_PATH_SUFFIX_NMMETA            ".nmmeta"
 
-#define NM_KEYFILE_PATH_NMLOADED_NULL            "/dev/null"
+#define NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL      "/dev/null"
 
 gboolean nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extension);
 
diff --git a/libnm-core/nm-keyfile-utils.c b/libnm-core/nm-keyfile-utils.c
index e243150d..522d5b71 100644
--- a/libnm-core/nm-keyfile-utils.c
+++ b/libnm-core/nm-keyfile-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -29,6 +28,43 @@
 #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.
+ */
+int
+nm_key_file_get_boolean (GKeyFile *kf, const char *group, const char *key, int default_value)
+{
+	gs_free char *value = NULL;
+
+	value = g_key_file_get_value (kf, group, key, NULL);
+
+	if (!value)
+		return default_value;
+	return _nm_utils_ascii_str_to_bool (value, default_value);
+}
+
+/*****************************************************************************/
+
 typedef struct {
 	const char *setting;
 	const char *alias;
@@ -415,7 +451,7 @@ _keyfile_key_encode (const char *name,
 
 	/* See g_key_file_is_key_name().
 	 *
-	 * GKeyfile allows all UTF-8 characters (even non-well formed sequences),
+	 * GKeyFile allows all UTF-8 characters (even non-well formed sequences),
 	 * except:
 	 *  - no empty keys
 	 *  - no leading/trailing ' '
diff --git a/libnm-core/nm-keyfile-utils.h b/libnm-core/nm-keyfile-utils.h
index 9403dfa3..9c5b0143 100644
--- a/libnm-core/nm-keyfile-utils.h
+++ b/libnm-core/nm-keyfile-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/libnm-core/nm-keyfile.c b/libnm-core/nm-keyfile.c
index bf8d2193..85a9ea11 100644
--- a/libnm-core/nm-keyfile.c
+++ b/libnm-core/nm-keyfile.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -364,6 +363,53 @@ read_field (char **current, const char **out_err_str, const char *characters, co
 	}
 }
 
+/*****************************************************************************/
+
+#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 "/;,"
@@ -994,6 +1040,11 @@ read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
 	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;
@@ -1020,6 +1071,7 @@ read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
 					nm_setting_bond_add_option (NM_SETTING_BOND (setting), name, value);
 			}
 		}
+		openconnect_fix_secret_flags (setting);
 		return;
 	}
 
@@ -1041,7 +1093,10 @@ read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
 			                     value);
 		}
 		g_object_set (setting, NM_SETTING_USER_DATA, data, NULL);
+		return;
 	}
+
+	nm_assert_not_reached ();
 }
 
 static gsize
@@ -1611,16 +1666,16 @@ team_config_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key
 	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
-	    && conf[0]
-	    && !nm_utils_is_json_object (conf, &error)) {
+	    && !nm_setting_verify (setting, NULL, &error)) {
 		handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
 		             _("ignoring invalid team configuration: %s"),
 		             error->message);
-		g_clear_pointer (&conf, g_free);
+		g_object_set (G_OBJECT (setting), key, NULL, NULL);
 	}
-
-	g_object_set (G_OBJECT (setting), key, conf, NULL);
 }
 
 static void
@@ -2019,6 +2074,64 @@ bridge_vlan_writer (KeyfileWriterInfo *info,
 	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,
@@ -2131,6 +2244,11 @@ write_hash_of_string (GKeyFile *file,
 	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)) {
@@ -2478,6 +2596,13 @@ static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = {
 			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,
@@ -3412,7 +3537,9 @@ nm_keyfile_read (GKeyFile *keyfile,
 			vpn_secrets = TRUE;
 		} else if (NM_STR_HAS_PREFIX (groups[i], NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER))
 			_read_setting_wireguard_peer (&info);
-		else
+		else if (nm_streq (groups[i], NM_KEYFILE_GROUP_NMMETA)) {
+			/* pass */
+		} else
 			_read_setting (&info);
 
 		info.group = NULL;
@@ -3856,7 +3983,7 @@ nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extensi
 
 	if (require_extension) {
 		if (   l <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)
-		    || !g_str_has_suffix (base, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION))
+		    || !NM_STR_HAS_SUFFIX (base, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION))
 			return TRUE;
 		return FALSE;
 	}
@@ -3865,7 +3992,10 @@ nm_keyfile_utils_ignore_filename (const char *filename, gboolean require_extensi
 	if (base[l - 1] == '~')
 		return TRUE;
 
-	/* Ignore temporary files */
+	/* 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;
 
diff --git a/libnm-core/nm-property-compare.c b/libnm-core/nm-property-compare.c
index d3a19ec7..cb3ee0c2 100644
--- a/libnm-core/nm-property-compare.c
+++ b/libnm-core/nm-property-compare.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-property-compare.h b/libnm-core/nm-property-compare.h
index da2aaf2c..13046caa 100644
--- a/libnm-core/nm-property-compare.h
+++ b/libnm-core/nm-property-compare.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c
index 5114ff74..c571bca8 100644
--- a/libnm-core/nm-setting-8021x.c
+++ b/libnm-core/nm-setting-8021x.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -92,7 +90,7 @@ typedef struct {
 	EAPMethodValidateFunc v_func;
 } EAPMethodsTable;
 
-static EAPMethodsTable eap_methods_table[];
+static const EAPMethodsTable eap_methods_table[];
 
 /*****************************************************************************/
 
@@ -2787,7 +2785,7 @@ need_secrets_phase2 (NMSetting8021x *self,
 	}
 }
 
-static EAPMethodsTable eap_methods_table[] = {
+static const EAPMethodsTable eap_methods_table[] = {
 	{ "leap", need_secrets_password, verify_identity },
 	{ "pwd", need_secrets_password, verify_identity },
 	{ "md5", need_secrets_password, verify_identity },
diff --git a/libnm-core/nm-setting-8021x.h b/libnm-core/nm-setting-8021x.h
index eb7b1948..5a5ae650 100644
--- a/libnm-core/nm-setting-8021x.h
+++ b/libnm-core/nm-setting-8021x.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-adsl.c b/libnm-core/nm-setting-adsl.c
index b2de7027..bd29a988 100644
--- a/libnm-core/nm-setting-adsl.c
+++ b/libnm-core/nm-setting-adsl.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-adsl.h b/libnm-core/nm-setting-adsl.h
index 4f04355e..c92f04ab 100644
--- a/libnm-core/nm-setting-adsl.h
+++ b/libnm-core/nm-setting-adsl.h
@@ -1,5 +1,3 @@
-/* -*- mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-bluetooth.c b/libnm-core/nm-setting-bluetooth.c
index bc4f8bd0..06618740 100644
--- a/libnm-core/nm-setting-bluetooth.c
+++ b/libnm-core/nm-setting-bluetooth.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-bluetooth.h b/libnm-core/nm-setting-bluetooth.h
index 58326702..407dfa76 100644
--- a/libnm-core/nm-setting-bluetooth.h
+++ b/libnm-core/nm-setting-bluetooth.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-bond.c b/libnm-core/nm-setting-bond.c
index 51ce2deb..ee32afb5 100644
--- a/libnm-core/nm-setting-bond.c
+++ b/libnm-core/nm-setting-bond.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -77,8 +75,8 @@ static const BondDefault defaults[] = {
 	{ 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, 3,
-	  { "none", "active", "backup", "all", NULL } },
+	{ 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 } },
@@ -511,6 +509,7 @@ static const struct {
 	{ 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)) },
@@ -854,21 +853,25 @@ options_equal (NMSettingBond *s_bond,
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_BOND_OPTIONS)) {
-		return (   !other
-		        || options_equal (NM_SETTING_BOND (setting),
-		                          NM_SETTING_BOND (other),
+		return (   !set_b
+		        || options_equal (NM_SETTING_BOND (set_a),
+		                          NM_SETTING_BOND (set_b),
 		                          flags));
 	}
 
 	return NM_SETTING_CLASS (nm_setting_bond_parent_class)->compare_property (sett_info,
 	                                                                          property_idx,
-	                                                                          setting,
-	                                                                          other,
+	                                                                          con_a,
+	                                                                          set_a,
+	                                                                          con_b,
+	                                                                          set_b,
 	                                                                          flags);
 }
 
diff --git a/libnm-core/nm-setting-bond.h b/libnm-core/nm-setting-bond.h
index e2d8f9b5..7b932ca9 100644
--- a/libnm-core/nm-setting-bond.h
+++ b/libnm-core/nm-setting-bond.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-bridge-port.c b/libnm-core/nm-setting-bridge-port.c
index 7a8b345f..e15f26a9 100644
--- a/libnm-core/nm-setting-bridge-port.c
+++ b/libnm-core/nm-setting-bridge-port.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -356,8 +354,10 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	NMSettingBridgePortPrivate *priv_a;
@@ -365,9 +365,9 @@ compare_property (const NMSettInfoSetting *sett_info,
 	guint i;
 
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_BRIDGE_PORT_VLANS)) {
-		if (other) {
-			priv_a = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (setting);
-			priv_b = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (other);
+		if (set_b) {
+			priv_a = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (set_a);
+			priv_b = NM_SETTING_BRIDGE_PORT_GET_PRIVATE (set_b);
 
 			if (priv_a->vlans->len != priv_b->vlans->len)
 				return FALSE;
@@ -381,8 +381,10 @@ compare_property (const NMSettInfoSetting *sett_info,
 
 	return NM_SETTING_CLASS (nm_setting_bridge_port_parent_class)->compare_property (sett_info,
 	                                                                                 property_idx,
-	                                                                                 setting,
-	                                                                                 other,
+	                                                                                 con_a,
+	                                                                                 set_a,
+	                                                                                 con_b,
+	                                                                                 set_b,
 	                                                                                 flags);
 }
 
diff --git a/libnm-core/nm-setting-bridge-port.h b/libnm-core/nm-setting-bridge-port.h
index 5b75c1ec..2d75ac0a 100644
--- a/libnm-core/nm-setting-bridge-port.h
+++ b/libnm-core/nm-setting-bridge-port.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-bridge.c b/libnm-core/nm-setting-bridge.c
index c6aca021..23856e5c 100644
--- a/libnm-core/nm-setting-bridge.c
+++ b/libnm-core/nm-setting-bridge.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -997,8 +995,10 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	NMSettingBridgePrivate *priv_a;
@@ -1006,9 +1006,9 @@ compare_property (const NMSettInfoSetting *sett_info,
 	guint i;
 
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_BRIDGE_VLANS)) {
-		if (other) {
-			priv_a = NM_SETTING_BRIDGE_GET_PRIVATE (setting);
-			priv_b = NM_SETTING_BRIDGE_GET_PRIVATE (other);
+		if (set_b) {
+			priv_a = NM_SETTING_BRIDGE_GET_PRIVATE (set_a);
+			priv_b = NM_SETTING_BRIDGE_GET_PRIVATE (set_b);
 
 			if (priv_a->vlans->len != priv_b->vlans->len)
 				return FALSE;
@@ -1022,8 +1022,10 @@ compare_property (const NMSettInfoSetting *sett_info,
 
 	return NM_SETTING_CLASS (nm_setting_bridge_parent_class)->compare_property (sett_info,
 	                                                                            property_idx,
-	                                                                            setting,
-	                                                                            other,
+	                                                                            con_a,
+	                                                                            set_a,
+	                                                                            con_b,
+	                                                                            set_b,
 	                                                                            flags);
 }
 
diff --git a/libnm-core/nm-setting-bridge.h b/libnm-core/nm-setting-bridge.h
index c01ab35c..9d507d53 100644
--- a/libnm-core/nm-setting-bridge.h
+++ b/libnm-core/nm-setting-bridge.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-cdma.c b/libnm-core/nm-setting-cdma.c
index b05daaff..d8c2f462 100644
--- a/libnm-core/nm-setting-cdma.c
+++ b/libnm-core/nm-setting-cdma.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-cdma.h b/libnm-core/nm-setting-cdma.h
index 6e429bd1..1c0d5947 100644
--- a/libnm-core/nm-setting-cdma.h
+++ b/libnm-core/nm-setting-cdma.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c
index e4fb70c2..8c5fe941 100644
--- a/libnm-core/nm-setting-connection.c
+++ b/libnm-core/nm-setting-connection.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -78,6 +76,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSettingConnection,
 	PROP_LLMNR,
 	PROP_STABLE_ID,
 	PROP_AUTH_RETRIES,
+	PROP_WAIT_DEVICE_TIMEOUT,
 );
 
 typedef struct {
@@ -104,6 +103,7 @@ typedef struct {
 	int auth_retries;
 	int mdns;
 	int llmnr;
+	int wait_device_timeout;
 } NMSettingConnectionPrivate;
 
 G_DEFINE_TYPE (NMSettingConnection, nm_setting_connection, NM_TYPE_SETTING)
@@ -592,6 +592,26 @@ nm_setting_connection_get_timestamp (NMSettingConnection *setting)
 	return NM_SETTING_CONNECTION_GET_PRIVATE (setting)->timestamp;
 }
 
+static GVariant *
+_to_dbus_fcn_timestamp (const NMSettInfoSetting *sett_info,
+                        guint property_idx,
+                        NMConnection *connection,
+                        NMSetting *setting,
+                        NMConnectionSerializationFlags flags,
+                        const NMConnectionSerializationOptions *options)
+{
+	guint64 v;
+
+	v =   options && options->timestamp.has
+	    ? options->timestamp.val
+	    : NM_SETTING_CONNECTION_GET_PRIVATE (setting)->timestamp;
+
+	if (v == 0u)
+		return NULL;
+
+	return g_variant_new_uint64 (v);
+}
+
 /**
  * nm_setting_connection_get_read_only:
  * @setting: the #NMSettingConnection
@@ -675,6 +695,23 @@ nm_setting_connection_is_slave_type (NMSettingConnection *setting,
 }
 
 /**
+ * nm_setting_connection_get_wait_device_timeout:
+ * @setting: the #NMSettingConnection
+ *
+ * Returns: the %NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT property with
+ *   the timeout in milli seconds. -1 is the default.
+ *
+ * Since: 1.20
+ */
+gint32
+nm_setting_connection_get_wait_device_timeout (NMSettingConnection *setting)
+{
+	g_return_val_if_fail (NM_IS_SETTING_CONNECTION (setting), -1);
+
+	return NM_SETTING_CONNECTION_GET_PRIVATE (setting)->wait_device_timeout;
+}
+
+/**
  * nm_setting_connection_get_autoconnect_slaves:
  * @setting: the #NMSettingConnection
  *
@@ -1131,6 +1168,20 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
+	if (   priv->wait_device_timeout != -1
+	    && !priv->interface_name) {
+		/* currently, only waiting by interface-name is implemented. Hence reject
+		 * configurations that are not implemented (yet). */
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             _("wait-device-timeout requires %s"),
+		             NM_SETTING_CONNECTION_INTERFACE_NAME);
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_CONNECTION_SETTING_NAME,
+		                NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT);
+		return FALSE;
+	}
+
 	/* *** errors above here should be always fatal, below NORMALIZABLE_ERROR *** */
 
 	if (!priv->uuid) {
@@ -1268,8 +1319,10 @@ nm_setting_connection_no_interface_name (NMSetting *setting,
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	if (   NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_ID)
@@ -1282,8 +1335,10 @@ compare_property (const NMSettInfoSetting *sett_info,
 
 	return NM_SETTING_CLASS (nm_setting_connection_parent_class)->compare_property (sett_info,
 	                                                                                property_idx,
-	                                                                                setting,
-	                                                                                other,
+	                                                                                con_a,
+	                                                                                set_a,
+	                                                                                con_b,
+	                                                                                set_b,
 	                                                                                flags);
 }
 
@@ -1400,6 +1455,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_LLMNR:
 		g_value_set_int (value, priv->llmnr);
 		break;
+	case PROP_WAIT_DEVICE_TIMEOUT:
+		g_value_set_int (value, priv->wait_device_timeout);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1492,6 +1550,9 @@ set_property (GObject *object, guint prop_id,
 	case PROP_LLMNR:
 		priv->llmnr = g_value_get_int (value);
 		break;
+	case PROP_WAIT_DEVICE_TIMEOUT:
+		priv->wait_device_timeout = g_value_get_int (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1507,6 +1568,7 @@ nm_setting_connection_init (NMSettingConnection *setting)
 
 	priv->mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
 	priv->llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
+	priv->wait_device_timeout = -1;
 }
 
 /**
@@ -1873,6 +1935,13 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	                         NM_SETTING_PARAM_FUZZY_IGNORE |
 	                         G_PARAM_STATIC_STRINGS);
 
+	_properties_override_add_override (properties_override,
+	                                   obj_properties[PROP_TIMESTAMP],
+	                                   G_VARIANT_TYPE_UINT64,
+	                                   _to_dbus_fcn_timestamp,
+	                                   NULL,
+	                                   NULL);
+
 	/**
 	 * NMSettingConnection:read-only:
 	 *
@@ -2177,6 +2246,38 @@ nm_setting_connection_class_init (NMSettingConnectionClass *klass)
 	                      G_PARAM_READWRITE |
 	                      G_PARAM_STATIC_STRINGS);
 
+	/**
+	 * NMSettingConnection:wait-device-timeout:
+	 *
+	 * Timeout in milliseconds to wait for device at startup.
+	 * During boot, devices may take a while to be detected by the driver.
+	 * This property will cause to delay NetworkManager-wait-online.service
+	 * and nm-online to give the device a chance to appear.
+	 *
+	 * Note that this property only works together with NMSettingConnection:interface-name
+	 * to identify the device that will be waited for.
+	 *
+	 * The value 0 means no wait time. The default value is -1, which
+	 * currently has the same meaning as no wait time.
+	 *
+	 * Since: 1.20
+	 **/
+	/* ---ifcfg-rh---
+	 * property: wait-device-timeout
+	 * variable: DEVTIMEOUT(+)
+	 * values: timeout in seconds.
+	 * description: for initscripts compatibility, this variable must be
+	 *   a whole integer. If necessary, NetworkManager stores also a fractional
+	 *   component for the milliseconds.
+	 * example: DEVTIMEOUT=5
+	 * ---end---
+	 */
+	obj_properties[PROP_WAIT_DEVICE_TIMEOUT] =
+	    g_param_spec_int (NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, "", "",
+	                      -1, G_MAXINT32, -1,
+	                      G_PARAM_READWRITE |
+	                      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_CONNECTION,
diff --git a/libnm-core/nm-setting-connection.h b/libnm-core/nm-setting-connection.h
index b65fb67b..a3c1334d 100644
--- a/libnm-core/nm-setting-connection.h
+++ b/libnm-core/nm-setting-connection.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -67,6 +65,7 @@ G_BEGIN_DECLS
 #define NM_SETTING_CONNECTION_AUTH_RETRIES   "auth-retries"
 #define NM_SETTING_CONNECTION_MDNS           "mdns"
 #define NM_SETTING_CONNECTION_LLMNR          "llmnr"
+#define NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT "wait-device-timeout"
 
 /* Types for property values */
 /**
@@ -216,6 +215,9 @@ NMSettingConnectionMdns   nm_setting_connection_get_mdns (NMSettingConnection *s
 NM_AVAILABLE_IN_1_14
 NMSettingConnectionLlmnr  nm_setting_connection_get_llmnr (NMSettingConnection *setting);
 
+NM_AVAILABLE_IN_1_20
+gint32 nm_setting_connection_get_wait_device_timeout (NMSettingConnection *setting);
+
 G_END_DECLS
 
 #endif /* __NM_SETTING_CONNECTION_H__ */
diff --git a/libnm-core/nm-setting-dcb.c b/libnm-core/nm-setting-dcb.c
index 419d9825..9feb4ea2 100644
--- a/libnm-core/nm-setting-dcb.c
+++ b/libnm-core/nm-setting-dcb.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-dcb.h b/libnm-core/nm-setting-dcb.h
index 92ca2682..842f5303 100644
--- a/libnm-core/nm-setting-dcb.h
+++ b/libnm-core/nm-setting-dcb.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-dummy.c b/libnm-core/nm-setting-dummy.c
index bd85a74e..6a2a5d0f 100644
--- a/libnm-core/nm-setting-dummy.c
+++ b/libnm-core/nm-setting-dummy.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-dummy.h b/libnm-core/nm-setting-dummy.h
index 62fe3fa6..1fe2bbf7 100644
--- a/libnm-core/nm-setting-dummy.h
+++ b/libnm-core/nm-setting-dummy.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-ethtool.c b/libnm-core/nm-setting-ethtool.c
index 827e3a62..555ac34d 100644
--- a/libnm-core/nm-setting-ethtool.c
+++ b/libnm-core/nm-setting-ethtool.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -40,13 +38,17 @@
 
 /**
  * nm_ethtool_optname_is_feature:
- * @optname: the option name to check
+ * @optname: (allow-none): the option name to check
  *
  * Checks whether @optname is a valid option name for an offload feature.
  *
  * %Returns: %TRUE, if @optname is valid
  *
- * Since: 1.14
+ * Since: 1.20
+ *
+ * Note that nm_ethtool_optname_is_feature() was first added to the libnm header files
+ * in 1.14.0 but forgot to actually add to the library. This happened belatedly in 1.20.0 and
+ * the stable versions 1.18.2, 1.16.4 and 1.14.8 (with linker version "libnm_1_14_8").
  */
 gboolean
 nm_ethtool_optname_is_feature (const char *optname)
@@ -93,6 +95,9 @@ _notify_attributes (NMSettingEthtool *self)
  * Gets and offload feature setting. Returns %NM_TERNARY_DEFAULT if the
  * feature is not set.
  *
+ * Note that @optname must be a valid name for a feature, according to
+ * nm_ethtool_optname_is_feature().
+ *
  * Returns: a #NMTernary value indicating whether the offload feature
  *   is enabled, disabled, or left untouched.
  *
@@ -126,6 +131,9 @@ nm_setting_ethtool_get_feature (NMSettingEthtool *setting,
  *
  * Sets and offload feature setting.
  *
+ * Note that @optname must be a valid name for a feature, according to
+ * nm_ethtool_optname_is_feature().
+ *
  * Since: 1.14
  */
 void
@@ -247,6 +255,34 @@ nm_setting_ethtool_init_features (NMSettingEthtool *setting,
 
 /*****************************************************************************/
 
+/**
+ * nm_setting_ethtool_get_optnames:
+ * @setting: the #NMSettingEthtool instance.
+ * @out_length: (out) (optional): return location for the number of keys returned, or %NULL
+ *
+ * This returns all options names that are set. This includes the feature names
+ * like %NM_ETHTOOL_OPTNAME_FEATURE_GRO. See nm_ethtool_optname_is_feature() to
+ * check whether the option name is valid for offload features.
+ *
+ * Returns: (array zero-terminated=1) (transfer container): list of set option
+ *   names or %NULL if no options are set. The option names are still owned by
+ *   @setting and may get invalidated when @setting gets modified.
+ *
+ * Since: 1.20
+ */
+const char **
+nm_setting_ethtool_get_optnames (NMSettingEthtool *setting,
+                                 guint *out_length)
+{
+	g_return_val_if_fail (NM_IS_SETTING_ETHTOOL (setting), NULL);
+
+	return nm_utils_strdict_get_keys (_nm_setting_gendata_hash (NM_SETTING (setting), FALSE),
+	                                  TRUE,
+	                                  out_length);
+}
+
+/*****************************************************************************/
+
 static gboolean
 verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
diff --git a/libnm-core/nm-setting-ethtool.h b/libnm-core/nm-setting-ethtool.h
index 66a94489..7b747be9 100644
--- a/libnm-core/nm-setting-ethtool.h
+++ b/libnm-core/nm-setting-ethtool.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -84,7 +83,7 @@ G_BEGIN_DECLS
 #define NM_ETHTOOL_OPTNAME_FEATURE_TX_UDP_TNL_SEGMENTATION      "feature-tx-udp_tnl-segmentation"
 #define NM_ETHTOOL_OPTNAME_FEATURE_TX_VLAN_STAG_HW_INSERT       "feature-tx-vlan-stag-hw-insert"
 
-NM_AVAILABLE_IN_1_14
+NM_AVAILABLE_IN_1_20
 gboolean nm_ethtool_optname_is_feature (const char *optname);
 
 /*****************************************************************************/
@@ -120,6 +119,10 @@ void              nm_setting_ethtool_set_feature (NMSettingEthtool *setting,
 NM_AVAILABLE_IN_1_14
 void              nm_setting_ethtool_clear_features (NMSettingEthtool *setting);
 
+NM_AVAILABLE_IN_1_20
+const char **     nm_setting_ethtool_get_optnames (NMSettingEthtool *setting,
+                                                   guint *out_length);
+
 G_END_DECLS
 
 #endif /* __NM_SETTING_ETHTOOL_H__ */
diff --git a/libnm-core/nm-setting-generic.c b/libnm-core/nm-setting-generic.c
index fe5ce3ca..95f42de2 100644
--- a/libnm-core/nm-setting-generic.c
+++ b/libnm-core/nm-setting-generic.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-generic.h b/libnm-core/nm-setting-generic.h
index 864b9004..15f40231 100644
--- a/libnm-core/nm-setting-generic.h
+++ b/libnm-core/nm-setting-generic.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-gsm.c b/libnm-core/nm-setting-gsm.c
index e6784e42..bf2e7b23 100644
--- a/libnm-core/nm-setting-gsm.c
+++ b/libnm-core/nm-setting-gsm.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-gsm.h b/libnm-core/nm-setting-gsm.h
index 0763b9df..8dc0fb48 100644
--- a/libnm-core/nm-setting-gsm.h
+++ b/libnm-core/nm-setting-gsm.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-infiniband.c b/libnm-core/nm-setting-infiniband.c
index bde7eda9..dc3bcdd5 100644
--- a/libnm-core/nm-setting-infiniband.c
+++ b/libnm-core/nm-setting-infiniband.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-infiniband.h b/libnm-core/nm-setting-infiniband.h
index ff78b3dc..ea8ba47c 100644
--- a/libnm-core/nm-setting-infiniband.h
+++ b/libnm-core/nm-setting-infiniband.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-ip-config.c b/libnm-core/nm-setting-ip-config.c
index f362945f..458d93b3 100644
--- a/libnm-core/nm-setting-ip-config.c
+++ b/libnm-core/nm-setting-ip-config.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -1212,25 +1210,22 @@ nm_ip_route_set_attribute (NMIPRoute *route, const char *name, GVariant *value)
 		g_hash_table_remove (route->attributes, name);
 }
 
-#define ATTR_SPEC_PTR(name, type, v4, v6, str_type) \
-	&(NMVariantAttributeSpec) { name, type, v4, v6, FALSE, FALSE, str_type }
-
-static const NMVariantAttributeSpec * const ip_route_attribute_spec[] = {
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_TABLE,           G_VARIANT_TYPE_UINT32,   TRUE,  TRUE,  0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_SRC,             G_VARIANT_TYPE_STRING,   TRUE,  TRUE, 'a'),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_FROM,            G_VARIANT_TYPE_STRING,   FALSE, TRUE, 'p'),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_TOS,             G_VARIANT_TYPE_BYTE,     TRUE,  FALSE, 0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_ONLINK,          G_VARIANT_TYPE_BOOLEAN,  TRUE,  TRUE,  0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_WINDOW,          G_VARIANT_TYPE_UINT32,   TRUE,  TRUE,  0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_CWND,            G_VARIANT_TYPE_UINT32,   TRUE,  TRUE,  0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_INITCWND,        G_VARIANT_TYPE_UINT32,   TRUE,  TRUE,  0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_INITRWND,        G_VARIANT_TYPE_UINT32,   TRUE,  TRUE,  0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_MTU,             G_VARIANT_TYPE_UINT32,   TRUE,  TRUE,  0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_LOCK_WINDOW,     G_VARIANT_TYPE_BOOLEAN,  TRUE,  TRUE,  0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND,       G_VARIANT_TYPE_BOOLEAN,  TRUE,  TRUE,  0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_LOCK_INITCWND,   G_VARIANT_TYPE_BOOLEAN,  TRUE,  TRUE,  0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_LOCK_INITRWND,   G_VARIANT_TYPE_BOOLEAN,  TRUE,  TRUE,  0 ),
-	ATTR_SPEC_PTR (NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU,        G_VARIANT_TYPE_BOOLEAN,  TRUE,  TRUE,  0 ),
+static const NMVariantAttributeSpec *const ip_route_attribute_spec[] = {
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_TABLE,         G_VARIANT_TYPE_UINT32,  .v4 = TRUE, .v6 = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_SRC,           G_VARIANT_TYPE_STRING,  .v4 = TRUE, .v6 = TRUE, .str_type = 'a', ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_FROM,          G_VARIANT_TYPE_STRING,              .v6 = TRUE, .str_type = 'p', ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_TOS,           G_VARIANT_TYPE_BYTE,    .v4 = TRUE,                              ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_ONLINK,        G_VARIANT_TYPE_BOOLEAN, .v4 = TRUE, .v6 = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_WINDOW,        G_VARIANT_TYPE_UINT32,  .v4 = TRUE, .v6 = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_CWND,          G_VARIANT_TYPE_UINT32,  .v4 = TRUE, .v6 = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_INITCWND,      G_VARIANT_TYPE_UINT32,  .v4 = TRUE, .v6 = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_INITRWND,      G_VARIANT_TYPE_UINT32,  .v4 = TRUE, .v6 = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_MTU,           G_VARIANT_TYPE_UINT32,  .v4 = TRUE, .v6 = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_LOCK_WINDOW,   G_VARIANT_TYPE_BOOLEAN, .v4 = TRUE, .v6 = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND,     G_VARIANT_TYPE_BOOLEAN, .v4 = TRUE, .v6 = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_LOCK_INITCWND, G_VARIANT_TYPE_BOOLEAN, .v4 = TRUE, .v6 = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_LOCK_INITRWND, G_VARIANT_TYPE_BOOLEAN, .v4 = TRUE, .v6 = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU,      G_VARIANT_TYPE_BOOLEAN, .v4 = TRUE, .v6 = TRUE,                  ),
 	NULL,
 };
 
@@ -1397,6 +1392,7 @@ struct NMIPRoutingRule {
 	guint ref_count;
 	guint32 priority;
 	guint32 table;
+	gint32 suppress_prefixlength;
 	guint32 fwmark;
 	guint32 fwmask;
 	guint16 sport_start;
@@ -1477,10 +1473,11 @@ nm_ip_routing_rule_new (int addr_family)
 
 	self = g_slice_new (NMIPRoutingRule);
 	*self = (NMIPRoutingRule) {
-		.ref_count    = 1,
-		.is_v4        = (addr_family == AF_INET),
-		.action       = FR_ACT_TO_TBL,
-		.table        = RT_TABLE_MAIN,
+		.ref_count             = 1,
+		.is_v4                 = (addr_family == AF_INET),
+		.action                = FR_ACT_TO_TBL,
+		.table                 = RT_TABLE_MAIN,
+		.suppress_prefixlength = -1,
 	};
 	return self;
 }
@@ -1504,50 +1501,52 @@ nm_ip_routing_rule_new_clone (const NMIPRoutingRule *rule)
 
 	self = g_slice_new (NMIPRoutingRule);
 	*self = (NMIPRoutingRule) {
-		.ref_count    = 1,
-		.sealed       = FALSE,
-		.is_v4        = rule->is_v4,
-
-		.priority     = rule->priority,
-		.priority_has = rule->priority_has,
-
-		.invert       = rule->invert,
-
-		.tos          = rule->tos,
-
-		.fwmark       = rule->fwmark,
-		.fwmask       = rule->fwmask,
-
-		.sport_start  = rule->sport_start,
-		.sport_end    = rule->sport_end,
-		.dport_start  = rule->dport_start,
-		.dport_end    = rule->dport_end,
-
-		.ipproto      = rule->ipproto,
-
-		.from_len     = rule->from_len,
-		.from_bin     = rule->from_bin,
-		.from_str     =   (   rule->from_has
-		                   && !rule->from_valid)
-		                ? g_strdup (rule->from_str)
-		                : NULL,
-		.from_has     = rule->from_has,
-		.from_valid   = rule->from_valid,
-
-		.to_len       = rule->to_len,
-		.to_bin       = rule->to_bin,
-		.to_str       =   (   rule->to_has
-		                   && !rule->to_valid)
-		                ? g_strdup (rule->to_str)
-		                : NULL,
-		.to_has       = rule->to_has,
-		.to_valid     = rule->to_valid,
-
-		.iifname      = g_strdup (rule->iifname),
-		.oifname      = g_strdup (rule->oifname),
-
-		.action       = rule->action,
-		.table        = rule->table,
+		.ref_count             = 1,
+		.sealed                = FALSE,
+		.is_v4                 = rule->is_v4,
+
+		.priority              = rule->priority,
+		.priority_has          = rule->priority_has,
+
+		.invert                = rule->invert,
+
+		.tos                   = rule->tos,
+
+		.fwmark                = rule->fwmark,
+		.fwmask                = rule->fwmask,
+
+		.sport_start           = rule->sport_start,
+		.sport_end             = rule->sport_end,
+		.dport_start           = rule->dport_start,
+		.dport_end             = rule->dport_end,
+
+		.ipproto               = rule->ipproto,
+
+		.from_len              = rule->from_len,
+		.from_bin              = rule->from_bin,
+		.from_str              =   (   rule->from_has
+		                            && !rule->from_valid)
+		                         ? g_strdup (rule->from_str)
+		                         : NULL,
+		.from_has              = rule->from_has,
+		.from_valid            = rule->from_valid,
+
+		.to_len                = rule->to_len,
+		.to_bin                = rule->to_bin,
+		.to_str                =   (   rule->to_has
+		                            && !rule->to_valid)
+		                         ? g_strdup (rule->to_str)
+		                         : NULL,
+		.to_has                = rule->to_has,
+		.to_valid              = rule->to_valid,
+
+		.iifname               = g_strdup (rule->iifname),
+		.oifname               = g_strdup (rule->oifname),
+
+		.action                = rule->action,
+		.table                 = rule->table,
+
+		.suppress_prefixlength = rule->suppress_prefixlength,
 	};
 	return self;
 }
@@ -1628,7 +1627,7 @@ nm_ip_routing_rule_is_sealed (const NMIPRoutingRule *self)
  * @self: the #NMIPRoutingRule instance
  *
  * Seals the routing rule. Afterwards, the instance can no longer be
- * modfied, and it is a bug to call any of the accessors that would
+ * modified, and it is a bug to call any of the accessors that would
  * modify the rule. If @self was already sealed, this has no effect.
  *
  * Since: 1.18
@@ -2215,7 +2214,7 @@ nm_ip_routing_rule_get_xifname_bin (const NMIPRoutingRule *self,
  * The name supports C backslash escaping for non-UTF-8 characters.
  * Note that nm_ip_routing_rule_from_string() too uses backslash
  * escaping when tokenizing the words by whitespace. So, in string
- * representation you'd get double backslashs.
+ * representation you'd get double backslashes.
  *
  * Since: 1.18
  */
@@ -2252,7 +2251,7 @@ nm_ip_routing_rule_get_oifname (const NMIPRoutingRule *self)
  * The name supports C backslash escaping for non-UTF-8 characters.
  * Note that nm_ip_routing_rule_from_string() too uses backslash
  * escaping when tokenizing the words by whitespace. So, in string
- * representation you'd get double backslashs.
+ * representation you'd get double backslashes.
  *
  * Since: 1.18
  */
@@ -2331,6 +2330,38 @@ nm_ip_routing_rule_set_table (NMIPRoutingRule *self, guint32 table)
 }
 
 /**
+ * nm_ip_routing_rule_get_suppress_prefixlength:
+ * @self: the #NMIPRoutingRule instance
+ *
+ * Returns: the suppress_prefixlength of the rule. -1 means that the value is unset.
+ *
+ * Since: 1.20
+ */
+gint32
+nm_ip_routing_rule_get_suppress_prefixlength (const NMIPRoutingRule *self)
+{
+	g_return_val_if_fail (NM_IS_IP_ROUTING_RULE (self, TRUE), -1);
+
+	return self->suppress_prefixlength;
+}
+
+/**
+ * nm_ip_routing_rule_set_suppress_prefixlength:
+ * @self: the #NMIPRoutingRule instance
+ * @suppress_prefixlength: the suppress_prefixlength to set. The value -1 means
+ *   unset.
+ *
+ * Since: 1.20
+ */
+void
+nm_ip_routing_rule_set_suppress_prefixlength (NMIPRoutingRule *self, gint32 suppress_prefixlength)
+{
+	g_return_if_fail (NM_IS_IP_ROUTING_RULE (self, FALSE));
+
+	self->suppress_prefixlength = suppress_prefixlength;
+}
+
+/**
  * nm_ip_routing_rule_cmp:
  * @rule: (allow-none): the #NMIPRoutingRule instance to compare
  * @other: (allow-none): the other #NMIPRoutingRule instance to compare
@@ -2366,6 +2397,8 @@ nm_ip_routing_rule_cmp (const NMIPRoutingRule *rule,
 
 	NM_CMP_FIELD (rule, other, table);
 
+	NM_CMP_FIELD (rule, other, suppress_prefixlength);
+
 	NM_CMP_FIELD (rule, other, sport_start);
 	NM_CMP_FIELD (rule, other, sport_end);
 	NM_CMP_FIELD (rule, other, dport_start);
@@ -2576,6 +2609,20 @@ nm_ip_routing_rule_validate (const NMIPRoutingRule *self,
 		return FALSE;
 	}
 
+	if (self->suppress_prefixlength != -1) {
+		if (   self->suppress_prefixlength < -1
+		    || self->suppress_prefixlength > (self->is_v4 ? 32 : 128)) {
+			g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("suppress_prefixlength out of range"));
+			return FALSE;
+		}
+		if (self->action != FR_ACT_TO_TBL) {
+			g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("suppress_prefixlength is only allowed with the to-table action"));
+			return FALSE;
+		}
+	}
+
 	return TRUE;
 }
 
@@ -2598,6 +2645,7 @@ typedef enum {
 	RR_DBUS_ATTR_SPORT_END,
 	RR_DBUS_ATTR_SPORT_START,
 	RR_DBUS_ATTR_TABLE,
+	RR_DBUS_ATTR_SUPPRESS_PREFIXLENGTH,
 	RR_DBUS_ATTR_TO,
 	RR_DBUS_ATTR_TOS,
 	RR_DBUS_ATTR_TO_LEN,
@@ -2612,25 +2660,26 @@ typedef struct {
 
 static const RRDbusData rr_dbus_data[_RR_DBUS_ATTR_NUM] = {
 #define _D(attr, _name, type) [attr] = { .name = _name, .dbus_type = type, }
-	_D (RR_DBUS_ATTR_ACTION,      NM_IP_ROUTING_RULE_ATTR_ACTION,      G_VARIANT_TYPE_BYTE),
-	_D (RR_DBUS_ATTR_DPORT_END,   NM_IP_ROUTING_RULE_ATTR_DPORT_END,   G_VARIANT_TYPE_UINT16),
-	_D (RR_DBUS_ATTR_DPORT_START, NM_IP_ROUTING_RULE_ATTR_DPORT_START, G_VARIANT_TYPE_UINT16),
-	_D (RR_DBUS_ATTR_FAMILY,      NM_IP_ROUTING_RULE_ATTR_FAMILY,      G_VARIANT_TYPE_INT32),
-	_D (RR_DBUS_ATTR_FROM,        NM_IP_ROUTING_RULE_ATTR_FROM,        G_VARIANT_TYPE_STRING),
-	_D (RR_DBUS_ATTR_FROM_LEN,    NM_IP_ROUTING_RULE_ATTR_FROM_LEN,    G_VARIANT_TYPE_BYTE),
-	_D (RR_DBUS_ATTR_FWMARK,      NM_IP_ROUTING_RULE_ATTR_FWMARK,      G_VARIANT_TYPE_UINT32),
-	_D (RR_DBUS_ATTR_FWMASK,      NM_IP_ROUTING_RULE_ATTR_FWMASK,      G_VARIANT_TYPE_UINT32),
-	_D (RR_DBUS_ATTR_IIFNAME,     NM_IP_ROUTING_RULE_ATTR_IIFNAME,     G_VARIANT_TYPE_STRING),
-	_D (RR_DBUS_ATTR_INVERT,      NM_IP_ROUTING_RULE_ATTR_INVERT,      G_VARIANT_TYPE_BOOLEAN),
-	_D (RR_DBUS_ATTR_IPPROTO,     NM_IP_ROUTING_RULE_ATTR_IPPROTO,     G_VARIANT_TYPE_BYTE),
-	_D (RR_DBUS_ATTR_OIFNAME,     NM_IP_ROUTING_RULE_ATTR_OIFNAME,     G_VARIANT_TYPE_STRING),
-	_D (RR_DBUS_ATTR_PRIORITY,    NM_IP_ROUTING_RULE_ATTR_PRIORITY,    G_VARIANT_TYPE_UINT32),
-	_D (RR_DBUS_ATTR_SPORT_END,   NM_IP_ROUTING_RULE_ATTR_SPORT_END,   G_VARIANT_TYPE_UINT16),
-	_D (RR_DBUS_ATTR_SPORT_START, NM_IP_ROUTING_RULE_ATTR_SPORT_START, G_VARIANT_TYPE_UINT16),
-	_D (RR_DBUS_ATTR_TABLE,       NM_IP_ROUTING_RULE_ATTR_TABLE,       G_VARIANT_TYPE_UINT32),
-	_D (RR_DBUS_ATTR_TO,          NM_IP_ROUTING_RULE_ATTR_TO,          G_VARIANT_TYPE_STRING),
-	_D (RR_DBUS_ATTR_TOS,         NM_IP_ROUTING_RULE_ATTR_TOS,         G_VARIANT_TYPE_BYTE),
-	_D (RR_DBUS_ATTR_TO_LEN,      NM_IP_ROUTING_RULE_ATTR_TO_LEN,      G_VARIANT_TYPE_BYTE),
+	_D (RR_DBUS_ATTR_ACTION,                NM_IP_ROUTING_RULE_ATTR_ACTION,                G_VARIANT_TYPE_BYTE),
+	_D (RR_DBUS_ATTR_DPORT_END,             NM_IP_ROUTING_RULE_ATTR_DPORT_END,             G_VARIANT_TYPE_UINT16),
+	_D (RR_DBUS_ATTR_DPORT_START,           NM_IP_ROUTING_RULE_ATTR_DPORT_START,           G_VARIANT_TYPE_UINT16),
+	_D (RR_DBUS_ATTR_FAMILY,                NM_IP_ROUTING_RULE_ATTR_FAMILY,                G_VARIANT_TYPE_INT32),
+	_D (RR_DBUS_ATTR_FROM,                  NM_IP_ROUTING_RULE_ATTR_FROM,                  G_VARIANT_TYPE_STRING),
+	_D (RR_DBUS_ATTR_FROM_LEN,              NM_IP_ROUTING_RULE_ATTR_FROM_LEN,              G_VARIANT_TYPE_BYTE),
+	_D (RR_DBUS_ATTR_FWMARK,                NM_IP_ROUTING_RULE_ATTR_FWMARK,                G_VARIANT_TYPE_UINT32),
+	_D (RR_DBUS_ATTR_FWMASK,                NM_IP_ROUTING_RULE_ATTR_FWMASK,                G_VARIANT_TYPE_UINT32),
+	_D (RR_DBUS_ATTR_IIFNAME,               NM_IP_ROUTING_RULE_ATTR_IIFNAME,               G_VARIANT_TYPE_STRING),
+	_D (RR_DBUS_ATTR_INVERT,                NM_IP_ROUTING_RULE_ATTR_INVERT,                G_VARIANT_TYPE_BOOLEAN),
+	_D (RR_DBUS_ATTR_IPPROTO,               NM_IP_ROUTING_RULE_ATTR_IPPROTO,               G_VARIANT_TYPE_BYTE),
+	_D (RR_DBUS_ATTR_OIFNAME,               NM_IP_ROUTING_RULE_ATTR_OIFNAME,               G_VARIANT_TYPE_STRING),
+	_D (RR_DBUS_ATTR_PRIORITY,              NM_IP_ROUTING_RULE_ATTR_PRIORITY,              G_VARIANT_TYPE_UINT32),
+	_D (RR_DBUS_ATTR_SPORT_END,             NM_IP_ROUTING_RULE_ATTR_SPORT_END,             G_VARIANT_TYPE_UINT16),
+	_D (RR_DBUS_ATTR_SPORT_START,           NM_IP_ROUTING_RULE_ATTR_SPORT_START,           G_VARIANT_TYPE_UINT16),
+	_D (RR_DBUS_ATTR_SUPPRESS_PREFIXLENGTH, NM_IP_ROUTING_RULE_ATTR_SUPPRESS_PREFIXLENGTH, G_VARIANT_TYPE_INT32),
+	_D (RR_DBUS_ATTR_TABLE,                 NM_IP_ROUTING_RULE_ATTR_TABLE,                 G_VARIANT_TYPE_UINT32),
+	_D (RR_DBUS_ATTR_TO,                    NM_IP_ROUTING_RULE_ATTR_TO,                    G_VARIANT_TYPE_STRING),
+	_D (RR_DBUS_ATTR_TOS,                   NM_IP_ROUTING_RULE_ATTR_TOS,                   G_VARIANT_TYPE_BYTE),
+	_D (RR_DBUS_ATTR_TO_LEN,                NM_IP_ROUTING_RULE_ATTR_TO_LEN,                G_VARIANT_TYPE_BYTE),
 #undef _D
 };
 
@@ -2793,6 +2842,9 @@ nm_ip_routing_rule_from_dbus (GVariant *variant,
 	if (variants[RR_DBUS_ATTR_TABLE])
 		nm_ip_routing_rule_set_table (self, g_variant_get_uint32 (variants[RR_DBUS_ATTR_TABLE]));
 
+	if (variants[RR_DBUS_ATTR_SUPPRESS_PREFIXLENGTH])
+		nm_ip_routing_rule_set_suppress_prefixlength (self, g_variant_get_int32 (variants[RR_DBUS_ATTR_SUPPRESS_PREFIXLENGTH]));
+
 	if (   strict
 	    && !nm_ip_routing_rule_validate (self, error))
 		return NULL;
@@ -2894,6 +2946,9 @@ nm_ip_routing_rule_to_dbus (const NMIPRoutingRule *self)
 	if (self->table != 0)
 		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_TABLE, g_variant_new_uint32 (self->table));
 
+	if (self->suppress_prefixlength != -1)
+		_rr_to_dbus_add (&builder, RR_DBUS_ATTR_SUPPRESS_PREFIXLENGTH, g_variant_new_int32 (self->suppress_prefixlength));
+
 	return g_variant_builder_end (&builder);;
 }
 
@@ -2970,6 +3025,7 @@ nm_ip_routing_rule_from_string (const char *str,
 	gint64 i64_fwmask = -1;
 	gint64 i64_sport_start = -1;
 	gint64 i64_ipproto = -1;
+	gint64 i64_suppress_prefixlength = -1;
 	guint16 sport_end = 0;
 	gint64 i64_dport_start = -1;
 	guint16 dport_end = 0;
@@ -2997,7 +3053,8 @@ nm_ip_routing_rule_from_string (const char *str,
 	 *   Of course, valid rules can be converted to string and read back the same (round-trip).
 	 *
 	 * - iproute2 in may regards is flexible about the command lines. For example
-	 *   - for tables it accepts table names from /etc/iproute2/rt_tables
+	 *   - for tables it accepts table names from /etc/iproute2/rt_tables. We only
+	 *     accept the special aliases "main", "local", and "default".
 	 *   - key names like "preference" can be abbreviated to "prefe", we don't do that.
 	 *   - the "preference"/"priority" may be unspecified, in which kernel automatically
 	 *     chooses an unused priority (during `ip rule add`). We don't allow for that, the
@@ -3077,8 +3134,16 @@ nm_ip_routing_rule_from_string (const char *str,
 			if (i64_table != -1)
 				goto next_fail_word0_duplicate_key;
 			i64_table = _nm_utils_ascii_str_to_int64 (word1, 0, 1, G_MAXUINT32, -1);
-			if (i64_table == -1)
-				goto next_fail_word1_invalid_value;
+			if (i64_table == -1) {
+				if (nm_streq (word1, "main"))
+					i64_table = RT_TABLE_MAIN;
+				else if (nm_streq (word1, "local"))
+					i64_table = RT_TABLE_LOCAL;
+				else if (nm_streq (word1, "default"))
+					i64_table = RT_TABLE_DEFAULT;
+				else
+					goto next_fail_word1_invalid_value;
+			}
 			goto next_words_consumed;
 		}
 		if (NM_IN_STRSET (word0, "tos",
@@ -3156,6 +3221,17 @@ nm_ip_routing_rule_from_string (const char *str,
 			word_oifname = word1;
 			goto next_words_consumed;
 		}
+		if (NM_IN_STRSET (word0, "suppress_prefixlength",
+		                         "sup_pl")) {
+			if (!word1)
+				continue;
+			if (i64_suppress_prefixlength != -1)
+				goto next_fail_word0_duplicate_key;
+			i64_suppress_prefixlength = _nm_utils_ascii_str_to_int64 (word1, 0, 0, G_MAXINT32, -1);;
+			if (i64_suppress_prefixlength == -1)
+				goto next_fail_word1_invalid_value;
+			goto next_words_consumed;
+		}
 
 		/* also the action is still unsupported. For the moment, we only support
 		 * FR_ACT_TO_TBL, which is the default (by not expressing it on the command
@@ -3249,6 +3325,9 @@ next_words_consumed:
 	if (i64_dport_start != -1)
 		nm_ip_routing_rule_set_destination_port (self, i64_dport_start, dport_end);
 
+	if (i64_suppress_prefixlength != -1)
+		nm_ip_routing_rule_set_suppress_prefixlength (self, i64_suppress_prefixlength);
+
 	if (   val_from_len > 0
 	    || (   val_from_len == 0
 	        && !nm_ip_addr_is_null (addr_family, &val_from))) {
@@ -3485,6 +3564,12 @@ nm_ip_routing_rule_to_string (const NMIPRoutingRule *self,
 		                        (guint) self->table);
 	}
 
+	if (self->suppress_prefixlength != -1) {
+		g_string_append_printf (nm_gstring_add_space_delimiter (str),
+		                        "suppress_prefixlength %d",
+		                        (int) self->suppress_prefixlength);
+	}
+
 	return g_string_free (g_steal_pointer (&str), FALSE);
 }
 
@@ -4555,7 +4640,8 @@ _routing_rules_dbus_only_synth (const NMSettInfoSetting *sett_info,
                                 guint property_idx,
                                 NMConnection *connection,
                                 NMSetting *setting,
-                                NMConnectionSerializationFlags flags)
+                                NMConnectionSerializationFlags flags,
+                                const NMConnectionSerializationOptions *options)
 {
 	NMSettingIPConfig *self = NM_SETTING_IP_CONFIG (setting);
 	NMSettingIPConfigPrivate *priv;
@@ -4969,8 +5055,10 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	NMSettingIPConfigPrivate *a_priv;
@@ -4978,9 +5066,9 @@ compare_property (const NMSettInfoSetting *sett_info,
 	guint i;
 
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_IP_CONFIG_ADDRESSES)) {
-		if (other) {
-			a_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
-			b_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (other);
+		if (set_b) {
+			a_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (set_a);
+			b_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (set_b);
 
 			if (a_priv->addresses->len != b_priv->addresses->len)
 				return FALSE;
@@ -4993,9 +5081,9 @@ compare_property (const NMSettInfoSetting *sett_info,
 	}
 
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_IP_CONFIG_ROUTES)) {
-		if (other) {
-			a_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
-			b_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (other);
+		if (set_b) {
+			a_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (set_a);
+			b_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (set_b);
 
 			if (a_priv->routes->len != b_priv->routes->len)
 				return FALSE;
@@ -5008,11 +5096,11 @@ compare_property (const NMSettInfoSetting *sett_info,
 	}
 
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_IP_CONFIG_ROUTING_RULES)) {
-		if (other) {
+		if (set_b) {
 			guint n;
 
-			a_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (setting);
-			b_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (other);
+			a_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (set_a);
+			b_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE (set_b);
 
 			n = (a_priv->routing_rules) ? a_priv->routing_rules->len : 0u;
 			if (n != (b_priv->routing_rules ? b_priv->routing_rules->len : 0u))
@@ -5027,8 +5115,10 @@ compare_property (const NMSettInfoSetting *sett_info,
 
 	return NM_SETTING_CLASS (nm_setting_ip_config_parent_class)->compare_property (sett_info,
 	                                                                               property_idx,
-	                                                                               setting,
-	                                                                               other,
+	                                                                               con_a,
+	                                                                               set_a,
+	                                                                               con_b,
+	                                                                               set_b,
 	                                                                               flags);
 }
 
@@ -5377,9 +5467,9 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *klass)
 	 *
 	 * IP configuration method.
 	 *
-	 * #NMSettingIP4Config and #NMSettingIP6Config both support "auto",
-	 * "manual", and "link-local". See the subclass-specific documentation for
-	 * other values.
+	 * #NMSettingIP4Config and #NMSettingIP6Config both support "disabled",
+	 * "auto", "manual", and "link-local". See the subclass-specific
+	 * documentation for other values.
 	 *
 	 * In general, for the "auto" method, properties such as
 	 * #NMSettingIPConfig:dns and #NMSettingIPConfig:routes specify information
@@ -5467,7 +5557,7 @@ nm_setting_ip_config_class_init (NMSettingIPConfigClass *klass)
 	 * so in presence of at least a negative priority, only DNS servers from
 	 * connections with the lowest priority value will be used.
 	 *
-	 * When using a DNS resolver that supports split-DNS as dns=dnsmasq or
+	 * When using a DNS resolver that supports Conditional Forwarding as dns=dnsmasq or
 	 * dns=systemd-resolved, each connection is used to query domains in its
 	 * search list.  Queries for domains not present in any search list are
 	 * routed through connections having the '~.' special wildcard domain, which
diff --git a/libnm-core/nm-setting-ip-config.h b/libnm-core/nm-setting-ip-config.h
index 76a94914..4a522367 100644
--- a/libnm-core/nm-setting-ip-config.h
+++ b/libnm-core/nm-setting-ip-config.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -264,6 +262,11 @@ guint32 nm_ip_routing_rule_get_table (const NMIPRoutingRule *self);
 NM_AVAILABLE_IN_1_18
 void nm_ip_routing_rule_set_table (NMIPRoutingRule *self, guint32 table);
 
+NM_AVAILABLE_IN_1_20
+gint32 nm_ip_routing_rule_get_suppress_prefixlength (const NMIPRoutingRule *self);
+NM_AVAILABLE_IN_1_20
+void nm_ip_routing_rule_set_suppress_prefixlength (NMIPRoutingRule *self, gint32 suppress_prefixlength);
+
 NM_AVAILABLE_IN_1_18
 int nm_ip_routing_rule_cmp (const NMIPRoutingRule *rule,
                             const NMIPRoutingRule *other);
diff --git a/libnm-core/nm-setting-ip-tunnel.c b/libnm-core/nm-setting-ip-tunnel.c
index 42c44c1c..b4921fea 100644
--- a/libnm-core/nm-setting-ip-tunnel.c
+++ b/libnm-core/nm-setting-ip-tunnel.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-ip-tunnel.h b/libnm-core/nm-setting-ip-tunnel.h
index efa754a8..fae62d0d 100644
--- a/libnm-core/nm-setting-ip-tunnel.h
+++ b/libnm-core/nm-setting-ip-tunnel.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-ip4-config.c b/libnm-core/nm-setting-ip4-config.c
index dd3b79b7..73ba4b74 100644
--- a/libnm-core/nm-setting-ip4-config.c
+++ b/libnm-core/nm-setting-ip4-config.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -253,19 +251,19 @@ ip4_dns_from_dbus (GVariant *dbus_value,
 }
 
 static GVariant *
-ip4_addresses_get (NMSetting  *setting,
-                   const char *property)
+ip4_addresses_get (const NMSettInfoSetting *sett_info,
+                   guint property_idx,
+                   NMConnection *connection,
+                   NMSetting *setting,
+                   NMConnectionSerializationFlags flags,
+                   const NMConnectionSerializationOptions *options)
 {
-	GPtrArray *addrs;
+	gs_unref_ptrarray GPtrArray *addrs = NULL;
 	const char *gateway;
-	GVariant *ret;
 
-	g_object_get (setting, property, &addrs, NULL);
+	g_object_get (setting, NM_SETTING_IP_CONFIG_ADDRESSES, &addrs, NULL);
 	gateway = nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (setting));
-	ret = nm_utils_ip4_addresses_to_variant (addrs, gateway);
-	g_ptr_array_unref (addrs);
-
-	return ret;
+	return nm_utils_ip4_addresses_to_variant (addrs, gateway);
 }
 
 static gboolean
@@ -311,7 +309,8 @@ ip4_address_labels_get (const NMSettInfoSetting *sett_info,
                         guint property_idx,
                         NMConnection *connection,
                         NMSetting *setting,
-                        NMConnectionSerializationFlags flags)
+                        NMConnectionSerializationFlags flags,
+                        const NMConnectionSerializationOptions *options)
 {
 	NMSettingIPConfig *s_ip = NM_SETTING_IP_CONFIG (setting);
 	gboolean have_labels = FALSE;
@@ -354,7 +353,8 @@ ip4_address_data_get (const NMSettInfoSetting *sett_info,
                       guint property_idx,
                       NMConnection *connection,
                       NMSetting *setting,
-                      NMConnectionSerializationFlags flags)
+                      NMConnectionSerializationFlags flags,
+                      const NMConnectionSerializationOptions *options)
 {
 	gs_unref_ptrarray GPtrArray *addrs = NULL;
 
@@ -388,17 +388,17 @@ ip4_address_data_set (NMSetting  *setting,
 }
 
 static GVariant *
-ip4_routes_get (NMSetting  *setting,
-                const char *property)
+ip4_routes_get (const NMSettInfoSetting *sett_info,
+                guint property_idx,
+                NMConnection *connection,
+                NMSetting *setting,
+                NMConnectionSerializationFlags flags,
+                const NMConnectionSerializationOptions *options)
 {
-	GPtrArray *routes;
-	GVariant *ret;
-
-	g_object_get (setting, property, &routes, NULL);
-	ret = nm_utils_ip4_routes_to_variant (routes);
-	g_ptr_array_unref (routes);
+	gs_unref_ptrarray GPtrArray *routes = NULL;
 
-	return ret;
+	g_object_get (setting, NM_SETTING_IP_CONFIG_ROUTES, &routes, NULL);
+	return nm_utils_ip4_routes_to_variant (routes);
 }
 
 static gboolean
@@ -427,7 +427,8 @@ ip4_route_data_get (const NMSettInfoSetting *sett_info,
                     guint property_idx,
                     NMConnection *connection,
                     NMSetting *setting,
-                    NMConnectionSerializationFlags flags)
+                    NMConnectionSerializationFlags flags,
+                    const NMConnectionSerializationOptions *options)
 {
 	gs_unref_ptrarray GPtrArray *routes = NULL;
 
diff --git a/libnm-core/nm-setting-ip4-config.h b/libnm-core/nm-setting-ip4-config.h
index ca894509..bd6b6a2c 100644
--- a/libnm-core/nm-setting-ip4-config.h
+++ b/libnm-core/nm-setting-ip4-config.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-ip6-config.c b/libnm-core/nm-setting-ip6-config.c
index d6c085ac..bafdb373 100644
--- a/libnm-core/nm-setting-ip6-config.c
+++ b/libnm-core/nm-setting-ip6-config.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -39,7 +37,7 @@
  * #NMSettingIP6Config has few properties or methods of its own; it inherits
  * almost everything from #NMSettingIPConfig.
  *
- * NetworkManager supports 6 values for the #NMSettingIPConfig:method property
+ * NetworkManager supports 7 values for the #NMSettingIPConfig:method property
  * for IPv6.  If "auto" is specified then the appropriate automatic method (PPP,
  * router advertisement, etc) is used for the device and most other properties
  * can be left unset.  To force the use of DHCP only, specify "dhcp"; this
@@ -48,7 +46,8 @@
  * If "manual" is specified, static IP addressing is used and at least one IP
  * address must be given in the "addresses" property.  If "ignore" is specified,
  * IPv6 configuration is not done. Note: the "shared" method is not yet
- * supported.
+ * supported. If "disabled" is specified, IPv6 is disabled completely for the
+ * interface.
  **/
 
 /*****************************************************************************/
@@ -166,7 +165,7 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	/* Base class already checked that it exists */
 	g_assert (method);
 
-	if (!strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
+	if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
 		if (nm_setting_ip_config_get_num_addresses (s_ip) == 0) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
@@ -176,12 +175,12 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 			g_prefix_error (error, "%s.%s: ", NM_SETTING_IP6_CONFIG_SETTING_NAME, NM_SETTING_IP_CONFIG_ADDRESSES);
 			return FALSE;
 		}
-	} else if (   !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)
-	           || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)
-	           || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
-
-		/* Shared allows IP addresses and DNS; link-local and disabled do not */
-		if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED) != 0) {
+	} else if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+	                                 NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
+	                                 NM_SETTING_IP6_CONFIG_METHOD_SHARED,
+	                                 NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
+		/* Shared allows IP addresses and DNS; other methods do not */
+		if (!nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
 			if (nm_setting_ip_config_get_num_dns (s_ip) > 0) {
 				g_set_error (error,
 				             NM_CONNECTION_ERROR,
@@ -212,8 +211,8 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 				return FALSE;
 			}
 		}
-	} else if (   !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)
-	           || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
+	} else if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+	                                 NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
 		/* nothing to do */
 	} else {
 		g_set_error_literal (error,
@@ -286,12 +285,13 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 
 	/* Failures from here on are NORMALIZABLE... */
 
-	if (   !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)
+	if (   NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+	                             NM_SETTING_IP6_CONFIG_METHOD_DISABLED)
 	    && !nm_setting_ip_config_get_may_fail (s_ip)) {
 		g_set_error_literal (error,
 		                     NM_CONNECTION_ERROR,
 		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-		                     _("property should be TRUE when method is set to ignore"));
+		                     _("property should be TRUE when method is set to ignore or disabled"));
 		g_prefix_error (error, "%s.%s: ", NM_SETTING_IP6_CONFIG_SETTING_NAME, NM_SETTING_IP_CONFIG_MAY_FAIL);
 		return NM_SETTING_VERIFY_NORMALIZABLE;
 	}
@@ -313,19 +313,19 @@ ip6_dns_from_dbus (GVariant *dbus_value,
 }
 
 static GVariant *
-ip6_addresses_get (NMSetting  *setting,
-                   const char *property)
+ip6_addresses_get (const NMSettInfoSetting *sett_info,
+                   guint property_idx,
+                   NMConnection *connection,
+                   NMSetting *setting,
+                   NMConnectionSerializationFlags flags,
+                   const NMConnectionSerializationOptions *options)
 {
-	GPtrArray *addrs;
+	gs_unref_ptrarray GPtrArray *addrs = NULL;
 	const char *gateway;
-	GVariant *ret;
 
-	g_object_get (setting, property, &addrs, NULL);
+	g_object_get (setting, NM_SETTING_IP_CONFIG_ADDRESSES, &addrs, NULL);
 	gateway = nm_setting_ip_config_get_gateway (NM_SETTING_IP_CONFIG (setting));
-	ret = nm_utils_ip6_addresses_to_variant (addrs, gateway);
-	g_ptr_array_unref (addrs);
-
-	return ret;
+	return nm_utils_ip6_addresses_to_variant (addrs, gateway);
 }
 
 static gboolean
@@ -360,7 +360,8 @@ ip6_address_data_get (const NMSettInfoSetting *sett_info,
                       guint property_idx,
                       NMConnection *connection,
                       NMSetting *setting,
-                      NMConnectionSerializationFlags flags)
+                      NMConnectionSerializationFlags flags,
+                      const NMConnectionSerializationOptions *options)
 {
 	gs_unref_ptrarray GPtrArray *addrs = NULL;
 
@@ -394,17 +395,17 @@ ip6_address_data_set (NMSetting  *setting,
 }
 
 static GVariant *
-ip6_routes_get (NMSetting  *setting,
-                const char *property)
+ip6_routes_get (const NMSettInfoSetting *sett_info,
+                guint property_idx,
+                NMConnection *connection,
+                NMSetting *setting,
+                NMConnectionSerializationFlags flags,
+                const NMConnectionSerializationOptions *options)
 {
-	GPtrArray *routes;
-	GVariant *ret;
-
-	g_object_get (setting, property, &routes, NULL);
-	ret = nm_utils_ip6_routes_to_variant (routes);
-	g_ptr_array_unref (routes);
+	gs_unref_ptrarray GPtrArray *routes = NULL;
 
-	return ret;
+	g_object_get (setting, NM_SETTING_IP_CONFIG_ROUTES, &routes, NULL);
+	return nm_utils_ip6_routes_to_variant (routes);
 }
 
 static gboolean
@@ -433,7 +434,8 @@ ip6_route_data_get (const NMSettInfoSetting *sett_info,
                     guint property_idx,
                     NMConnection *connection,
                     NMSetting *setting,
-                    NMConnectionSerializationFlags flags)
+                    NMConnectionSerializationFlags flags,
+                    const NMConnectionSerializationOptions *options)
 {
 	gs_unref_ptrarray GPtrArray *routes = NULL;
 
@@ -569,10 +571,11 @@ nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *klass)
 
 	/* ---ifcfg-rh---
 	 * property: method
-	 * variable: IPV6INIT, IPV6FORWARDING, IPV6_AUTOCONF, DHCPV6C
+	 * variable: IPV6INIT, IPV6FORWARDING, IPV6_AUTOCONF, DHCPV6C, IPV6_DISABLED
 	 * default:  IPV6INIT=yes; IPV6FORWARDING=no; IPV6_AUTOCONF=!IPV6FORWARDING, DHCPV6=no
 	 * description: Method used for IPv6 protocol configuration.
-	 *   ignore ~ IPV6INIT=no; auto ~ IPV6_AUTOCONF=yes; dhcp ~ IPV6_AUTOCONF=no and DHCPV6C=yes
+	 *   ignore ~ IPV6INIT=no; auto ~ IPV6_AUTOCONF=yes; dhcp ~ IPV6_AUTOCONF=no and DHCPV6C=yes;
+	 *   disabled ~ IPV6_DISABLED=yes
 	 * ---end---
 	 */
 
diff --git a/libnm-core/nm-setting-ip6-config.h b/libnm-core/nm-setting-ip6-config.h
index ae8ab1a2..bb590ae4 100644
--- a/libnm-core/nm-setting-ip6-config.h
+++ b/libnm-core/nm-setting-ip6-config.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -102,6 +100,13 @@ G_BEGIN_DECLS
 #define NM_SETTING_IP6_CONFIG_METHOD_SHARED     "shared"
 
 /**
+ * NM_SETTING_IP6_CONFIG_METHOD_DISABLED:
+ *
+ * IPv6 is disabled for the connection.
+ */
+#define NM_SETTING_IP6_CONFIG_METHOD_DISABLED   "disabled"
+
+/**
  * NMSettingIP6ConfigPrivacy:
  * @NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN: unknown or no value specified
  * @NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED: IPv6 Privacy Extensions are disabled
diff --git a/libnm-core/nm-setting-macsec.c b/libnm-core/nm-setting-macsec.c
index 19d1989e..792d3f20 100644
--- a/libnm-core/nm-setting-macsec.c
+++ b/libnm-core/nm-setting-macsec.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-macsec.h b/libnm-core/nm-setting-macsec.h
index 0f545007..6a9dc7f8 100644
--- a/libnm-core/nm-setting-macsec.h
+++ b/libnm-core/nm-setting-macsec.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-macvlan.c b/libnm-core/nm-setting-macvlan.c
index 332dc007..65a7e779 100644
--- a/libnm-core/nm-setting-macvlan.c
+++ b/libnm-core/nm-setting-macvlan.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-macvlan.h b/libnm-core/nm-setting-macvlan.h
index 605efd0f..67aadb48 100644
--- a/libnm-core/nm-setting-macvlan.h
+++ b/libnm-core/nm-setting-macvlan.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-match.c b/libnm-core/nm-setting-match.c
index 2f331060..b1b8e55f 100644
--- a/libnm-core/nm-setting-match.c
+++ b/libnm-core/nm-setting-match.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-match.h b/libnm-core/nm-setting-match.h
index a39feca2..31917d25 100644
--- a/libnm-core/nm-setting-match.h
+++ b/libnm-core/nm-setting-match.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-olpc-mesh.c b/libnm-core/nm-setting-olpc-mesh.c
index f6a32dbb..a5e9de73 100644
--- a/libnm-core/nm-setting-olpc-mesh.c
+++ b/libnm-core/nm-setting-olpc-mesh.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-olpc-mesh.h b/libnm-core/nm-setting-olpc-mesh.h
index 69b3c6e9..e781e75f 100644
--- a/libnm-core/nm-setting-olpc-mesh.h
+++ b/libnm-core/nm-setting-olpc-mesh.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-ovs-bridge.c b/libnm-core/nm-setting-ovs-bridge.c
index e69dcdea..473bdb94 100644
--- a/libnm-core/nm-setting-ovs-bridge.c
+++ b/libnm-core/nm-setting-ovs-bridge.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -41,6 +40,7 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 	PROP_MCAST_SNOOPING_ENABLE,
 	PROP_RSTP_ENABLE,
 	PROP_STP_ENABLE,
+	PROP_DATAPATH_TYPE,
 );
 
 /**
@@ -52,6 +52,7 @@ struct _NMSettingOvsBridge {
 	NMSetting parent;
 
 	char *fail_mode;
+	char *datapath_type;
 	gboolean mcast_snooping_enable;
 	gboolean rstp_enable;
 	gboolean stp_enable;
@@ -129,6 +130,22 @@ nm_setting_ovs_bridge_get_stp_enable (NMSettingOvsBridge *self)
 	return self->stp_enable;
 }
 
+/**
+ * nm_setting_ovs_bridge_get_datapath_type:
+ * @self: the #NMSettingOvsBridge
+ *
+ * Returns: the #NMSettingOvsBridge:datapath_type property of the setting
+ *
+ * Since: 1.20
+ **/
+const char *
+nm_setting_ovs_bridge_get_datapath_type (NMSettingOvsBridge *self)
+{
+	g_return_val_if_fail (NM_IS_SETTING_OVS_BRIDGE (self), NULL);
+
+	return self->datapath_type;
+}
+
 /*****************************************************************************/
 
 static int
@@ -173,6 +190,16 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
+	if (!NM_IN_STRSET (self->datapath_type, "system", "netdev", NULL)) {
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             _("'%s' is not valid"),
+		             self->datapath_type);
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_OVS_BRIDGE_SETTING_NAME, NM_SETTING_OVS_BRIDGE_DATAPATH_TYPE);
+		return FALSE;
+	}
+
 	return TRUE;
 }
 
@@ -197,6 +224,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_STP_ENABLE:
 		g_value_set_boolean (value, self->stp_enable);
 		break;
+	case PROP_DATAPATH_TYPE:
+		g_value_set_string (value, self->datapath_type);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -223,6 +253,10 @@ set_property (GObject *object, guint prop_id,
 	case PROP_STP_ENABLE:
 		self->stp_enable = g_value_get_boolean (value);
 		break;
+	case PROP_DATAPATH_TYPE:
+		g_free (self->datapath_type);
+		self->datapath_type = g_value_dup_string (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -257,6 +291,7 @@ finalize (GObject *object)
 	NMSettingOvsBridge *self = NM_SETTING_OVS_BRIDGE (object);
 
 	g_free (self->fail_mode);
+	g_free (self->datapath_type);
 
 	G_OBJECT_CLASS (nm_setting_ovs_bridge_parent_class)->finalize (object);
 }
@@ -330,6 +365,20 @@ nm_setting_ovs_bridge_class_init (NMSettingOvsBridgeClass *klass)
 	                          G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
+	/**
+	 * NMSettingOvsBridge:datapath-type:
+	 *
+	 * The data path type. One of "system", "netdev" or empty.
+	 *
+	 * Since: 1.20
+	 **/
+	obj_properties[PROP_DATAPATH_TYPE] =
+	    g_param_spec_string (NM_SETTING_OVS_BRIDGE_DATAPATH_TYPE, "", "",
+	                         NULL,
+	                         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_OVS_BRIDGE);
diff --git a/libnm-core/nm-setting-ovs-bridge.h b/libnm-core/nm-setting-ovs-bridge.h
index d4837e58..b9f92c06 100644
--- a/libnm-core/nm-setting-ovs-bridge.h
+++ b/libnm-core/nm-setting-ovs-bridge.h
@@ -41,6 +41,7 @@ G_BEGIN_DECLS
 #define NM_SETTING_OVS_BRIDGE_MCAST_SNOOPING_ENABLE "mcast-snooping-enable"
 #define NM_SETTING_OVS_BRIDGE_RSTP_ENABLE           "rstp-enable"
 #define NM_SETTING_OVS_BRIDGE_STP_ENABLE            "stp-enable"
+#define NM_SETTING_OVS_BRIDGE_DATAPATH_TYPE         "datapath-type"
 
 typedef struct _NMSettingOvsBridgeClass NMSettingOvsBridgeClass;
 
@@ -57,6 +58,8 @@ NM_AVAILABLE_IN_1_10
 gboolean    nm_setting_ovs_bridge_get_rstp_enable           (NMSettingOvsBridge *self);
 NM_AVAILABLE_IN_1_10
 gboolean    nm_setting_ovs_bridge_get_stp_enable            (NMSettingOvsBridge *self);
+NM_AVAILABLE_IN_1_20
+const char *nm_setting_ovs_bridge_get_datapath_type         (NMSettingOvsBridge *self);
 
 G_END_DECLS
 
diff --git a/libnm-core/nm-setting-ovs-dpdk.c b/libnm-core/nm-setting-ovs-dpdk.c
new file mode 100644
index 00000000..4e831e6e
--- /dev/null
+++ b/libnm-core/nm-setting-ovs-dpdk.c
@@ -0,0 +1,172 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-setting-ovs-dpdk.h"
+
+#include "nm-connection-private.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-private.h"
+
+/**
+ * SECTION:nm-setting-ovs-dpdk
+ * @short_description: Describes connection properties for Open vSwitch DPDK interfaces.
+ *
+ * The #NMSettingOvsDpdk object is a #NMSetting subclass that describes properties
+ * necessary for Open vSwitch interfaces of type "dpdk".
+ **/
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+	PROP_DEVARGS,
+);
+
+/**
+ * NMSettingOvsDpdk:
+ *
+ * OvsDpdk Link Settings
+ */
+struct _NMSettingOvsDpdk {
+	NMSetting parent;
+
+	char *devargs;
+};
+
+struct _NMSettingOvsDpdkClass {
+	NMSettingClass parent;
+};
+
+G_DEFINE_TYPE (NMSettingOvsDpdk, nm_setting_ovs_dpdk, NM_TYPE_SETTING)
+
+/*****************************************************************************/
+
+/**
+ * nm_setting_ovs_dpdk_get_devargs:
+ * @self: the #NMSettingOvsDpdk
+ *
+ * Returns: the #NMSettingOvsDpdk:devargs property of the setting
+ *
+ * Since: 1.20
+ **/
+const char *
+nm_setting_ovs_dpdk_get_devargs (NMSettingOvsDpdk *self)
+{
+	g_return_val_if_fail (NM_IS_SETTING_OVS_DPDK (self), NULL);
+
+	return self->devargs;
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMSettingOvsDpdk *self = NM_SETTING_OVS_DPDK (object);
+
+	switch (prop_id) {
+	case PROP_DEVARGS:
+		g_value_set_string (value, self->devargs);
+		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)
+{
+	NMSettingOvsDpdk *self = NM_SETTING_OVS_DPDK (object);
+
+	switch (prop_id) {
+	case PROP_DEVARGS:
+		g_free (self->devargs);
+		self->devargs = g_value_dup_string (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_setting_ovs_dpdk_init (NMSettingOvsDpdk *self)
+{
+}
+
+/**
+ * nm_setting_ovs_dpdk_new:
+ *
+ * Creates a new #NMSettingOvsDpdk object with default values.
+ *
+ * Returns: (transfer full): the new empty #NMSettingOvsDpdk object
+ *
+ * Since: 1.20
+ **/
+NMSetting *
+nm_setting_ovs_dpdk_new (void)
+{
+	return (NMSetting *) g_object_new (NM_TYPE_SETTING_OVS_DPDK, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMSettingOvsDpdk *self = NM_SETTING_OVS_DPDK (object);
+
+	g_free (self->devargs);
+
+	G_OBJECT_CLASS (nm_setting_ovs_dpdk_parent_class)->finalize (object);
+}
+
+static void
+nm_setting_ovs_dpdk_class_init (NMSettingOvsDpdkClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMSettingClass *setting_class = NM_SETTING_CLASS (klass);
+
+	object_class->set_property = set_property;
+	object_class->get_property = get_property;
+	object_class->finalize     = finalize;
+
+	/**
+	 * NMSettingOvsDpdk:devargs:
+	 *
+	 * Open vSwitch DPDK device arguments.
+	 *
+	 * Since: 1.20
+	 **/
+	obj_properties[PROP_DEVARGS] =
+	    g_param_spec_string (NM_SETTING_OVS_DPDK_DEVARGS, "", "",
+	                         NULL,
+	                         G_PARAM_READWRITE |
+	                         G_PARAM_CONSTRUCT |
+	                         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_OVS_DPDK);
+}
diff --git a/libnm-core/nm-setting-ovs-dpdk.h b/libnm-core/nm-setting-ovs-dpdk.h
new file mode 100644
index 00000000..75ec6752
--- /dev/null
+++ b/libnm-core/nm-setting-ovs-dpdk.h
@@ -0,0 +1,54 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_SETTING_OVS_DPDK_H__
+#define __NM_SETTING_OVS_DPDK_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_OVS_DPDK            (nm_setting_ovs_dpdk_get_type ())
+#define NM_SETTING_OVS_DPDK(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTING_OVS_DPDK, NMSettingOvsDpdk))
+#define NM_SETTING_OVS_DPDK_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTING_OVS_DPDKCONFIG, NMSettingOvsDpdkClass))
+#define NM_IS_SETTING_OVS_DPDK(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SETTING_OVS_DPDK))
+#define NM_IS_SETTING_OVS_DPDK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SETTING_OVS_DPDK))
+#define NM_SETTING_OVS_DPDK_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SETTING_OVS_DPDK, NMSettingOvsDpdkClass))
+
+#define NM_SETTING_OVS_DPDK_SETTING_NAME        "ovs-dpdk"
+
+#define NM_SETTING_OVS_DPDK_DEVARGS             "devargs"
+
+typedef struct _NMSettingOvsDpdkClass NMSettingOvsDpdkClass;
+
+NM_AVAILABLE_IN_1_20
+GType nm_setting_ovs_dpdk_get_type (void);
+NM_AVAILABLE_IN_1_20
+NMSetting *nm_setting_ovs_dpdk_new (void);
+
+NM_AVAILABLE_IN_1_20
+const char *nm_setting_ovs_dpdk_get_devargs (NMSettingOvsDpdk *self);
+
+G_END_DECLS
+
+#endif /* __NM_SETTING_OVS_DPDK_H__ */
diff --git a/libnm-core/nm-setting-ovs-interface.c b/libnm-core/nm-setting-ovs-interface.c
index ad7f2ead..406c7b2b 100644
--- a/libnm-core/nm-setting-ovs-interface.c
+++ b/libnm-core/nm-setting-ovs-interface.c
@@ -83,11 +83,11 @@ _nm_setting_ovs_interface_verify_interface_type (NMSettingOvsInterface *self,
                                                  gboolean *out_modified,
                                                  GError **error)
 {
-	gboolean has_patch;
 	const char *type;
+	const char *type_from_setting = NULL;
+	const char *type_setting = NULL;
 	const char *connection_type;
 	gboolean is_ovs_connection_type;
-	gboolean missing_patch_setting = FALSE;
 
 	g_return_val_if_fail (NM_IS_SETTING_OVS_INTERFACE (self), FALSE);
 	if (normalize) {
@@ -101,10 +101,7 @@ _nm_setting_ovs_interface_verify_interface_type (NMSettingOvsInterface *self,
 	type = self ? self->type : NULL;
 
 	if (   type
-	    && !NM_IN_STRSET (type,
-	                      "internal",
-	                      "system",
-	                      "patch")) {
+	    && !NM_IN_STRSET (type, "internal", "system", "patch", "dpdk")) {
 		g_set_error (error,
 		             NM_CONNECTION_ERROR,
 		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -163,9 +160,26 @@ _nm_setting_ovs_interface_verify_interface_type (NMSettingOvsInterface *self,
 		is_ovs_connection_type = FALSE;
 	}
 
-	has_patch = !!nm_connection_get_setting_by_name (connection, NM_SETTING_OVS_PATCH_SETTING_NAME);
+	if (nm_connection_get_setting_by_name (connection, NM_SETTING_OVS_PATCH_SETTING_NAME)) {
+		type_from_setting = "patch";
+		type_setting = NM_SETTING_OVS_PATCH_SETTING_NAME;
+	}
+
+	if (nm_connection_get_setting_by_name (connection, NM_SETTING_OVS_DPDK_SETTING_NAME)) {
+		if (type_from_setting) {
+			g_set_error (error,
+			             NM_CONNECTION_ERROR,
+			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("A connection can not have both '%s' and '%s' settings at the same time"),
+			             NM_SETTING_OVS_DPDK_SETTING_NAME,
+			             type_setting);
+			return FALSE;
+		}
+		type_from_setting = "dpdk";
+		type_setting = NM_SETTING_OVS_DPDK_SETTING_NAME;
+	}
 
-	if (has_patch) {
+	if (type_from_setting) {
 		if (!is_ovs_connection_type) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
@@ -176,20 +190,22 @@ _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;
 		}
+
 		if (type) {
-			if (!nm_streq (type, "patch")) {
+			if (!nm_streq (type, type_from_setting)) {
 				g_set_error (error,
 				             NM_CONNECTION_ERROR,
 				             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-				             _("A connection with '%s' setting needs to be of 'patch' interface type, not '%s'"),
-				             NM_SETTING_OVS_PATCH_SETTING_NAME,
+				             _("A connection with '%s' setting needs to be of '%s' interface type, not '%s'"),
+				             type_setting,
+				             type_from_setting,
 				             type);
 				g_prefix_error (error, "%s.%s: ", NM_SETTING_OVS_INTERFACE_SETTING_NAME, NM_SETTING_OVS_INTERFACE_TYPE);
 				return FALSE;
 			}
 			return TRUE;
 		}
-		type = "patch";
+		type = type_from_setting;
 		goto normalize;
 	} else {
 		if (nm_streq0 (type, "patch")) {
@@ -225,8 +241,6 @@ normalize:
 			             _("Missing ovs interface type"));
 			g_prefix_error (error, "%s.%s: ", NM_SETTING_OVS_INTERFACE_SETTING_NAME, NM_SETTING_OVS_INTERFACE_TYPE);
 		}
-		if (missing_patch_setting) {
-		}
 		return NM_SETTING_VERIFY_NORMALIZABLE_ERROR;
 	}
 
@@ -375,7 +389,7 @@ nm_setting_ovs_interface_class_init (NMSettingOvsInterfaceClass *klass)
 	/**
 	 * NMSettingOvsInterface:type:
 	 *
-	 * The interface type. Either "internal", or empty.
+	 * The interface type. Either "internal", "system", "patch", "dpdk", or empty.
 	 *
 	 * Since: 1.10
 	 **/
diff --git a/libnm-core/nm-setting-ovs-patch.c b/libnm-core/nm-setting-ovs-patch.c
index 2a46810c..dd0bdc71 100644
--- a/libnm-core/nm-setting-ovs-patch.c
+++ b/libnm-core/nm-setting-ovs-patch.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-ovs-port.c b/libnm-core/nm-setting-ovs-port.c
index e690d641..4a4d74af 100644
--- a/libnm-core/nm-setting-ovs-port.c
+++ b/libnm-core/nm-setting-ovs-port.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-ppp.c b/libnm-core/nm-setting-ppp.c
index 12a8c366..eb4ef7ab 100644
--- a/libnm-core/nm-setting-ppp.c
+++ b/libnm-core/nm-setting-ppp.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-ppp.h b/libnm-core/nm-setting-ppp.h
index 01536d84..30578a81 100644
--- a/libnm-core/nm-setting-ppp.h
+++ b/libnm-core/nm-setting-ppp.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-pppoe.c b/libnm-core/nm-setting-pppoe.c
index e2d516c5..0f079279 100644
--- a/libnm-core/nm-setting-pppoe.c
+++ b/libnm-core/nm-setting-pppoe.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-pppoe.h b/libnm-core/nm-setting-pppoe.h
index cab96e3b..ab319aa3 100644
--- a/libnm-core/nm-setting-pppoe.h
+++ b/libnm-core/nm-setting-pppoe.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-private.h b/libnm-core/nm-setting-private.h
index b423eb5c..c4c0bb48 100644
--- a/libnm-core/nm-setting-private.h
+++ b/libnm-core/nm-setting-private.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -88,7 +87,8 @@ GVariant *_nm_setting_get_deprecated_virtual_interface_name (const NMSettInfoSet
                                                              guint property_idx,
                                                              NMConnection *connection,
                                                              NMSetting *setting,
-                                                             NMConnectionSerializationFlags flags);
+                                                             NMConnectionSerializationFlags flags,
+                                                             const NMConnectionSerializationOptions *options);
 
 NMSettingVerifyResult _nm_setting_verify (NMSetting *setting,
                                           NMConnection *connection,
@@ -107,7 +107,8 @@ gboolean _nm_setting_slave_type_is_valid (const char *slave_type, const char **o
 
 GVariant   *_nm_setting_to_dbus       (NMSetting *setting,
                                        NMConnection *connection,
-                                       NMConnectionSerializationFlags flags);
+                                       NMConnectionSerializationFlags flags,
+                                       const NMConnectionSerializationOptions *options);
 
 NMSetting  *_nm_setting_new_from_dbus (GType setting_type,
                                        GVariant *setting_dict,
@@ -175,21 +176,21 @@ void _properties_override_add__helper (GArray *properties_override,
 void _properties_override_add_dbus_only (GArray *properties_override,
                                          const char *property_name,
                                          const GVariantType *dbus_type,
-                                         NMSettingPropertySynthFunc synth_func,
-                                         NMSettingPropertySetFunc set_func);
+                                         NMSettInfoPropToDBusFcn to_dbus_fcn,
+                                         NMSettInfoPropFromDBusFcn from_dbus_fcn);
 
 void _properties_override_add_override (GArray *properties_override,
                                         GParamSpec *param_spec,
                                         const GVariantType *dbus_type,
-                                        NMSettingPropertyGetFunc get_func,
-                                        NMSettingPropertySetFunc set_func,
-                                        NMSettingPropertyNotSetFunc not_set_func);
+                                        NMSettInfoPropToDBusFcn to_dbus_fcn,
+                                        NMSettInfoPropFromDBusFcn from_dbus_fcn,
+                                        NMSettInfoPropMissingFromDBusFcn missing_from_dbus_fcn);
 
 void _properties_override_add_transform (GArray *properties_override,
                                          GParamSpec *param_spec,
                                          const GVariantType *dbus_type,
-                                         NMSettingPropertyTransformToFunc to_dbus,
-                                         NMSettingPropertyTransformFromFunc from_dbus);
+                                         NMSettInfoPropGPropToDBusFcn gprop_to_dbus_fcn,
+                                         NMSettInfoPropGPropFromDBusFcn gprop_from_dbus_fcn);
 
 /*****************************************************************************/
 
diff --git a/libnm-core/nm-setting-proxy.c b/libnm-core/nm-setting-proxy.c
index dbb69aeb..bbda0ba1 100644
--- a/libnm-core/nm-setting-proxy.c
+++ b/libnm-core/nm-setting-proxy.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-proxy.h b/libnm-core/nm-setting-proxy.h
index 0c72799f..3800ac76 100644
--- a/libnm-core/nm-setting-proxy.h
+++ b/libnm-core/nm-setting-proxy.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-serial.c b/libnm-core/nm-setting-serial.c
index 36bc4f69..9bdc887a 100644
--- a/libnm-core/nm-setting-serial.c
+++ b/libnm-core/nm-setting-serial.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-serial.h b/libnm-core/nm-setting-serial.h
index 2362b246..bec9829a 100644
--- a/libnm-core/nm-setting-serial.h
+++ b/libnm-core/nm-setting-serial.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-sriov.c b/libnm-core/nm-setting-sriov.c
index b662ca2c..90ac44ab 100644
--- a/libnm-core/nm-setting-sriov.c
+++ b/libnm-core/nm-setting-sriov.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -365,17 +364,14 @@ nm_sriov_vf_get_attribute (const NMSriovVF *vf, const char *name)
 	return g_hash_table_lookup (vf->attributes, name);
 }
 
-#define SRIOV_ATTR_SPEC_PTR(name, type, str_type) \
-	&(NMVariantAttributeSpec) { name, type, FALSE, FALSE, FALSE, FALSE, str_type }
-
-const NMVariantAttributeSpec * const _nm_sriov_vf_attribute_spec[] = {
-	SRIOV_ATTR_SPEC_PTR (NM_SRIOV_VF_ATTRIBUTE_MAC,          G_VARIANT_TYPE_STRING,  'm'),
-	SRIOV_ATTR_SPEC_PTR (NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK,  G_VARIANT_TYPE_BOOLEAN,  0),
-	SRIOV_ATTR_SPEC_PTR (NM_SRIOV_VF_ATTRIBUTE_TRUST,        G_VARIANT_TYPE_BOOLEAN,  0),
-	SRIOV_ATTR_SPEC_PTR (NM_SRIOV_VF_ATTRIBUTE_MIN_TX_RATE,  G_VARIANT_TYPE_UINT32,   0),
-	SRIOV_ATTR_SPEC_PTR (NM_SRIOV_VF_ATTRIBUTE_MAX_TX_RATE,  G_VARIANT_TYPE_UINT32,   0),
+const NMVariantAttributeSpec *const _nm_sriov_vf_attribute_spec[] = {
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_SRIOV_VF_ATTRIBUTE_MAC,         G_VARIANT_TYPE_STRING,  .str_type = 'm', ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK, G_VARIANT_TYPE_BOOLEAN,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_SRIOV_VF_ATTRIBUTE_TRUST,       G_VARIANT_TYPE_BOOLEAN,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_SRIOV_VF_ATTRIBUTE_MIN_TX_RATE, G_VARIANT_TYPE_UINT32,                   ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE (NM_SRIOV_VF_ATTRIBUTE_MAX_TX_RATE, G_VARIANT_TYPE_UINT32,                   ),
 	/* D-Bus only, synthetic attributes */
-	SRIOV_ATTR_SPEC_PTR ("vlans",                            G_VARIANT_TYPE_STRING,  'd'),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("vlans",                           G_VARIANT_TYPE_STRING,  .str_type = 'd', ),
 	NULL,
 };
 
@@ -905,7 +901,12 @@ _nm_setting_sriov_sort_vfs (NMSettingSriov *setting)
 /*****************************************************************************/
 
 static GVariant *
-vfs_to_dbus (NMSetting *setting, const char *property)
+vfs_to_dbus (const NMSettInfoSetting *sett_info,
+             guint property_idx,
+             NMConnection *connection,
+             NMSetting *setting,
+             NMConnectionSerializationFlags flags,
+             const NMConnectionSerializationOptions *options)
 {
 	gs_unref_ptrarray GPtrArray *vfs = NULL;
 	GVariantBuilder builder;
@@ -1129,8 +1130,10 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	NMSettingSriov *a;
@@ -1138,9 +1141,9 @@ compare_property (const NMSettInfoSetting *sett_info,
 	guint i;
 
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_SRIOV_VFS)) {
-		if (other) {
-			a = NM_SETTING_SRIOV (setting);
-			b = NM_SETTING_SRIOV (other);
+		if (set_b) {
+			a = NM_SETTING_SRIOV (set_a);
+			b = NM_SETTING_SRIOV (set_b);
 
 			if (a->vfs->len != b->vfs->len)
 				return FALSE;
@@ -1154,8 +1157,10 @@ compare_property (const NMSettInfoSetting *sett_info,
 
 	return NM_SETTING_CLASS (nm_setting_sriov_parent_class)->compare_property (sett_info,
 	                                                                           property_idx,
-	                                                                           setting,
-	                                                                           other,
+	                                                                           con_a,
+	                                                                           set_a,
+	                                                                           con_b,
+	                                                                           set_b,
 	                                                                           flags);
 }
 
@@ -1365,7 +1370,7 @@ nm_setting_sriov_class_init (NMSettingSriovClass *klass)
 	 */
 	obj_properties[PROP_AUTOPROBE_DRIVERS] =
 	    g_param_spec_enum (NM_SETTING_SRIOV_AUTOPROBE_DRIVERS, "", "",
-	                       nm_ternary_get_type (),
+	                       NM_TYPE_TERNARY,
 	                       NM_TERNARY_DEFAULT,
 	                       NM_SETTING_PARAM_FUZZY_IGNORE |
 	                       G_PARAM_READWRITE |
diff --git a/libnm-core/nm-setting-sriov.h b/libnm-core/nm-setting-sriov.h
index 2e209964..a1ad726b 100644
--- a/libnm-core/nm-setting-sriov.h
+++ b/libnm-core/nm-setting-sriov.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-tc-config.c b/libnm-core/nm-setting-tc-config.c
index dc2f4f64..fe8e695c 100644
--- a/libnm-core/nm-setting-tc-config.c
+++ b/libnm-core/nm-setting-tc-config.c
@@ -1325,16 +1325,18 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
-	NMSettingTCConfig *a_tc_config = NM_SETTING_TC_CONFIG (setting);
-	NMSettingTCConfig *b_tc_config = NM_SETTING_TC_CONFIG (other);
+	NMSettingTCConfig *a_tc_config = NM_SETTING_TC_CONFIG (set_a);
+	NMSettingTCConfig *b_tc_config = NM_SETTING_TC_CONFIG (set_b);
 	guint i;
 
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_TC_CONFIG_QDISCS)) {
-		if (other) {
+		if (set_b) {
 			if (a_tc_config->qdiscs->len != b_tc_config->qdiscs->len)
 				return FALSE;
 			for (i = 0; i < a_tc_config->qdiscs->len; i++) {
@@ -1346,7 +1348,7 @@ compare_property (const NMSettInfoSetting *sett_info,
 	}
 
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_TC_CONFIG_TFILTERS)) {
-		if (other) {
+		if (set_b) {
 			if (a_tc_config->tfilters->len != b_tc_config->tfilters->len)
 				return FALSE;
 			for (i = 0; i < a_tc_config->tfilters->len; i++) {
@@ -1359,8 +1361,10 @@ compare_property (const NMSettInfoSetting *sett_info,
 
 	return NM_SETTING_CLASS (nm_setting_tc_config_parent_class)->compare_property (sett_info,
 	                                                                               property_idx,
-	                                                                               setting,
-	                                                                               other,
+	                                                                               con_a,
+	                                                                               set_a,
+	                                                                               con_b,
+	                                                                               set_b,
 	                                                                               flags);
 }
 
@@ -1479,17 +1483,17 @@ next:
 }
 
 static GVariant *
-tc_qdiscs_get (NMSetting *setting,
-               const char *property)
+tc_qdiscs_get (const NMSettInfoSetting *sett_info,
+               guint property_idx,
+               NMConnection *connection,
+               NMSetting *setting,
+               NMConnectionSerializationFlags flags,
+               const NMConnectionSerializationOptions *options)
 {
-	GPtrArray *qdiscs;
-	GVariant *ret;
+	gs_unref_ptrarray GPtrArray *qdiscs = NULL;
 
 	g_object_get (setting, NM_SETTING_TC_CONFIG_QDISCS, &qdiscs, NULL);
-	ret = _qdiscs_to_variant (qdiscs);
-	g_ptr_array_unref (qdiscs);
-
-	return ret;
+	return _qdiscs_to_variant (qdiscs);
 }
 
 static gboolean
@@ -1665,17 +1669,17 @@ next:
 }
 
 static GVariant *
-tc_tfilters_get (NMSetting *setting,
-                 const char *property)
+tc_tfilters_get (const NMSettInfoSetting *sett_info,
+                guint property_idx,
+                NMConnection *connection,
+                NMSetting *setting,
+                NMConnectionSerializationFlags flags,
+                const NMConnectionSerializationOptions *options)
 {
-	GPtrArray *tfilters;
-	GVariant *ret;
+	gs_unref_ptrarray GPtrArray *tfilters = NULL;
 
 	g_object_get (setting, NM_SETTING_TC_CONFIG_TFILTERS, &tfilters, NULL);
-	ret = _tfilters_to_variant (tfilters);
-	g_ptr_array_unref (tfilters);
-
-	return ret;
+	return _tfilters_to_variant (tfilters);
 }
 
 static gboolean
@@ -1686,12 +1690,10 @@ tc_tfilters_set (NMSetting *setting,
                  NMSettingParseFlags parse_flags,
                  GError **error)
 {
-	GPtrArray *tfilters;
+	gs_unref_ptrarray GPtrArray *tfilters = NULL;
 
 	tfilters = _tfilters_from_variant (value);
 	g_object_set (setting, NM_SETTING_TC_CONFIG_TFILTERS, tfilters, NULL);
-	g_ptr_array_unref (tfilters);
-
 	return TRUE;
 }
 
diff --git a/libnm-core/nm-setting-team-port.c b/libnm-core/nm-setting-team-port.c
index b2c57ffa..6c087aa9 100644
--- a/libnm-core/nm-setting-team-port.c
+++ b/libnm-core/nm-setting-team-port.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -30,6 +29,7 @@
 #include "nm-utils-private.h"
 #include "nm-connection-private.h"
 #include "nm-setting-connection.h"
+#include "nm-team-utils.h"
 
 /**
  * SECTION:nm-setting-team-port
@@ -41,34 +41,10 @@
 
 /*****************************************************************************/
 
-NM_GOBJECT_PROPERTIES_DEFINE (NMSettingTeamPort,
-	PROP_CONFIG,
-	PROP_QUEUE_ID,
-	PROP_PRIO,
-	PROP_STICKY,
-	PROP_LACP_PRIO,
-	PROP_LACP_KEY,
-	PROP_LINK_WATCHERS,
-);
-
-static const _NMUtilsTeamPropertyKeys _prop_to_keys[_PROPERTY_ENUMS_LAST] = {
-	[PROP_CONFIG] =        { },
-	[PROP_QUEUE_ID] =      { .key1 = "queue_id",   .default_int = NM_SETTING_TEAM_PORT_QUEUE_ID_DEFAULT, },
-	[PROP_PRIO] =          { .key1 = "prio",       },
-	[PROP_STICKY] =        { .key1 = "sticky",     },
-	[PROP_LACP_PRIO] =     { .key1 = "lacp_prio",  .default_int = NM_SETTING_TEAM_PORT_LACP_PRIO_DEFAULT, },
-	[PROP_LACP_KEY] =      { .key1 = "lacp_key",   },
-	[PROP_LINK_WATCHERS] = { .key1 = "link_watch", },
-};
+static GParamSpec *obj_properties[_NM_TEAM_ATTRIBUTE_PORT_NUM] = { NULL, };
 
 typedef struct {
-	char *config;
-	int queue_id;
-	int prio;
-	gboolean sticky;
-	int lacp_prio;
-	int lacp_key;
-	GPtrArray *link_watchers; /* Array of NMTeamLinkWatcher */
+	NMTeamSetting *team_setting;
 } NMSettingTeamPortPrivate;
 
 G_DEFINE_TYPE (NMSettingTeamPort, nm_setting_team_port, NM_TYPE_SETTING)
@@ -77,6 +53,25 @@ G_DEFINE_TYPE (NMSettingTeamPort, nm_setting_team_port, NM_TYPE_SETTING)
 
 /*****************************************************************************/
 
+NMTeamSetting *
+_nm_setting_team_port_get_team_setting (NMSettingTeamPort *setting)
+{
+	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->team_setting;
+}
+
+/*****************************************************************************/
+
+#define _maybe_changed(self, changed) \
+	nm_team_setting_maybe_changed (NM_SETTING (_NM_ENSURE_TYPE (NMSettingTeamPort *, self)), (const GParamSpec *const*) obj_properties, (changed))
+
+#define _maybe_changed_with_assert(self, changed) \
+	G_STMT_START { \
+		if (!_maybe_changed ((self), (changed))) \
+			nm_assert_not_reached (); \
+	} G_STMT_END
+
+/*****************************************************************************/
+
 /**
  * nm_setting_team_port_get_config:
  * @setting: the #NMSettingTeamPort
@@ -88,7 +83,7 @@ nm_setting_team_port_get_config (NMSettingTeamPort *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM_PORT (setting), NULL);
 
-	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->config;
+	return nm_team_setting_config_get (NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->team_setting);
 }
 
 /**
@@ -104,7 +99,7 @@ nm_setting_team_port_get_queue_id (NMSettingTeamPort *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM_PORT (setting), -1);
 
-	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->queue_id;
+	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->team_setting->d.port.queue_id;
 }
 
 /**
@@ -120,7 +115,7 @@ nm_setting_team_port_get_prio (NMSettingTeamPort *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM_PORT (setting), 0);
 
-	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->prio;
+	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->team_setting->d.port.prio;
 }
 
 /**
@@ -136,7 +131,7 @@ nm_setting_team_port_get_sticky (NMSettingTeamPort *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM_PORT (setting), FALSE);
 
-	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->sticky;
+	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->team_setting->d.port.sticky;
 }
 
 /**
@@ -152,7 +147,7 @@ nm_setting_team_port_get_lacp_prio (NMSettingTeamPort *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM_PORT (setting), 0);
 
-	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->lacp_prio;
+	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->team_setting->d.port.lacp_prio;
 }
 
 /**
@@ -168,7 +163,7 @@ nm_setting_team_port_get_lacp_key (NMSettingTeamPort *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM_PORT (setting), 0);
 
-	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->lacp_key;
+	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->team_setting->d.port.lacp_key;
 }
 
 /**
@@ -182,11 +177,9 @@ nm_setting_team_port_get_lacp_key (NMSettingTeamPort *setting)
 guint
 nm_setting_team_port_get_num_link_watchers (NMSettingTeamPort *setting)
 {
-	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
-
 	g_return_val_if_fail (NM_IS_SETTING_TEAM_PORT (setting), 0);
 
-	return priv->link_watchers->len;
+	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->team_setting->d.link_watchers->len;
 }
 
 /**
@@ -201,12 +194,15 @@ nm_setting_team_port_get_num_link_watchers (NMSettingTeamPort *setting)
 NMTeamLinkWatcher *
 nm_setting_team_port_get_link_watcher (NMSettingTeamPort *setting, guint idx)
 {
-	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
+	NMSettingTeamPortPrivate *priv;
 
 	g_return_val_if_fail (NM_IS_SETTING_TEAM_PORT (setting), NULL);
-	g_return_val_if_fail (idx < priv->link_watchers->len, NULL);
 
-	return priv->link_watchers->pdata[idx];
+	priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
+
+	g_return_val_if_fail (idx < priv->team_setting->d.link_watchers->len, NULL);
+
+	return priv->team_setting->d.link_watchers->pdata[idx];
 }
 
 /**
@@ -225,20 +221,12 @@ gboolean
 nm_setting_team_port_add_link_watcher (NMSettingTeamPort *setting,
                                        NMTeamLinkWatcher *link_watcher)
 {
-	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
-	guint i;
-
 	g_return_val_if_fail (NM_IS_SETTING_TEAM_PORT (setting), FALSE);
 	g_return_val_if_fail (link_watcher != NULL, FALSE);
 
-	for (i = 0; i < priv->link_watchers->len; i++) {
-		if (nm_team_link_watcher_equal (priv->link_watchers->pdata[i], link_watcher))
-			return FALSE;
-	}
-
-	g_ptr_array_add (priv->link_watchers, nm_team_link_watcher_dup (link_watcher));
-	_notify (setting, PROP_LINK_WATCHERS);
-	return TRUE;
+	return _maybe_changed (setting,
+	                       nm_team_setting_value_link_watchers_add (NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->team_setting,
+	                                                                link_watcher));
 }
 
 /**
@@ -253,13 +241,17 @@ nm_setting_team_port_add_link_watcher (NMSettingTeamPort *setting,
 void
 nm_setting_team_port_remove_link_watcher (NMSettingTeamPort *setting, guint idx)
 {
-	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
+	NMSettingTeamPortPrivate *priv;
 
 	g_return_if_fail (NM_IS_SETTING_TEAM_PORT (setting));
-	g_return_if_fail (idx < priv->link_watchers->len);
 
-	g_ptr_array_remove_index (priv->link_watchers, idx);
-	_notify (setting, PROP_LINK_WATCHERS);
+	priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
+
+	g_return_if_fail (idx < priv->team_setting->d.link_watchers->len);
+
+	_maybe_changed_with_assert (setting,
+	                            nm_team_setting_value_link_watchers_remove (priv->team_setting,
+	                                                                        idx));
 }
 
 /**
@@ -277,19 +269,12 @@ gboolean
 nm_setting_team_port_remove_link_watcher_by_value (NMSettingTeamPort *setting,
                                                    NMTeamLinkWatcher *link_watcher)
 {
-	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
-	guint i;
-
 	g_return_val_if_fail (NM_IS_SETTING_TEAM_PORT (setting), FALSE);
+	g_return_val_if_fail (link_watcher, FALSE);
 
-	for (i = 0; i < priv->link_watchers->len; i++) {
-		if (nm_team_link_watcher_equal (priv->link_watchers->pdata[i], link_watcher)) {
-			g_ptr_array_remove_index (priv->link_watchers, i);
-			_notify (setting, PROP_LINK_WATCHERS);
-			return TRUE;
-		}
-	}
-	return FALSE;
+	return _maybe_changed (setting,
+	                       nm_team_setting_value_link_watchers_remove_by_value (NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->team_setting,
+	                                                                            link_watcher));
 }
 
 /**
@@ -303,27 +288,12 @@ nm_setting_team_port_remove_link_watcher_by_value (NMSettingTeamPort *setting,
 void
 nm_setting_team_port_clear_link_watchers (NMSettingTeamPort *setting)
 {
-	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
-
 	g_return_if_fail (NM_IS_SETTING_TEAM_PORT (setting));
 
-	if (priv->link_watchers->len != 0) {
-		g_ptr_array_set_size (priv->link_watchers, 0);
-		_notify (setting, PROP_LINK_WATCHERS);
-	}
-}
-
-static GVariant *
-team_link_watchers_to_dbus (const GValue *prop_value)
-{
-	return _nm_utils_team_link_watchers_to_variant (g_value_get_boxed (prop_value));
-}
-
-static void
-team_link_watchers_from_dbus (GVariant   *dbus_value,
-                              GValue     *prop_value)
-{
-	g_value_take_boxed (prop_value, _nm_utils_team_link_watchers_from_variant (dbus_value));
+	_maybe_changed (setting,
+	                nm_team_setting_value_link_watchers_set_list (NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->team_setting,
+	                                                              NULL,
+	                                                              0));
 }
 
 static gboolean
@@ -360,29 +330,8 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		}
 	}
 
-	if (priv->config) {
-		if (strlen (priv->config) > 1*1024*1024) {
-			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			             _("team config exceeds size limit"));
-			g_prefix_error (error,
-			                "%s.%s: ",
-			                NM_SETTING_TEAM_PORT_SETTING_NAME,
-			                NM_SETTING_TEAM_PORT_CONFIG);
-			return FALSE;
-		}
-
-		if (!nm_utils_is_json_object (priv->config, error)) {
-			g_prefix_error (error,
-			                "%s.%s: ",
-			                NM_SETTING_TEAM_PORT_SETTING_NAME,
-			                NM_SETTING_TEAM_PORT_CONFIG);
-			/* We treat an empty string as no config for compatibility. */
-			return *priv->config ? FALSE : NM_SETTING_VERIFY_NORMALIZABLE;
-		}
-	}
-
-	/* NOTE: normalizable/normalizable-errors must appear at the end with decreasing severity.
-	 * Take care to properly order statements with priv->config above. */
+	if (!nm_team_setting_verify (priv->team_setting, error))
+		return FALSE;
 
 	return TRUE;
 }
@@ -390,8 +339,10 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	NMSettingTeamPortPrivate *a_priv;
@@ -401,31 +352,30 @@ compare_property (const NMSettInfoSetting *sett_info,
 
 		if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE))
 			return NM_TERNARY_DEFAULT;
-		if (!other)
+		if (!set_b)
 			return TRUE;
-		a_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
-		b_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (other);
-		return _nm_team_link_watchers_equal (a_priv->link_watchers,
-		                                     b_priv->link_watchers,
-		                                     TRUE);
+		a_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (set_a);
+		b_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (set_b);
+		return nm_team_link_watchers_equal (a_priv->team_setting->d.link_watchers,
+		                                    b_priv->team_setting->d.link_watchers,
+		                                    TRUE);
 	}
 
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_TEAM_PORT_CONFIG)) {
-		if (other) {
-			a_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
-			b_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (other);
-
+		if (set_b) {
 			if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) {
 				/* If we are trying to match a connection in order to assume it (and thus
 				 * @flags contains INFERRABLE), use the "relaxed" matching for team
 				 * configuration. Otherwise, for all other purposes (including connection
 				 * comparison before an update), resort to the default string comparison. */
-				return _nm_utils_team_config_equal (a_priv->config,
-				                                    b_priv->config,
-				                                    TRUE);
+				return TRUE;
 			}
 
-			return nm_streq0 (a_priv->config, b_priv->config);
+			a_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (set_a);
+			b_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (set_b);
+
+			return nm_streq0 (nm_team_setting_config_get (a_priv->team_setting),
+			                  nm_team_setting_config_get (b_priv->team_setting));
 		}
 
 		return TRUE;
@@ -433,11 +383,44 @@ compare_property (const NMSettInfoSetting *sett_info,
 
 	return NM_SETTING_CLASS (nm_setting_team_port_parent_class)->compare_property (sett_info,
 	                                                                               property_idx,
-	                                                                               setting,
-	                                                                               other,
+	                                                                               con_a,
+	                                                                               set_a,
+	                                                                               con_b,
+	                                                                               set_b,
 	                                                                               flags);
 }
 
+static void
+duplicate_copy_properties (const NMSettInfoSetting *sett_info,
+                           NMSetting *src,
+                           NMSetting *dst)
+{
+	_maybe_changed (NM_SETTING_TEAM_PORT (dst),
+	                nm_team_setting_reset (NM_SETTING_TEAM_PORT_GET_PRIVATE (dst)->team_setting,
+	                                       NM_SETTING_TEAM_PORT_GET_PRIVATE (src)->team_setting));
+}
+
+static gboolean
+init_from_dbus (NMSetting *setting,
+                GHashTable *keys,
+                GVariant *setting_dict,
+                GVariant *connection_dict,
+                guint /* NMSettingParseFlags */ parse_flags,
+                GError **error)
+{
+	guint32 changed = 0;
+	gboolean success;
+
+	success = nm_team_setting_reset_from_dbus (NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->team_setting,
+	                                           setting_dict,
+	                                           keys,
+	                                           &changed,
+	                                           parse_flags,
+	                                           error);
+	_maybe_changed (NM_SETTING_TEAM_PORT (setting), changed);
+	return success;
+}
+
 /*****************************************************************************/
 
 static void
@@ -448,27 +431,26 @@ get_property (GObject *object, guint prop_id,
 	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
 
 	switch (prop_id) {
-	case PROP_CONFIG:
-		g_value_set_string (value, nm_setting_team_port_get_config (setting));
+	case NM_TEAM_ATTRIBUTE_CONFIG:
+		g_value_set_string (value,
+		                    nm_team_setting_config_get (priv->team_setting));
 		break;
-	case PROP_QUEUE_ID:
-		g_value_set_int (value, priv->queue_id);
+	case NM_TEAM_ATTRIBUTE_PORT_STICKY:
+		g_value_set_boolean (value,
+		                     nm_team_setting_value_get_bool (priv->team_setting,
+		                                                     prop_id));
 		break;
-	case PROP_PRIO:
-		g_value_set_int (value, priv->prio);
+	case NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID:
+	case NM_TEAM_ATTRIBUTE_PORT_PRIO:
+	case NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO:
+	case NM_TEAM_ATTRIBUTE_PORT_LACP_KEY:
+		g_value_set_int (value,
+		                 nm_team_setting_value_get_int32 (priv->team_setting,
+		                                                  prop_id));
 		break;
-	case PROP_STICKY:
-		g_value_set_boolean (value, priv->sticky);
-		break;
-	case PROP_LACP_PRIO:
-		g_value_set_int (value, priv->lacp_prio);
-		break;
-	case PROP_LACP_KEY:
-		g_value_set_int (value, priv->lacp_key);
-		break;
-	case PROP_LINK_WATCHERS:
-		g_value_take_boxed (value, _nm_utils_copy_array (priv->link_watchers,
-		                                                 (NMUtilsCopyFunc) nm_team_link_watcher_dup,
+	case NM_TEAM_ATTRIBUTE_LINK_WATCHERS:
+		g_value_take_boxed (value, _nm_utils_copy_array (priv->team_setting->d.link_watchers,
+		                                                 (NMUtilsCopyFunc) _nm_team_link_watcher_ref,
 		                                                 (GDestroyNotify) nm_team_link_watcher_unref));
 		break;
 	default:
@@ -481,81 +463,40 @@ static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
-	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (object);
-	const GValue *align_value = NULL;
-	gboolean align_config = FALSE;
-
-#define JSON_TO_VAL(typ, id)   _nm_utils_json_extract_##typ (priv->config, _prop_to_keys[id], TRUE)
+	NMSettingTeamPort *setting = NM_SETTING_TEAM_PORT (object);
+	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
+	guint32 changed;
+	const GPtrArray *v_ptrarr;
 
 	switch (prop_id) {
-	case PROP_CONFIG:
-		g_free (priv->config);
-		priv->config = g_value_dup_string (value);
-		priv->queue_id =  JSON_TO_VAL (int, PROP_QUEUE_ID);
-		priv->prio =      JSON_TO_VAL (int, PROP_PRIO);
-		priv->sticky =    JSON_TO_VAL (boolean, PROP_STICKY);
-		priv->lacp_prio = JSON_TO_VAL (int, PROP_LACP_PRIO);
-		priv->lacp_key =  JSON_TO_VAL (int, PROP_LACP_KEY);
-
-		g_ptr_array_unref (priv->link_watchers);
-		priv->link_watchers = JSON_TO_VAL (ptr_array, PROP_LINK_WATCHERS);
-		break;
-	case PROP_QUEUE_ID:
-		if (priv->queue_id == g_value_get_int (value))
-			break;
-		priv->queue_id = g_value_get_int (value);
-		if (priv->queue_id != NM_SETTING_TEAM_PORT_QUEUE_ID_DEFAULT)
-			align_value = value;
-		align_config = TRUE;
-		break;
-	case PROP_PRIO:
-		if (priv->prio == g_value_get_int (value))
-			break;
-		priv->prio = g_value_get_int (value);
-		if (priv->prio)
-			align_value = value;
-		align_config = TRUE;
+	case NM_TEAM_ATTRIBUTE_CONFIG:
+		changed = nm_team_setting_config_set (priv->team_setting, g_value_get_string (value));
 		break;
-	case PROP_STICKY:
-		if (priv->sticky == g_value_get_boolean (value))
-			break;
-		priv->sticky = g_value_get_boolean (value);
-		if (priv->sticky)
-			align_value = value;
-		align_config = TRUE;
+	case NM_TEAM_ATTRIBUTE_PORT_STICKY:
+		changed = nm_team_setting_value_set_bool (priv->team_setting,
+		                                          prop_id,
+		                                          g_value_get_boolean (value));
 		break;
-	case PROP_LACP_PRIO:
-		if (priv->lacp_prio == g_value_get_int (value))
-			break;
-		priv->lacp_prio = g_value_get_int (value);
-		/* from libteam sources: lacp_prio default value is 0xff */
-		if (priv->lacp_prio != NM_SETTING_TEAM_PORT_LACP_PRIO_DEFAULT)
-			align_value = value;
-		align_config = TRUE;
+	case NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID:
+	case NM_TEAM_ATTRIBUTE_PORT_PRIO:
+	case NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO:
+	case NM_TEAM_ATTRIBUTE_PORT_LACP_KEY:
+		changed = nm_team_setting_value_set_int32 (priv->team_setting,
+		                                           prop_id,
+		                                           g_value_get_int (value));
 		break;
-	case PROP_LACP_KEY:
-		if (priv->lacp_key == g_value_get_int (value))
-			break;
-		priv->lacp_key = g_value_get_int (value);
-		if (priv->lacp_key)
-			align_value = value;
-		align_config = TRUE;
-		break;
-	case PROP_LINK_WATCHERS:
-		g_ptr_array_unref (priv->link_watchers);
-		priv->link_watchers = _nm_utils_copy_array (g_value_get_boxed (value),
-		                                            (NMUtilsCopyFunc) nm_team_link_watcher_dup,
-		                                            (GDestroyNotify) nm_team_link_watcher_unref);
-		if (priv->link_watchers->len)
-			align_value = value;
-		align_config = TRUE;
+	case NM_TEAM_ATTRIBUTE_LINK_WATCHERS:
+		v_ptrarr = g_value_get_boxed (value);
+		changed = nm_team_setting_value_link_watchers_set_list (priv->team_setting,
+		                                                        v_ptrarr ? (const NMTeamLinkWatcher *const*) v_ptrarr->pdata : NULL,
+		                                                        v_ptrarr ? v_ptrarr->len                                     : 0u);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
+		return;
 	}
-	if (align_config)
-		_nm_utils_json_append_gvalue (&priv->config, _prop_to_keys[prop_id], align_value);
+
+	_maybe_changed (setting, changed & ~(((guint32) 1) << prop_id));
 }
 
 /*****************************************************************************/
@@ -565,9 +506,7 @@ nm_setting_team_port_init (NMSettingTeamPort *setting)
 {
 	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
 
-	priv->queue_id = NM_SETTING_TEAM_PORT_QUEUE_ID_DEFAULT;
-	priv->lacp_prio = NM_SETTING_TEAM_PORT_LACP_PRIO_DEFAULT;
-	priv->link_watchers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref);
+	priv->team_setting = nm_team_setting_new (TRUE, NULL);
 }
 
 /**
@@ -588,8 +527,7 @@ finalize (GObject *object)
 {
 	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (object);
 
-	g_free (priv->config);
-	g_ptr_array_unref (priv->link_watchers);
+	nm_team_setting_free (priv->team_setting);
 
 	G_OBJECT_CLASS (nm_setting_team_port_parent_class)->finalize (object);
 }
@@ -607,8 +545,17 @@ nm_setting_team_port_class_init (NMSettingTeamPortClass *klass)
 	object_class->set_property     = set_property;
 	object_class->finalize         = finalize;
 
-	setting_class->compare_property = compare_property;
-	setting_class->verify           = verify;
+	setting_class->compare_property          = compare_property;
+	setting_class->verify                    = verify;
+	setting_class->duplicate_copy_properties = duplicate_copy_properties;
+	setting_class->init_from_dbus            = init_from_dbus;
+
+#define _property_override(_properties_override, _param_spec, _variant_type, _is_link_watcher) \
+	_properties_override_add ((_properties_override), \
+	                          .param_spec          = (_param_spec), \
+	                          .dbus_type           = G_VARIANT_TYPE (""_variant_type""), \
+	                          .to_dbus_fcn         = _nm_team_settings_property_to_dbus, \
+	                          .gprop_from_dbus_fcn = ((_is_link_watcher) ? _nm_team_settings_property_from_dbus_link_watchers : NULL))
 
 	/**
 	 * NMSettingTeamPort:config:
@@ -624,12 +571,13 @@ nm_setting_team_port_class_init (NMSettingTeamPortClass *klass)
 	 * description: Team port configuration in JSON. See man teamd.conf for details.
 	 * ---end---
 	 */
-	obj_properties[PROP_CONFIG] =
+	obj_properties[NM_TEAM_ATTRIBUTE_CONFIG] =
 	    g_param_spec_string (NM_SETTING_TEAM_PORT_CONFIG, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_CONFIG], "s", FALSE);
 
 	/**
 	 * NMSettingTeamPort:queue-id:
@@ -639,11 +587,12 @@ nm_setting_team_port_class_init (NMSettingTeamPortClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_QUEUE_ID] =
+	obj_properties[NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID] =
 	    g_param_spec_int (NM_SETTING_TEAM_PORT_QUEUE_ID, "", "",
-	                      G_MININT32, G_MAXINT32, 0,
+	                      G_MININT32, G_MAXINT32, -1,
 	                      G_PARAM_READWRITE |
 	                      G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID], "i", FALSE);
 
 	/**
 	 * NMSettingTeamPort:prio:
@@ -652,11 +601,12 @@ nm_setting_team_port_class_init (NMSettingTeamPortClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_PRIO] =
+	obj_properties[NM_TEAM_ATTRIBUTE_PORT_PRIO] =
 	    g_param_spec_int (NM_SETTING_TEAM_PORT_PRIO, "", "",
 	                      G_MININT32, G_MAXINT32, 0,
 	                      G_PARAM_READWRITE |
 	                      G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_PORT_PRIO], "i", FALSE);
 
 	/**
 	 * NMSettingTeamPort:sticky:
@@ -665,11 +615,12 @@ nm_setting_team_port_class_init (NMSettingTeamPortClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_STICKY] =
+	obj_properties[NM_TEAM_ATTRIBUTE_PORT_STICKY] =
 	    g_param_spec_boolean (NM_SETTING_TEAM_PORT_STICKY, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
 	                          G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_PORT_STICKY], "b", FALSE);
 
 	/**
 	 * NMSettingTeamPort:lacp-prio:
@@ -678,11 +629,12 @@ nm_setting_team_port_class_init (NMSettingTeamPortClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_LACP_PRIO] =
+	obj_properties[NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO] =
 	    g_param_spec_int (NM_SETTING_TEAM_PORT_LACP_PRIO, "", "",
-	                      G_MININT32, G_MAXINT32, 0,
+	                      G_MININT32, G_MAXINT32, -1,
 	                      G_PARAM_READWRITE |
 	                      G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO], "i", FALSE);
 
 	/**
 	 * NMSettingTeamPort:lacp-key:
@@ -691,11 +643,12 @@ nm_setting_team_port_class_init (NMSettingTeamPortClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_LACP_KEY] =
+	obj_properties[NM_TEAM_ATTRIBUTE_PORT_LACP_KEY] =
 	    g_param_spec_int (NM_SETTING_TEAM_PORT_LACP_KEY, "", "",
-	                      G_MININT32, G_MAXINT32, 0,
+	                      G_MININT32, G_MAXINT32, -1,
 	                      G_PARAM_READWRITE |
 	                      G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_PORT_LACP_KEY], "i", FALSE);
 
 	/**
 	 * NMSettingTeamPort:link-watchers: (type GPtrArray(NMTeamLinkWatcher))
@@ -711,19 +664,14 @@ nm_setting_team_port_class_init (NMSettingTeamPortClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_LINK_WATCHERS] =
+	obj_properties[NM_TEAM_ATTRIBUTE_LINK_WATCHERS] =
 	    g_param_spec_boxed (NM_SETTING_TEAM_PORT_LINK_WATCHERS, "", "",
 	                        G_TYPE_PTR_ARRAY,
 	                        G_PARAM_READWRITE |
 	                        G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_LINK_WATCHERS], "aa{sv}", TRUE);
 
-	_properties_override_add_transform (properties_override,
-	                                    obj_properties[PROP_LINK_WATCHERS],
-	                                    G_VARIANT_TYPE ("aa{sv}"),
-	                                    team_link_watchers_to_dbus,
-	                                    team_link_watchers_from_dbus);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+	g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_properties), obj_properties);
 
 	_nm_setting_class_commit_full (setting_class, NM_META_SETTING_TYPE_TEAM_PORT,
 	                               NULL, properties_override);
diff --git a/libnm-core/nm-setting-team-port.h b/libnm-core/nm-setting-team-port.h
index 57611750..527c9077 100644
--- a/libnm-core/nm-setting-team-port.h
+++ b/libnm-core/nm-setting-team-port.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-team.c b/libnm-core/nm-setting-team.c
index 9fd070c1..d4940924 100644
--- a/libnm-core/nm-setting-team.c
+++ b/libnm-core/nm-setting-team.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -27,6 +26,7 @@
 
 #include "nm-utils.h"
 #include "nm-utils-private.h"
+#include "nm-team-utils.h"
 #include "nm-connection-private.h"
 
 /**
@@ -38,17 +38,17 @@
  **/
 
 /*****************************************************************************
- * NMTeamLinkWatch
+ * NMTeamLinkWatcher
  *****************************************************************************/
 
 G_DEFINE_BOXED_TYPE (NMTeamLinkWatcher, nm_team_link_watcher,
-                     nm_team_link_watcher_dup, nm_team_link_watcher_unref)
+                     _nm_team_link_watcher_ref, nm_team_link_watcher_unref)
 
-enum LinkWatcherTypes {
+typedef enum {
 	LINK_WATCHER_ETHTOOL   = 0,
 	LINK_WATCHER_NSNA_PING = 1,
-	LINK_WATCHER_ARP_PING  = 2
-};
+	LINK_WATCHER_ARP_PING  = 2,
+} LinkWatcherTypes;
 
 static const char* _link_watcher_name[] = {
 	[LINK_WATCHER_ETHTOOL]   = NM_TEAM_LINK_WATCHER_ETHTOOL,
@@ -57,36 +57,30 @@ static const char* _link_watcher_name[] = {
 };
 
 struct NMTeamLinkWatcher {
-	guint refcount;
+
+	int ref_count;
 
 	guint8 type; /* LinkWatcherTypes */
 
-	/*
-	 * The union is constructed in order to allow mapping the options of all the
-	 * watchers on the arp_ping one: this would allow to manipulate all the watchers
-	 * by using the arp_ping struct. See for instance the nm_team_link_watcher_unref()
-	 * and nm_team_link_watcher_equal() functions. So, if you need to change the union
-	 * be careful.
-	 */
 	union {
 		struct {
 			int delay_up;
 			int delay_down;
 		} ethtool;
 		struct {
+			const char *target_host;
 			int init_wait;
 			int interval;
 			int missed_max;
-			char *target_host;
 		} nsna_ping;
 		struct {
+			const char *target_host;
+			const char *source_host;
 			int init_wait;
 			int interval;
 			int missed_max;
-			char *target_host;
-			char *source_host;
-			NMTeamLinkWatcherArpPingFlags flags;
 			int vlanid;
+			NMTeamLinkWatcherArpPingFlags flags;
 		} arp_ping;
 	};
 };
@@ -94,15 +88,15 @@ struct NMTeamLinkWatcher {
 #define _CHECK_WATCHER_VOID(watcher) \
 	G_STMT_START { \
 		g_return_if_fail (watcher != NULL); \
-		g_return_if_fail (watcher->refcount > 0); \
-		g_return_if_fail (watcher->type <= LINK_WATCHER_ARP_PING); \
+		g_return_if_fail (watcher->ref_count > 0); \
+		nm_assert (watcher->type <= LINK_WATCHER_ARP_PING); \
 	} G_STMT_END
 
 #define _CHECK_WATCHER(watcher, err_val) \
 	G_STMT_START { \
 		g_return_val_if_fail (watcher != NULL, err_val); \
-		g_return_val_if_fail (watcher->refcount > 0, err_val); \
-		g_return_val_if_fail (watcher->type <= LINK_WATCHER_ARP_PING, err_val); \
+		g_return_val_if_fail (watcher->ref_count > 0, err_val); \
+		nm_assert (watcher->type <= LINK_WATCHER_ARP_PING); \
 	} G_STMT_END
 
 /**
@@ -136,9 +130,9 @@ nm_team_link_watcher_new_ethtool (int delay_up,
 		return NULL;
 	}
 
-	watcher = g_slice_new0 (NMTeamLinkWatcher);
-	watcher->refcount = 1;
+	watcher = g_malloc (nm_offsetofend (NMTeamLinkWatcher, ethtool));
 
+	watcher->ref_count = 1;
 	watcher->type = LINK_WATCHER_ETHTOOL;
 	watcher->ethtool.delay_up = delay_up;
 	watcher->ethtool.delay_down = delay_down;
@@ -170,6 +164,8 @@ nm_team_link_watcher_new_nsna_ping (int init_wait,
 {
 	NMTeamLinkWatcher *watcher;
 	const char *val_fail = NULL;
+	char *str;
+	gsize l_target_host;
 
 	if (!target_host) {
 		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
@@ -195,14 +191,20 @@ nm_team_link_watcher_new_nsna_ping (int init_wait,
 		return NULL;
 	}
 
-	watcher = g_slice_new0 (NMTeamLinkWatcher);
-	watcher->refcount = 1;
+	l_target_host = strlen (target_host) + 1;
 
+	watcher = g_malloc (  nm_offsetofend (NMTeamLinkWatcher, nsna_ping)
+	                    + l_target_host);
+
+	watcher->ref_count = 1;
 	watcher->type = LINK_WATCHER_NSNA_PING;
 	watcher->nsna_ping.init_wait = init_wait;
 	watcher->nsna_ping.interval = interval;
 	watcher->nsna_ping.missed_max = missed_max;
-	watcher->nsna_ping.target_host = g_strdup (target_host);
+
+	str = &((char *) watcher)[nm_offsetofend (NMTeamLinkWatcher, nsna_ping)];
+	watcher->nsna_ping.target_host = str;
+	memcpy (str, target_host, l_target_host);
 
 	return watcher;
 }
@@ -275,8 +277,12 @@ nm_team_link_watcher_new_arp_ping2 (int init_wait,
 {
 	NMTeamLinkWatcher *watcher;
 	const char *val_fail = NULL;
+	char *str;
+	gsize l_target_host;
+	gsize l_source_host;
 
-	if (!target_host || !source_host) {
+	if (   !target_host
+	    || !source_host) {
 		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
 		             _("Missing %s in arp_ping link watcher"),
 		             target_host ? "source-host" : "target-host");
@@ -313,18 +319,38 @@ nm_team_link_watcher_new_arp_ping2 (int init_wait,
 		return NULL;
 	}
 
-	watcher = g_slice_new0 (NMTeamLinkWatcher);
-	watcher->refcount = 1;
+	l_target_host = strlen (target_host) + 1;
+	l_source_host = strlen (source_host) + 1;
+
+	watcher = g_malloc (  nm_offsetofend (NMTeamLinkWatcher, arp_ping)
+	                    + l_target_host
+	                    + l_source_host);
 
+	watcher->ref_count = 1;
 	watcher->type = LINK_WATCHER_ARP_PING;
 	watcher->arp_ping.init_wait = init_wait;
 	watcher->arp_ping.interval = interval;
 	watcher->arp_ping.missed_max = missed_max;
-	watcher->arp_ping.target_host = g_strdup (target_host);
-	watcher->arp_ping.source_host = g_strdup (source_host);
 	watcher->arp_ping.flags = flags;
 	watcher->arp_ping.vlanid = vlanid;
 
+	str = &((char *) watcher)[nm_offsetofend (NMTeamLinkWatcher, arp_ping)];
+	watcher->arp_ping.target_host = str;
+	memcpy (str, target_host, l_target_host);
+
+	str += l_target_host;
+	watcher->arp_ping.source_host = str;
+	memcpy (str, source_host, l_source_host);
+
+	return watcher;
+}
+
+NMTeamLinkWatcher *
+_nm_team_link_watcher_ref (NMTeamLinkWatcher *watcher)
+{
+	_CHECK_WATCHER (watcher, NULL);
+
+	g_atomic_int_inc (&watcher->ref_count);
 	return watcher;
 }
 
@@ -337,10 +363,9 @@ nm_team_link_watcher_new_arp_ping2 (int init_wait,
  * Since: 1.12
  **/
 void
-nm_team_link_watcher_ref (NMTeamLinkWatcher *watcher){
-	_CHECK_WATCHER_VOID (watcher);
-
-	watcher->refcount++;
+nm_team_link_watcher_ref (NMTeamLinkWatcher *watcher)
+{
+	_nm_team_link_watcher_ref (watcher);
 }
 
 /**
@@ -357,12 +382,40 @@ nm_team_link_watcher_unref (NMTeamLinkWatcher *watcher)
 {
 	_CHECK_WATCHER_VOID (watcher);
 
-	watcher->refcount--;
-	if (watcher->refcount == 0) {
-		g_free (watcher->arp_ping.target_host);
-		g_free (watcher->arp_ping.source_host);
-		g_slice_free (NMTeamLinkWatcher, watcher);
+	if (g_atomic_int_dec_and_test (&watcher->ref_count))
+		g_free (watcher);
+}
+
+int
+nm_team_link_watcher_cmp (const NMTeamLinkWatcher *watcher,
+                          const NMTeamLinkWatcher *other)
+{
+	NM_CMP_SELF (watcher, other);
+
+	NM_CMP_FIELD (watcher, other, type);
+
+	switch (watcher->type) {
+	case LINK_WATCHER_ETHTOOL:
+		NM_CMP_FIELD (watcher, other, ethtool.delay_up);
+		NM_CMP_FIELD (watcher, other, ethtool.delay_down);
+		break;
+	case LINK_WATCHER_NSNA_PING:
+		NM_CMP_FIELD_STR (watcher, other, nsna_ping.target_host);
+		NM_CMP_FIELD (watcher, other, nsna_ping.init_wait);
+		NM_CMP_FIELD (watcher, other, nsna_ping.interval);
+		NM_CMP_FIELD (watcher, other, nsna_ping.missed_max);
+		break;
+	case LINK_WATCHER_ARP_PING:
+		NM_CMP_FIELD_STR (watcher, other, arp_ping.target_host);
+		NM_CMP_FIELD_STR (watcher, other, arp_ping.source_host);
+		NM_CMP_FIELD (watcher, other, arp_ping.init_wait);
+		NM_CMP_FIELD (watcher, other, arp_ping.interval);
+		NM_CMP_FIELD (watcher, other, arp_ping.missed_max);
+		NM_CMP_FIELD (watcher, other, arp_ping.vlanid);
+		NM_CMP_FIELD (watcher, other, arp_ping.flags);
+		break;
 	}
+	return 0;
 }
 
 /**
@@ -378,50 +431,58 @@ nm_team_link_watcher_unref (NMTeamLinkWatcher *watcher)
  * Since: 1.12
  **/
 gboolean
-nm_team_link_watcher_equal (NMTeamLinkWatcher *watcher, NMTeamLinkWatcher *other)
+nm_team_link_watcher_equal (const NMTeamLinkWatcher *watcher,
+                            const NMTeamLinkWatcher *other)
 {
-	_CHECK_WATCHER (watcher, FALSE);
-	_CHECK_WATCHER (other, FALSE);
-
-	if (   watcher->type != other->type
-	    || !nm_streq0 (watcher->arp_ping.target_host, other->arp_ping.target_host)
-	    || !nm_streq0 (watcher->arp_ping.source_host, other->arp_ping.source_host)
-	    || watcher->arp_ping.init_wait != other->arp_ping.init_wait
-	    || watcher->arp_ping.interval != other->arp_ping.interval
-	    || watcher->arp_ping.missed_max != other->arp_ping.missed_max
-	    || watcher->arp_ping.vlanid != other->arp_ping.vlanid
-	    || watcher->arp_ping.flags != other->arp_ping.flags)
-		return FALSE;
+	return nm_team_link_watcher_cmp (watcher, other) == 0;
+}
 
-	return TRUE;
+static int
+_team_link_watchers_cmp_p_with_data (gconstpointer data_a,
+                                     gconstpointer data_b,
+                                     gpointer user_data)
+{
+	return nm_team_link_watcher_cmp (*((const NMTeamLinkWatcher *const*) data_a),
+	                                 *((const NMTeamLinkWatcher *const*) data_b));
 }
 
-gboolean
-_nm_team_link_watchers_equal (GPtrArray *a, GPtrArray *b, gboolean ignore_order)
+int
+nm_team_link_watchers_cmp (const NMTeamLinkWatcher *const*a,
+                           const NMTeamLinkWatcher *const*b,
+                           gsize len,
+                           gboolean ignore_order)
 {
-	guint i, j;
+	gs_free const NMTeamLinkWatcher **a_free = NULL;
+	gs_free const NMTeamLinkWatcher **b_free = NULL;
+	guint i;
 
-	if (a->len != b->len)
-		return FALSE;
-	if (ignore_order) {
-		/* FIXME: comparing this way is O(n^2). Don't do that, instead
-		 *        add nm_team_link_watcher_cmp(), sort both lists, and
-		 *        compare step by step. */
-		for (i = 0; i < a->len; i++) {
-			for (j = 0; j < b->len; j++) {
-				if (nm_team_link_watcher_equal (a->pdata[i], b->pdata[j]))
-					break;
-			}
-			if (j == b->len)
-				return FALSE;
-		}
-	} else {
-		for (i = 0; i < a->len; i++) {
-			if (!nm_team_link_watcher_equal (a->pdata[i], b->pdata[i]))
-				return FALSE;
-		}
+	if (   ignore_order
+	    && len > 1) {
+		a = nm_memdup_maybe_a (200, a, len * sizeof (*a), &a_free);
+		b = nm_memdup_maybe_a (200, b, len * sizeof (*b), &b_free);
+		g_qsort_with_data ((gpointer) a, len, sizeof (*a), _team_link_watchers_cmp_p_with_data, NULL);
+		g_qsort_with_data ((gpointer) b, len, sizeof (*b), _team_link_watchers_cmp_p_with_data, NULL);
 	}
-	return TRUE;
+	for (i = 0; i < len; i++) {
+		NM_CMP_RETURN (nm_team_link_watcher_cmp (a[i],
+		                                         b[i]));
+	}
+	return 0;
+}
+
+gboolean
+nm_team_link_watchers_equal (const GPtrArray *a,
+                             const GPtrArray *b,
+                             gboolean ignore_order)
+{
+	return    a == b
+	       || (   a
+	           && b
+	           && a->len == b->len
+	           && (nm_team_link_watchers_cmp ((const NMTeamLinkWatcher *const*) a->pdata,
+	                                          (const NMTeamLinkWatcher *const*) b->pdata,
+	                                          a->len,
+	                                          ignore_order) == 0));
 }
 
 /**
@@ -435,7 +496,7 @@ _nm_team_link_watchers_equal (GPtrArray *a, GPtrArray *b, gboolean ignore_order)
  * Since: 1.12
  **/
 NMTeamLinkWatcher *
-nm_team_link_watcher_dup (NMTeamLinkWatcher *watcher)
+nm_team_link_watcher_dup (const NMTeamLinkWatcher *watcher)
 {
 	_CHECK_WATCHER (watcher, NULL);
 
@@ -462,7 +523,7 @@ nm_team_link_watcher_dup (NMTeamLinkWatcher *watcher)
 		                                           watcher->arp_ping.flags,
 		                                          NULL);
 	default:
-		g_assert_not_reached ();
+		nm_assert_not_reached ();
 		return NULL;
 	}
 }
@@ -476,7 +537,7 @@ nm_team_link_watcher_dup (NMTeamLinkWatcher *watcher)
  * Since: 1.12
  **/
 const char *
-nm_team_link_watcher_get_name (NMTeamLinkWatcher *watcher)
+nm_team_link_watcher_get_name (const NMTeamLinkWatcher *watcher)
 {
 	_CHECK_WATCHER (watcher, NULL);
 
@@ -493,13 +554,13 @@ nm_team_link_watcher_get_name (NMTeamLinkWatcher *watcher)
  * Since: 1.12
  **/
 int
-nm_team_link_watcher_get_delay_up (NMTeamLinkWatcher *watcher)
+nm_team_link_watcher_get_delay_up (const NMTeamLinkWatcher *watcher)
 {
 	_CHECK_WATCHER (watcher, 0);
 
-	if (watcher->type != LINK_WATCHER_ETHTOOL)
-		return -1;
-	return watcher->ethtool.delay_up;
+	if (watcher->type == LINK_WATCHER_ETHTOOL)
+		return watcher->ethtool.delay_up;
+	return -1;
 }
 
 /**
@@ -512,13 +573,13 @@ nm_team_link_watcher_get_delay_up (NMTeamLinkWatcher *watcher)
  * Since: 1.12
  **/
 int
-nm_team_link_watcher_get_delay_down (NMTeamLinkWatcher *watcher)
+nm_team_link_watcher_get_delay_down (const NMTeamLinkWatcher *watcher)
 {
 	_CHECK_WATCHER (watcher, 0);
 
-	if (watcher->type != LINK_WATCHER_ETHTOOL)
-		return -1;
-	return watcher->ethtool.delay_down;
+	if (watcher->type == LINK_WATCHER_ETHTOOL)
+		return watcher->ethtool.delay_down;
+	return -1;
 }
 
 /**
@@ -531,15 +592,15 @@ nm_team_link_watcher_get_delay_down (NMTeamLinkWatcher *watcher)
  * Since: 1.12
  **/
 int
-nm_team_link_watcher_get_init_wait (NMTeamLinkWatcher *watcher)
+nm_team_link_watcher_get_init_wait (const NMTeamLinkWatcher *watcher)
 {
 	_CHECK_WATCHER (watcher, 0);
 
-	if (!NM_IN_SET (watcher->type,
-	                LINK_WATCHER_NSNA_PING,
-	                LINK_WATCHER_ARP_PING))
-		return -1;
-	return watcher->arp_ping.init_wait;
+	if (watcher->type == LINK_WATCHER_NSNA_PING)
+		return watcher->nsna_ping.init_wait;
+	if (watcher->type == LINK_WATCHER_ARP_PING)
+		return watcher->arp_ping.init_wait;
+	return -1;
 }
 
 /**
@@ -552,15 +613,15 @@ nm_team_link_watcher_get_init_wait (NMTeamLinkWatcher *watcher)
  * Since: 1.12
  **/
 int
-nm_team_link_watcher_get_interval (NMTeamLinkWatcher *watcher)
+nm_team_link_watcher_get_interval (const NMTeamLinkWatcher *watcher)
 {
 	_CHECK_WATCHER (watcher, 0);
 
-	if (!NM_IN_SET (watcher->type,
-	                LINK_WATCHER_NSNA_PING,
-	                LINK_WATCHER_ARP_PING))
-		return -1;
-	return watcher->arp_ping.interval;
+	if (watcher->type == LINK_WATCHER_NSNA_PING)
+		return watcher->nsna_ping.interval;
+	if (watcher->type == LINK_WATCHER_ARP_PING)
+		return watcher->arp_ping.interval;
+	return -1;
 }
 
 /**
@@ -572,15 +633,15 @@ nm_team_link_watcher_get_interval (NMTeamLinkWatcher *watcher)
  * Since: 1.12
  **/
 int
-nm_team_link_watcher_get_missed_max (NMTeamLinkWatcher *watcher)
+nm_team_link_watcher_get_missed_max (const NMTeamLinkWatcher *watcher)
 {
 	_CHECK_WATCHER (watcher, 0);
 
-	if (!NM_IN_SET (watcher->type,
-	                LINK_WATCHER_NSNA_PING,
-	                LINK_WATCHER_ARP_PING))
-		return -1;
-	return watcher->arp_ping.missed_max;
+	if (watcher->type == LINK_WATCHER_NSNA_PING)
+		return watcher->nsna_ping.missed_max;
+	if (watcher->type == LINK_WATCHER_ARP_PING)
+		return watcher->arp_ping.missed_max;
+	return -1;
 }
 
 /**
@@ -592,13 +653,13 @@ nm_team_link_watcher_get_missed_max (NMTeamLinkWatcher *watcher)
  * Since: 1.16
  **/
 int
-nm_team_link_watcher_get_vlanid (NMTeamLinkWatcher *watcher)
+nm_team_link_watcher_get_vlanid (const NMTeamLinkWatcher *watcher)
 {
 	_CHECK_WATCHER (watcher, -1);
 
-	if (watcher->type != LINK_WATCHER_ARP_PING)
-		return -1;
-	return watcher->arp_ping.vlanid;
+	if (watcher->type == LINK_WATCHER_ARP_PING)
+		return watcher->arp_ping.vlanid;
+	return -1;
 }
 
 /**
@@ -611,11 +672,15 @@ nm_team_link_watcher_get_vlanid (NMTeamLinkWatcher *watcher)
  * Since: 1.12
  **/
 const char *
-nm_team_link_watcher_get_target_host (NMTeamLinkWatcher *watcher)
+nm_team_link_watcher_get_target_host (const NMTeamLinkWatcher *watcher)
 {
 	_CHECK_WATCHER (watcher, NULL);
 
-	return watcher->arp_ping.target_host;
+	if (watcher->type == LINK_WATCHER_NSNA_PING)
+		return watcher->nsna_ping.target_host;
+	if (watcher->type == LINK_WATCHER_ARP_PING)
+		return watcher->arp_ping.target_host;
+	return NULL;
 }
 
 /**
@@ -627,11 +692,13 @@ nm_team_link_watcher_get_target_host (NMTeamLinkWatcher *watcher)
  * Since: 1.12
  **/
 const char *
-nm_team_link_watcher_get_source_host (NMTeamLinkWatcher *watcher)
+nm_team_link_watcher_get_source_host (const NMTeamLinkWatcher *watcher)
 {
 	_CHECK_WATCHER (watcher, NULL);
 
-	return watcher->arp_ping.source_host;
+	if (watcher->type == LINK_WATCHER_ARP_PING)
+		return watcher->arp_ping.source_host;
+	return NULL;
 }
 
 /**
@@ -643,70 +710,21 @@ nm_team_link_watcher_get_source_host (NMTeamLinkWatcher *watcher)
  * Since: 1.12
  **/
 NMTeamLinkWatcherArpPingFlags
-nm_team_link_watcher_get_flags (NMTeamLinkWatcher *watcher)
+nm_team_link_watcher_get_flags (const NMTeamLinkWatcher *watcher)
 {
 	_CHECK_WATCHER (watcher, 0);
 
-	return watcher->arp_ping.flags;
+	if (watcher->type == LINK_WATCHER_ARP_PING)
+		return watcher->arp_ping.flags;
+	return 0;
 }
 
 /*****************************************************************************/
 
-NM_GOBJECT_PROPERTIES_DEFINE (NMSettingTeam,
-	PROP_CONFIG,
-	PROP_NOTIFY_PEERS_COUNT,
-	PROP_NOTIFY_PEERS_INTERVAL,
-	PROP_MCAST_REJOIN_COUNT,
-	PROP_MCAST_REJOIN_INTERVAL,
-	PROP_RUNNER,
-	PROP_RUNNER_HWADDR_POLICY,
-	PROP_RUNNER_TX_HASH,
-	PROP_RUNNER_TX_BALANCER,
-	PROP_RUNNER_TX_BALANCER_INTERVAL,
-	PROP_RUNNER_ACTIVE,
-	PROP_RUNNER_FAST_RATE,
-	PROP_RUNNER_SYS_PRIO,
-	PROP_RUNNER_MIN_PORTS,
-	PROP_RUNNER_AGG_SELECT_POLICY,
-	PROP_LINK_WATCHERS,
-);
-
-static const _NMUtilsTeamPropertyKeys _prop_to_keys[_PROPERTY_ENUMS_LAST] = {
-	[PROP_CONFIG] =                      { },
-	[PROP_NOTIFY_PEERS_COUNT] =          { .key1 = "notify_peers", .key2 = "count",                                           },
-	[PROP_NOTIFY_PEERS_INTERVAL] =       { .key1 = "notify_peers", .key2 = "interval",                                        },
-	[PROP_MCAST_REJOIN_COUNT] =          { .key1 = "mcast_rejoin", .key2 = "count",                                           },
-	[PROP_MCAST_REJOIN_INTERVAL] =       { .key1 = "mcast_rejoin", .key2 = "interval",                                        },
-	[PROP_RUNNER] =                      { .key1 = "runner",       .key2 = "name",                                            .default_str = NM_SETTING_TEAM_RUNNER_DEFAULT, },
-	[PROP_RUNNER_HWADDR_POLICY] =        { .key1 = "runner",       .key2 = "hwaddr_policy",                                   },
-	[PROP_RUNNER_TX_HASH] =              { .key1 = "runner",       .key2 = "tx_hash",                                         },
-	[PROP_RUNNER_TX_BALANCER] =          { .key1 = "runner",       .key2 = "tx_balancer",       .key3 = "name", },
-	[PROP_RUNNER_TX_BALANCER_INTERVAL] = { .key1 = "runner",       .key2 = "tx_balancer",       .key3 = "balancing_interval", .default_int = -1 },
-	[PROP_RUNNER_ACTIVE] =               { .key1 = "runner",       .key2 = "active",                                          },
-	[PROP_RUNNER_FAST_RATE] =            { .key1 = "runner",       .key2 = "fast_rate",                                       },
-	[PROP_RUNNER_SYS_PRIO] =             { .key1 = "runner",       .key2 = "sys_prio",                                        .default_int = -1, },
-	[PROP_RUNNER_MIN_PORTS] =            { .key1 = "runner",       .key2 = "min_ports",                                       .default_int = -1, },
-	[PROP_RUNNER_AGG_SELECT_POLICY] =    { .key1 = "runner",       .key2 = "agg_select_policy",                               },
-	[PROP_LINK_WATCHERS] =               { .key1 = "link_watch",                                                              },
-};
+static GParamSpec *obj_properties[_NM_TEAM_ATTRIBUTE_MASTER_NUM] = { NULL, };
 
 typedef struct {
-	char *config;
-	int notify_peers_count;
-	int notify_peers_interval;
-	int mcast_rejoin_count;
-	int mcast_rejoin_interval;
-	char *runner;
-	char *runner_hwaddr_policy;
-	GPtrArray *runner_tx_hash;
-	char *runner_tx_balancer;
-	int runner_tx_balancer_interval;
-	gboolean runner_active;
-	gboolean runner_fast_rate;
-	int runner_sys_prio;
-	int runner_min_ports;
-	char *runner_agg_select_policy;
-	GPtrArray *link_watchers; /* Array of NMTeamLinkWatcher */
+	NMTeamSetting *team_setting;
 } NMSettingTeamPrivate;
 
 G_DEFINE_TYPE (NMSettingTeam, nm_setting_team, NM_TYPE_SETTING)
@@ -715,6 +733,23 @@ G_DEFINE_TYPE (NMSettingTeam, nm_setting_team, NM_TYPE_SETTING)
 
 /*****************************************************************************/
 
+NMTeamSetting *
+_nm_setting_team_get_team_setting (NMSettingTeam *setting)
+{
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting;
+}
+
+/*****************************************************************************/
+
+#define _maybe_changed(self, changed) \
+	nm_team_setting_maybe_changed (NM_SETTING (_NM_ENSURE_TYPE (NMSettingTeam *, self)), (const GParamSpec *const*) obj_properties, (changed))
+
+#define _maybe_changed_with_assert(self, changed) \
+	G_STMT_START { \
+		if (!_maybe_changed ((self), (changed))) \
+			nm_assert_not_reached (); \
+	} G_STMT_END
+
 /**
  * nm_setting_team_get_config:
  * @setting: the #NMSettingTeam
@@ -726,7 +761,7 @@ nm_setting_team_get_config (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), NULL);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->config;
+	return nm_team_setting_config_get (NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting);
 }
 
 /**
@@ -742,7 +777,7 @@ nm_setting_team_get_notify_peers_count (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), 0);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->notify_peers_count;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.notify_peers_count;
 }
 
 /**
@@ -758,7 +793,7 @@ nm_setting_team_get_notify_peers_interval (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), 0);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->notify_peers_interval;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.notify_peers_interval;
 }
 
 /**
@@ -774,7 +809,7 @@ nm_setting_team_get_mcast_rejoin_count (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), 0);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->mcast_rejoin_count;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.mcast_rejoin_count;
 }
 
 /**
@@ -790,7 +825,7 @@ nm_setting_team_get_mcast_rejoin_interval (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), 0);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->mcast_rejoin_interval;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.mcast_rejoin_interval;
 }
 
 /**
@@ -806,7 +841,7 @@ nm_setting_team_get_runner (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), NULL);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->runner;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.runner;
 }
 
 /**
@@ -822,7 +857,7 @@ nm_setting_team_get_runner_hwaddr_policy (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), NULL);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->runner_hwaddr_policy;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.runner_hwaddr_policy;
 }
 
 /**
@@ -838,7 +873,7 @@ nm_setting_team_get_runner_tx_balancer (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), NULL);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->runner_tx_balancer;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.runner_tx_balancer;
 }
 
 /**
@@ -854,7 +889,7 @@ nm_setting_team_get_runner_tx_balancer_interval (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), 0);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->runner_tx_balancer_interval;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.runner_tx_balancer_interval;
 }
 
 /**
@@ -870,7 +905,7 @@ nm_setting_team_get_runner_active (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), FALSE);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->runner_active;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.runner_active;
 }
 
 /**
@@ -886,7 +921,7 @@ nm_setting_team_get_runner_fast_rate (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), FALSE);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->runner_fast_rate;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.runner_fast_rate;
 }
 
 /**
@@ -902,7 +937,7 @@ nm_setting_team_get_runner_sys_prio (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), 0);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->runner_sys_prio;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.runner_sys_prio;
 }
 
 /**
@@ -918,7 +953,7 @@ nm_setting_team_get_runner_min_ports (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), 0);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->runner_min_ports;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.runner_min_ports;
 }
 
 /**
@@ -934,7 +969,7 @@ nm_setting_team_get_runner_agg_select_policy (NMSettingTeam *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), NULL);
 
-	return NM_SETTING_TEAM_GET_PRIVATE (setting)->runner_agg_select_policy;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.runner_agg_select_policy;
 }
 
 /**
@@ -950,19 +985,22 @@ nm_setting_team_get_runner_agg_select_policy (NMSettingTeam *setting)
  **/
 gboolean
 nm_setting_team_remove_runner_tx_hash_by_value (NMSettingTeam *setting,
-                                               const char *txhash)
+                                                const char *txhash)
 {
 	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+	const GPtrArray *arr;
 	guint i;
 
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), FALSE);
 	g_return_val_if_fail (txhash != NULL, FALSE);
 
-	if (priv->runner_tx_hash) {
-		for (i = 0; i < priv->runner_tx_hash->len; i++) {
-			if (nm_streq (txhash, priv->runner_tx_hash->pdata[i])) {
-				g_ptr_array_remove_index (priv->runner_tx_hash, i);
-				_notify (setting, PROP_RUNNER_TX_HASH);
+	arr = priv->team_setting->d.master.runner_tx_hash;
+	if (arr) {
+		for (i = 0; i < arr->len; i++) {
+			if (nm_streq (txhash, arr->pdata[i])) {
+				_maybe_changed_with_assert (setting,
+				                            nm_team_setting_value_master_runner_tx_hash_remove (priv->team_setting,
+				                                                                                i));
 				return TRUE;
 			}
 		}
@@ -981,11 +1019,12 @@ nm_setting_team_remove_runner_tx_hash_by_value (NMSettingTeam *setting,
 guint
 nm_setting_team_get_num_runner_tx_hash (NMSettingTeam *setting)
 {
-	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+	const GPtrArray *arr;
 
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), 0);
 
-	return priv->runner_tx_hash ? priv->runner_tx_hash->len : 0;
+	arr = NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.runner_tx_hash;
+	return arr ? arr->len : 0u;
 }
 
 /**
@@ -1000,13 +1039,16 @@ nm_setting_team_get_num_runner_tx_hash (NMSettingTeam *setting)
 const char *
 nm_setting_team_get_runner_tx_hash (NMSettingTeam *setting, guint idx)
 {
-	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+	const GPtrArray *arr;
 
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), NULL);
-	g_return_val_if_fail (priv->runner_tx_hash, NULL);
-	g_return_val_if_fail (idx < priv->runner_tx_hash->len, NULL);
 
-	return priv->runner_tx_hash->pdata[idx];
+	arr = NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.master.runner_tx_hash;
+
+	g_return_val_if_fail (arr, NULL);
+	g_return_val_if_fail (idx < arr->len, NULL);
+
+	return arr->pdata[idx];
 }
 
 /**
@@ -1021,14 +1063,18 @@ nm_setting_team_get_runner_tx_hash (NMSettingTeam *setting, guint idx)
 void
 nm_setting_team_remove_runner_tx_hash (NMSettingTeam *setting, guint idx)
 {
-	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+	NMSettingTeamPrivate *priv;
 
 	g_return_if_fail (NM_IS_SETTING_TEAM (setting));
-	g_return_if_fail (priv->runner_tx_hash);
-	g_return_if_fail (idx < priv->runner_tx_hash->len);
 
-	g_ptr_array_remove_index (priv->runner_tx_hash, idx);
-	_notify (setting, PROP_RUNNER_TX_HASH);
+	priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+
+	g_return_if_fail (priv->team_setting->d.master.runner_tx_hash);
+	g_return_if_fail (idx < priv->team_setting->d.master.runner_tx_hash->len);
+
+	_maybe_changed_with_assert (setting,
+	                            nm_team_setting_value_master_runner_tx_hash_remove (priv->team_setting,
+	                                                                                idx));
 }
 
 /**
@@ -1046,23 +1092,12 @@ nm_setting_team_remove_runner_tx_hash (NMSettingTeam *setting, guint idx)
 gboolean
 nm_setting_team_add_runner_tx_hash (NMSettingTeam *setting, const char *txhash)
 {
-	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
-	guint i;
-
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), FALSE);
-	g_return_val_if_fail (txhash != NULL, FALSE);
-	g_return_val_if_fail (txhash[0] != '\0', FALSE);
-
-	if (!priv->runner_tx_hash)
-		priv->runner_tx_hash = g_ptr_array_new_with_free_func (g_free);
-	for (i = 0; i < priv->runner_tx_hash->len; i++) {
-		if (nm_streq (txhash, priv->runner_tx_hash->pdata[i]))
-			return FALSE;
-	}
+	g_return_val_if_fail (txhash, FALSE);
 
-	g_ptr_array_add (priv->runner_tx_hash, g_strdup (txhash));
-	_notify (setting, PROP_RUNNER_TX_HASH);
-	return TRUE;
+	return _maybe_changed (setting,
+	                       nm_team_setting_value_master_runner_tx_hash_add (NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting,
+	                                                                        txhash));
 }
 
 /**
@@ -1076,11 +1111,9 @@ nm_setting_team_add_runner_tx_hash (NMSettingTeam *setting, const char *txhash)
 guint
 nm_setting_team_get_num_link_watchers (NMSettingTeam *setting)
 {
-	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
-
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), 0);
 
-	return priv->link_watchers->len;
+	return NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.link_watchers->len;
 }
 
 /**
@@ -1095,12 +1128,15 @@ nm_setting_team_get_num_link_watchers (NMSettingTeam *setting)
 NMTeamLinkWatcher *
 nm_setting_team_get_link_watcher (NMSettingTeam *setting, guint idx)
 {
-	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+	const GPtrArray *arr;
 
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), NULL);
-	g_return_val_if_fail (idx < priv->link_watchers->len, NULL);
 
-	return priv->link_watchers->pdata[idx];
+	arr = NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting->d.link_watchers;
+
+	g_return_val_if_fail (idx < arr->len, NULL);
+
+	return arr->pdata[idx];
 }
 
 /**
@@ -1119,20 +1155,12 @@ gboolean
 nm_setting_team_add_link_watcher (NMSettingTeam *setting,
                                   NMTeamLinkWatcher *link_watcher)
 {
-	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
-	guint i;
-
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), FALSE);
 	g_return_val_if_fail (link_watcher != NULL, FALSE);
 
-	for (i = 0; i < priv->link_watchers->len; i++) {
-		if (nm_team_link_watcher_equal (priv->link_watchers->pdata[i], link_watcher))
-			return FALSE;
-	}
-
-	g_ptr_array_add (priv->link_watchers, nm_team_link_watcher_dup (link_watcher));
-	_notify (setting, PROP_LINK_WATCHERS);
-	return TRUE;
+	return _maybe_changed (setting,
+	                       nm_team_setting_value_link_watchers_add (NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting,
+	                                                                link_watcher));
 }
 
 /**
@@ -1147,13 +1175,17 @@ nm_setting_team_add_link_watcher (NMSettingTeam *setting,
 void
 nm_setting_team_remove_link_watcher (NMSettingTeam *setting, guint idx)
 {
-	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+	NMSettingTeamPrivate *priv;
 
 	g_return_if_fail (NM_IS_SETTING_TEAM (setting));
-	g_return_if_fail (idx < priv->link_watchers->len);
 
-	g_ptr_array_remove_index (priv->link_watchers, idx);
-	_notify (setting, PROP_LINK_WATCHERS);
+	priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+
+	g_return_if_fail (idx < priv->team_setting->d.link_watchers->len);
+
+	_maybe_changed_with_assert (setting,
+	                            nm_team_setting_value_link_watchers_remove (priv->team_setting,
+	                                                                        idx));
 }
 
 /**
@@ -1171,19 +1203,12 @@ gboolean
 nm_setting_team_remove_link_watcher_by_value (NMSettingTeam *setting,
                                               NMTeamLinkWatcher *link_watcher)
 {
-	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
-	guint i;
-
 	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), FALSE);
+	g_return_val_if_fail (link_watcher, FALSE);
 
-	for (i = 0; i < priv->link_watchers->len; i++) {
-		if (nm_team_link_watcher_equal (priv->link_watchers->pdata[i], link_watcher)) {
-			g_ptr_array_remove_index (priv->link_watchers, i);
-			_notify (setting, PROP_LINK_WATCHERS);
-			return TRUE;
-		}
-	}
-	return FALSE;
+	return _maybe_changed (setting,
+	                       nm_team_setting_value_link_watchers_remove_by_value (NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting,
+	                                                                            link_watcher));
 }
 
 /**
@@ -1195,118 +1220,26 @@ nm_setting_team_remove_link_watcher_by_value (NMSettingTeam *setting,
  * Since: 1.12
  **/
 void
-nm_setting_team_clear_link_watchers (NMSettingTeam *setting) {
-	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
-
-	g_return_if_fail (NM_IS_SETTING_TEAM (setting));
-
-	if (priv->link_watchers->len != 0) {
-		g_ptr_array_set_size (priv->link_watchers, 0);
-		_notify (setting, PROP_LINK_WATCHERS);
-	}
-}
-
-static GVariant *
-team_link_watchers_to_dbus (const GValue *prop_value)
+nm_setting_team_clear_link_watchers (NMSettingTeam *setting)
 {
-	return _nm_utils_team_link_watchers_to_variant (g_value_get_boxed (prop_value));
-}
+	g_return_if_fail (NM_IS_SETTING_TEAM (setting));
 
-static void
-team_link_watchers_from_dbus (GVariant   *dbus_value,
-                              GValue     *prop_value)
-{
-	g_value_take_boxed (prop_value, _nm_utils_team_link_watchers_from_variant (dbus_value));
+	_maybe_changed (setting,
+	                nm_team_setting_value_link_watchers_set_list (NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting,
+	                                                              NULL,
+	                                                              0));
 }
 
 static gboolean
 verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
 	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
-	guint i;
 
 	if (!_nm_connection_verify_required_interface_name (connection, error))
 		return FALSE;
 
-	if (priv->config) {
-		if (strlen (priv->config) > 1*1024*1024) {
-			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			             _("team config exceeds size limit"));
-			g_prefix_error (error,
-			                "%s.%s: ",
-			                NM_SETTING_TEAM_SETTING_NAME,
-			                NM_SETTING_TEAM_CONFIG);
-			return FALSE;
-		}
-
-		if (!nm_utils_is_json_object (priv->config, error)) {
-			g_prefix_error (error,
-			                "%s.%s: ",
-			                NM_SETTING_TEAM_SETTING_NAME,
-			                NM_SETTING_TEAM_CONFIG);
-			/* We treat an empty string as no config for compatibility. */
-			return *priv->config ? FALSE : NM_SETTING_VERIFY_NORMALIZABLE;
-		}
-	}
-
-	if (   priv->runner
-	    && g_ascii_strcasecmp (priv->runner, NM_SETTING_TEAM_RUNNER_BROADCAST)
-	    && g_ascii_strcasecmp (priv->runner, NM_SETTING_TEAM_RUNNER_ROUNDROBIN)
-	    && g_ascii_strcasecmp (priv->runner, NM_SETTING_TEAM_RUNNER_RANDOM)
-	    && g_ascii_strcasecmp (priv->runner, NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP)
-	    && g_ascii_strcasecmp (priv->runner, NM_SETTING_TEAM_RUNNER_LOADBALANCE)
-	    && g_ascii_strcasecmp (priv->runner, NM_SETTING_TEAM_RUNNER_LACP)) {
-		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
-		                     _("invalid runner \"%s\""), priv->runner);
-
-		g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), NM_SETTING_TEAM_RUNNER);
+	if (!nm_team_setting_verify (priv->team_setting, error))
 		return FALSE;
-	}
-
-	/* Validate link watchers */
-	for (i = 0; i < priv->link_watchers->len; i++) {
-		NMTeamLinkWatcher *link_watcher = priv->link_watchers->pdata[i];
-		const char *name = nm_team_link_watcher_get_name (link_watcher);
-
-		if (!name) {
-			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_SETTING,
-			             _("missing link watcher name"));
-			g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting),
-			                NM_SETTING_TEAM_LINK_WATCHERS);
-			return FALSE;
-		}
-		if (!NM_IN_STRSET (name,
-		                   NM_TEAM_LINK_WATCHER_ETHTOOL,
-		                   NM_TEAM_LINK_WATCHER_ARP_PING,
-		                   NM_TEAM_LINK_WATCHER_NSNA_PING)) {
-			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
-			             _("unknown link watcher \"%s\""), name);
-			g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting),
-			                NM_SETTING_TEAM_LINK_WATCHERS);
-			return FALSE;
-		}
-
-		if (NM_IN_STRSET (name,
-		                  NM_TEAM_LINK_WATCHER_ARP_PING,
-		                  NM_TEAM_LINK_WATCHER_NSNA_PING)
-		    && !nm_team_link_watcher_get_target_host (link_watcher)) {
-			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_SETTING,
-			             _("missing target host"));
-			g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting),
-			                NM_SETTING_TEAM_LINK_WATCHERS);
-			return FALSE;
-		}
-		if (nm_streq (name, NM_TEAM_LINK_WATCHER_ARP_PING)
-		    && !nm_team_link_watcher_get_source_host (link_watcher)) {
-			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_SETTING,
-			             _("missing source address"));
-			g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting),
-			                NM_SETTING_TEAM_LINK_WATCHERS);
-			return FALSE;
-		}
-	}
-	/* NOTE: normalizable/normalizable-errors must appear at the end with decreasing severity.
-	 * Take care to properly order statements with priv->config above. */
 
 	return TRUE;
 }
@@ -1314,8 +1247,10 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	NMSettingTeamPrivate *a_priv, *b_priv;
@@ -1323,31 +1258,30 @@ compare_property (const NMSettInfoSetting *sett_info,
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_TEAM_LINK_WATCHERS)) {
 		if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE))
 			return NM_TERNARY_DEFAULT;
-		if (!other)
+		if (!set_b)
 			return TRUE;
-		a_priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
-		b_priv = NM_SETTING_TEAM_GET_PRIVATE (other);
-		return _nm_team_link_watchers_equal (a_priv->link_watchers,
-		                                     b_priv->link_watchers,
-		                                     TRUE);
+		a_priv = NM_SETTING_TEAM_GET_PRIVATE (set_a);
+		b_priv = NM_SETTING_TEAM_GET_PRIVATE (set_b);
+		return nm_team_link_watchers_equal (a_priv->team_setting->d.link_watchers,
+		                                    b_priv->team_setting->d.link_watchers,
+		                                    TRUE);
 	}
 
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_TEAM_CONFIG)) {
-		if (other) {
-			a_priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
-			b_priv = NM_SETTING_TEAM_GET_PRIVATE (other);
-
+		if (set_b) {
 			if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) {
 				/* If we are trying to match a connection in order to assume it (and thus
 				 * @flags contains INFERRABLE), use the "relaxed" matching for team
 				 * configuration. Otherwise, for all other purposes (including connection
 				 * comparison before an update), resort to the default string comparison. */
-				return _nm_utils_team_config_equal (a_priv->config,
-				                                    b_priv->config,
-				                                    TRUE);
+				return TRUE;
 			}
 
-			return nm_streq0 (a_priv->config, b_priv->config);
+			a_priv = NM_SETTING_TEAM_GET_PRIVATE (set_a);
+			b_priv = NM_SETTING_TEAM_GET_PRIVATE (set_b);
+
+			return nm_streq0 (nm_team_setting_config_get (a_priv->team_setting),
+			                  nm_team_setting_config_get (b_priv->team_setting));
 		}
 
 		return TRUE;
@@ -1355,61 +1289,45 @@ compare_property (const NMSettInfoSetting *sett_info,
 
 	return NM_SETTING_CLASS (nm_setting_team_parent_class)->compare_property (sett_info,
 	                                                                          property_idx,
-	                                                                          setting,
-	                                                                          other,
+	                                                                          con_a,
+	                                                                          set_a,
+	                                                                          con_b,
+	                                                                          set_b,
 	                                                                          flags);
 }
 
-#define JSON_TO_VAL(typ, id)   _nm_utils_json_extract_##typ (priv->config, _prop_to_keys[id], FALSE)
-
 static void
-_align_team_properties (NMSettingTeam *setting)
+duplicate_copy_properties (const NMSettInfoSetting *sett_info,
+                           NMSetting *src,
+                           NMSetting *dst)
 {
-	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
-	char **strv;
-	gsize i;
-
-	priv->notify_peers_count =          JSON_TO_VAL (int, PROP_NOTIFY_PEERS_COUNT);
-	priv->notify_peers_interval =       JSON_TO_VAL (int, PROP_NOTIFY_PEERS_INTERVAL);
-	priv->mcast_rejoin_count =          JSON_TO_VAL (int, PROP_MCAST_REJOIN_COUNT);
-	priv->mcast_rejoin_interval =       JSON_TO_VAL (int, PROP_MCAST_REJOIN_INTERVAL);
-	priv->runner_tx_balancer_interval = JSON_TO_VAL (int, PROP_RUNNER_TX_BALANCER_INTERVAL);
-	priv->runner_sys_prio =             JSON_TO_VAL (int, PROP_RUNNER_SYS_PRIO);
-	priv->runner_min_ports =            JSON_TO_VAL (int, PROP_RUNNER_MIN_PORTS);
-
-	priv->runner_active =    JSON_TO_VAL (boolean, PROP_RUNNER_ACTIVE);
-	priv->runner_fast_rate = JSON_TO_VAL (boolean, PROP_RUNNER_FAST_RATE);
-
-	g_free (priv->runner);
-	g_free (priv->runner_hwaddr_policy);
-	g_free (priv->runner_tx_balancer);
-	g_free (priv->runner_agg_select_policy);
-	priv->runner =                   JSON_TO_VAL (string, PROP_RUNNER);
-	priv->runner_hwaddr_policy =     JSON_TO_VAL (string, PROP_RUNNER_HWADDR_POLICY);
-	priv->runner_tx_balancer =       JSON_TO_VAL (string, PROP_RUNNER_TX_BALANCER);
-	priv->runner_agg_select_policy = JSON_TO_VAL (string, PROP_RUNNER_AGG_SELECT_POLICY);
-
-	strv = JSON_TO_VAL (strv, PROP_RUNNER_TX_HASH);
-	if (_nm_utils_strv_cmp_n ((  priv->runner_tx_hash
-	                           ? (const char *const*) priv->runner_tx_hash->pdata
-	                           : NULL),
-	                          (  priv->runner_tx_hash
-	                           ? (gssize) priv->runner_tx_hash->len
-	                           : (gssize) -1),
-	                          NM_CAST_STRV_CC (strv),
-	                          -1) != 0) {
-		nm_clear_pointer (&priv->runner_tx_hash, g_ptr_array_unref);
-		if (strv) {
-			priv->runner_tx_hash = g_ptr_array_new_full (NM_PTRARRAY_LEN (strv), g_free);
-			for (i = 0; strv[i]; i++)
-				g_ptr_array_add (priv->runner_tx_hash, strv[i]);
-			nm_clear_g_free (&strv);
-		}
-	}
-	nm_clear_pointer (&strv, g_strfreev);
+	_maybe_changed (NM_SETTING_TEAM (dst),
+	                nm_team_setting_reset (NM_SETTING_TEAM_GET_PRIVATE (dst)->team_setting,
+	                                       NM_SETTING_TEAM_GET_PRIVATE (src)->team_setting));
+}
+
+static gboolean
+init_from_dbus (NMSetting *setting,
+                GHashTable *keys,
+                GVariant *setting_dict,
+                GVariant *connection_dict,
+                guint /* NMSettingParseFlags */ parse_flags,
+                GError **error)
+{
+	guint32 changed = 0;
+	gboolean success;
+
+	if (keys)
+		g_hash_table_remove (keys, "interface-name");
 
-	g_ptr_array_unref (priv->link_watchers);
-	priv->link_watchers = JSON_TO_VAL (ptr_array, PROP_LINK_WATCHERS);
+	success = nm_team_setting_reset_from_dbus (NM_SETTING_TEAM_GET_PRIVATE (setting)->team_setting,
+	                                           setting_dict,
+	                                           keys,
+	                                           &changed,
+	                                           parse_flags,
+	                                           error);
+	_maybe_changed (NM_SETTING_TEAM (setting), changed);
+	return success;
 }
 
 /*****************************************************************************/
@@ -1420,59 +1338,48 @@ get_property (GObject *object, guint prop_id,
 {
 	NMSettingTeam *setting = NM_SETTING_TEAM (object);
 	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+	const GPtrArray *v_ptrarr;
 
 	switch (prop_id) {
-	case PROP_CONFIG:
-		g_value_set_string (value, nm_setting_team_get_config (setting));
-		break;
-	case PROP_NOTIFY_PEERS_COUNT:
-		g_value_set_int (value, priv->notify_peers_count);
+	case NM_TEAM_ATTRIBUTE_CONFIG:
+		g_value_set_string (value,
+		                    nm_team_setting_config_get (priv->team_setting));
 		break;
-	case PROP_NOTIFY_PEERS_INTERVAL:
-		g_value_set_int (value, priv->notify_peers_interval);
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE:
+		g_value_set_boolean (value,
+		                     nm_team_setting_value_get_bool (priv->team_setting,
+		                                                     prop_id));
 		break;
-	case PROP_MCAST_REJOIN_COUNT:
-		g_value_set_int (value, priv->mcast_rejoin_count);
+	case NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT:
+	case NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL:
+	case NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT:
+	case NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS:
+		g_value_set_int (value,
+		                 nm_team_setting_value_get_int32 (priv->team_setting,
+		                                                  prop_id));
 		break;
-	case PROP_MCAST_REJOIN_INTERVAL:
-		g_value_set_int (value, priv->mcast_rejoin_interval);
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY:
+		g_value_set_string (value,
+		                    nm_team_setting_value_get_string (priv->team_setting,
+		                                                      prop_id));
 		break;
-	case PROP_RUNNER:
-		g_value_set_string (value, nm_setting_team_get_runner (setting));
-		break;
-	case PROP_RUNNER_HWADDR_POLICY:
-		g_value_set_string (value, nm_setting_team_get_runner_hwaddr_policy (setting));
-		break;
-	case PROP_RUNNER_TX_HASH:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH:
+		v_ptrarr = priv->team_setting->d.master.runner_tx_hash;
 		g_value_take_boxed (value,
-		                      priv->runner_tx_hash
-		                    ? _nm_utils_ptrarray_to_strv (priv->runner_tx_hash)
+		                      v_ptrarr
+		                    ? _nm_utils_ptrarray_to_strv ((GPtrArray *) v_ptrarr)
 		                    : NULL);
 		break;
-	case PROP_RUNNER_TX_BALANCER:
-		g_value_set_string (value, nm_setting_team_get_runner_tx_balancer (setting));
-		break;
-	case PROP_RUNNER_TX_BALANCER_INTERVAL:
-		g_value_set_int (value, priv->runner_tx_balancer_interval);
-		break;
-	case PROP_RUNNER_ACTIVE:
-		g_value_set_boolean (value, nm_setting_team_get_runner_active (setting));
-		break;
-	case PROP_RUNNER_FAST_RATE:
-		g_value_set_boolean (value, nm_setting_team_get_runner_fast_rate (setting));
-		break;
-	case PROP_RUNNER_SYS_PRIO:
-		g_value_set_int (value, priv->runner_sys_prio);
-		break;
-	case PROP_RUNNER_MIN_PORTS:
-		g_value_set_int (value, priv->runner_min_ports);
-		break;
-	case PROP_RUNNER_AGG_SELECT_POLICY:
-		g_value_set_string (value, nm_setting_team_get_runner_agg_select_policy (setting));
-		break;
-	case PROP_LINK_WATCHERS:
-		g_value_take_boxed (value, _nm_utils_copy_array (priv->link_watchers,
-		                                                 (NMUtilsCopyFunc) nm_team_link_watcher_dup,
+	case NM_TEAM_ATTRIBUTE_LINK_WATCHERS:
+		g_value_take_boxed (value, _nm_utils_copy_array (priv->team_setting->d.link_watchers,
+		                                                 (NMUtilsCopyFunc) _nm_team_link_watcher_ref,
 		                                                 (GDestroyNotify) nm_team_link_watcher_unref));
 		break;
 	default:
@@ -1487,141 +1394,56 @@ set_property (GObject *object, guint prop_id,
 {
 	NMSettingTeam *setting = NM_SETTING_TEAM (object);
 	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (object);
-	const GValue *align_value = NULL;
-	gboolean align_config = FALSE;
-	char **strv;
+	guint32 changed;
+	const GPtrArray *v_ptrarr;
 
 	switch (prop_id) {
-	case PROP_CONFIG:
-		g_free (priv->config);
-		priv->config = g_value_dup_string (value);
-		_align_team_properties (setting);
-		break;
-	case PROP_NOTIFY_PEERS_COUNT:
-		if (priv->notify_peers_count == g_value_get_int (value))
-			break;
-		priv->notify_peers_count = g_value_get_int (value);
-		align_value = value;
-		align_config = TRUE;
-		break;
-	case PROP_NOTIFY_PEERS_INTERVAL:
-		if (priv->notify_peers_interval == g_value_get_int (value))
-			break;
-		priv->notify_peers_interval = g_value_get_int (value);
-		align_value = value;
-		align_config = TRUE;
-		break;
-	case PROP_MCAST_REJOIN_COUNT:
-		if (priv->mcast_rejoin_count == g_value_get_int (value))
-			break;
-		priv->mcast_rejoin_count = g_value_get_int (value);
-		align_value = value;
-		align_config = TRUE;
-		break;
-	case PROP_MCAST_REJOIN_INTERVAL:
-		if (priv->mcast_rejoin_interval == g_value_get_int (value))
-			break;
-		priv->mcast_rejoin_interval = g_value_get_int (value);
-		align_value = value;
-		align_config = TRUE;
-		break;
-	case PROP_RUNNER:
-		if (   !g_value_get_string (value)
-		    || nm_streq (priv->runner, g_value_get_string (value)))
-			break;
-		g_free (priv->runner);
-		priv->runner = g_value_dup_string (value);
-		_nm_utils_json_append_gvalue (&priv->config, _prop_to_keys[prop_id], value);
-		_align_team_properties (setting);
-		break;
-	case PROP_RUNNER_HWADDR_POLICY:
-		if (nm_streq0 (priv->runner_hwaddr_policy, g_value_get_string (value)))
-			break;
-		g_free (priv->runner_hwaddr_policy);
-		priv->runner_hwaddr_policy = g_value_dup_string (value);
-		align_value = value;
-		align_config = TRUE;
+	case NM_TEAM_ATTRIBUTE_CONFIG:
+		changed = nm_team_setting_config_set (priv->team_setting, g_value_get_string (value));
 		break;
-	case PROP_RUNNER_TX_HASH:
-		if (priv->runner_tx_hash)
-			g_ptr_array_unref (priv->runner_tx_hash);
-		strv = g_value_get_boxed (value);
-		if (strv && strv[0]) {
-			priv->runner_tx_hash = _nm_utils_strv_to_ptrarray (strv);
-			align_value = value;
-		} else
-			priv->runner_tx_hash = NULL;
-		align_config = TRUE;
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE:
+		changed = nm_team_setting_value_set_bool (priv->team_setting,
+		                                          prop_id,
+		                                          g_value_get_boolean (value));
 		break;
-	case PROP_RUNNER_TX_BALANCER:
-		if (nm_streq0 (priv->runner_tx_balancer, g_value_get_string (value)))
-			break;
-		g_free (priv->runner_tx_balancer);
-		priv->runner_tx_balancer = g_value_dup_string (value);
-		align_value = value;
-		align_config = TRUE;
+	case NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT:
+	case NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL:
+	case NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT:
+	case NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS:
+		changed = nm_team_setting_value_set_int32 (priv->team_setting,
+		                                           prop_id,
+		                                           g_value_get_int (value));
 		break;
-	case PROP_RUNNER_TX_BALANCER_INTERVAL:
-		if (priv->runner_tx_balancer_interval == g_value_get_int (value))
-			break;
-		priv->runner_tx_balancer_interval = g_value_get_int (value);
-		align_value = value;
-		align_config = TRUE;
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY:
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY:
+		changed = nm_team_setting_value_set_string (priv->team_setting,
+		                                            prop_id,
+		                                            g_value_get_string (value));
 		break;
-	case PROP_RUNNER_ACTIVE:
-		if (priv->runner_active == g_value_get_boolean (value))
-			break;
-		priv->runner_active = g_value_get_boolean (value);
-		align_value = value;
-		align_config = TRUE;
+	case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH:
+		v_ptrarr = g_value_get_boxed (value);
+		changed = nm_team_setting_value_master_runner_tx_hash_set_list (priv->team_setting,
+		                                                                v_ptrarr ? (const char *const*) v_ptrarr->pdata : NULL,
+		                                                                v_ptrarr ? v_ptrarr->len                        : 0u);
 		break;
-	case PROP_RUNNER_FAST_RATE:
-		if (priv->runner_fast_rate == g_value_get_boolean (value))
-			break;
-		priv->runner_fast_rate = g_value_get_boolean (value);
-		align_value = value;
-		align_config = TRUE;
-		break;
-	case PROP_RUNNER_SYS_PRIO:
-		if (priv->runner_sys_prio == g_value_get_int (value))
-			break;
-		priv->runner_sys_prio = g_value_get_int (value);
-		align_value = value;
-		align_config = TRUE;
-		break;
-	case PROP_RUNNER_MIN_PORTS:
-		if (priv->runner_min_ports == g_value_get_int (value))
-			break;
-		priv->runner_min_ports = g_value_get_int (value);
-		align_value = value;
-		align_config = TRUE;
-		break;
-	case PROP_RUNNER_AGG_SELECT_POLICY:
-		if (nm_streq0 (priv->runner_agg_select_policy, g_value_get_string (value)))
-			break;
-		g_free (priv->runner_agg_select_policy);
-		priv->runner_agg_select_policy = g_value_dup_string (value);
-		align_value = value;
-		align_config = TRUE;
-		break;
-	case PROP_LINK_WATCHERS:
-		g_ptr_array_unref (priv->link_watchers);
-		priv->link_watchers = _nm_utils_copy_array (g_value_get_boxed (value),
-		                                            (NMUtilsCopyFunc) nm_team_link_watcher_dup,
-		                                            (GDestroyNotify) nm_team_link_watcher_unref);
-		if (priv->link_watchers->len)
-			align_value = value;
-		align_config = TRUE;
+	case NM_TEAM_ATTRIBUTE_LINK_WATCHERS:
+		v_ptrarr = g_value_get_boxed (value);
+		changed = nm_team_setting_value_link_watchers_set_list (priv->team_setting,
+		                                                        v_ptrarr ? (const NMTeamLinkWatcher *const*) v_ptrarr->pdata : NULL,
+		                                                        v_ptrarr ? v_ptrarr->len                                     : 0u);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
+		return;
 	}
 
-	if (align_config) {
-		_nm_utils_json_append_gvalue (&priv->config, _prop_to_keys[prop_id], align_value);
-		_align_team_properties (setting);
-	}
+	_maybe_changed (setting, changed & ~(((guint32) 1) << prop_id));
 }
 
 /*****************************************************************************/
@@ -1631,11 +1453,7 @@ nm_setting_team_init (NMSettingTeam *setting)
 {
 	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
 
-	priv->runner = g_strdup (NM_SETTING_TEAM_RUNNER_ROUNDROBIN);
-	priv->runner_tx_balancer_interval = -1;
-	priv->runner_sys_prio = -1;
-	priv->runner_min_ports = -1;
-	priv->link_watchers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref);
+	priv->team_setting = nm_team_setting_new (FALSE, NULL);
 }
 
 /**
@@ -1656,14 +1474,7 @@ finalize (GObject *object)
 {
 	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (object);
 
-	g_free (priv->config);
-	g_free (priv->runner);
-	g_free (priv->runner_hwaddr_policy);
-	g_free (priv->runner_tx_balancer);
-	g_free (priv->runner_agg_select_policy);
-	if (priv->runner_tx_hash)
-		g_ptr_array_unref (priv->runner_tx_hash);
-	g_ptr_array_unref (priv->link_watchers);
+	nm_team_setting_free (priv->team_setting);
 
 	G_OBJECT_CLASS (nm_setting_team_parent_class)->finalize (object);
 }
@@ -1681,8 +1492,17 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	object_class->set_property     = set_property;
 	object_class->finalize         = finalize;
 
-	setting_class->compare_property = compare_property;
-	setting_class->verify           = verify;
+	setting_class->compare_property          = compare_property;
+	setting_class->verify                    = verify;
+	setting_class->duplicate_copy_properties = duplicate_copy_properties;
+	setting_class->init_from_dbus            = init_from_dbus;
+
+#define _property_override(_properties_override, _param_spec, _variant_type, _is_link_watcher) \
+	_properties_override_add ((_properties_override), \
+	                          .param_spec          = (_param_spec), \
+	                          .dbus_type           = G_VARIANT_TYPE (""_variant_type""), \
+	                          .to_dbus_fcn         = _nm_team_settings_property_to_dbus, \
+	                          .gprop_from_dbus_fcn = ((_is_link_watcher) ? _nm_team_settings_property_from_dbus_link_watchers : NULL))
 
 	/**
 	 * NMSettingTeam:config:
@@ -1698,12 +1518,13 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 * description: Team configuration in JSON. See man teamd.conf for details.
 	 * ---end---
 	 */
-	obj_properties[PROP_CONFIG] =
+	obj_properties[NM_TEAM_ATTRIBUTE_CONFIG] =
 	    g_param_spec_string (NM_SETTING_TEAM_CONFIG, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
 	                         NM_SETTING_PARAM_INFERRABLE |
 	                         G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_CONFIG], "s", FALSE);
 
 	/**
 	 * NMSettingTeam:notify-peers-count:
@@ -1712,11 +1533,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_NOTIFY_PEERS_COUNT] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT] =
 	    g_param_spec_int (NM_SETTING_TEAM_NOTIFY_PEERS_COUNT, "", "",
-	                      G_MININT32, G_MAXINT32, 0,
+	                      G_MININT32, G_MAXINT32, -1,
 	                      G_PARAM_READWRITE |
 	                      G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT], "i", FALSE);
 
 	/**
 	 * NMSettingTeam:notify-peers-interval:
@@ -1725,11 +1547,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_NOTIFY_PEERS_INTERVAL] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL] =
 	    g_param_spec_int (NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL, "", "",
-	                      G_MININT32, G_MAXINT32, 0,
+	                      G_MININT32, G_MAXINT32, -1,
 	                      G_PARAM_READWRITE |
 	                      G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL], "i", FALSE);
 
 	/**
 	 * NMSettingTeam:mcast-rejoin-count:
@@ -1738,11 +1561,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_MCAST_REJOIN_COUNT] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT] =
 	    g_param_spec_int (NM_SETTING_TEAM_MCAST_REJOIN_COUNT, "", "",
-	                      G_MININT32, G_MAXINT32, 0,
+	                      G_MININT32, G_MAXINT32, -1,
 	                      G_PARAM_READWRITE |
 	                      G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT], "i", FALSE);
 
 	/**
 	 * NMSettingTeam:mcast-rejoin-interval:
@@ -1751,11 +1575,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_MCAST_REJOIN_INTERVAL] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL] =
 	    g_param_spec_int (NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL, "", "",
-	                      G_MININT32, G_MAXINT32, 0,
+	                      G_MININT32, G_MAXINT32, -1,
 	                      G_PARAM_READWRITE |
 	                      G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL], "i", FALSE);
 
 	/**
 	 * NMSettingTeam:runner:
@@ -1763,18 +1588,15 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 * Corresponds to the teamd runner.name.
 	 * Permitted values are: "roundrobin", "broadcast", "activebackup",
 	 * "loadbalance", "lacp", "random".
-	 * When setting the runner, all the properties specific to the runner
-	 * will be reset to the default value; all the properties specific to
-	 * other runners will be set to an empty value (or if not possible to
-	 * a default value).
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_RUNNER] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER] =
 	    g_param_spec_string (NM_SETTING_TEAM_RUNNER, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
 	                         G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER], "s", FALSE);
 
 	/**
 	 * NMSettingTeam:runner-hwaddr-policy:
@@ -1783,11 +1605,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_RUNNER_HWADDR_POLICY] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY] =
 	    g_param_spec_string (NM_SETTING_TEAM_RUNNER_HWADDR_POLICY, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
 	                         G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY], "s", FALSE);
 
 	/**
 	 * NMSettingTeam:runner-tx-hash:
@@ -1796,12 +1619,13 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_RUNNER_TX_HASH] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH] =
 	    g_param_spec_boxed (NM_SETTING_TEAM_RUNNER_TX_HASH, "", "",
 	                        G_TYPE_STRV,
 	                        G_PARAM_READWRITE |
 	                        NM_SETTING_PARAM_INFERRABLE |
 	                        G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH], "as", FALSE);
 
 	/**
 	 * NMSettingTeam:runner-tx-balancer:
@@ -1810,11 +1634,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_RUNNER_TX_BALANCER] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER] =
 	    g_param_spec_string (NM_SETTING_TEAM_RUNNER_TX_BALANCER, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
 	                         G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER], "s", FALSE);
 
 	/**
 	 * NMSettingTeam:runner-tx-balancer-interval:
@@ -1823,11 +1648,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_RUNNER_TX_BALANCER_INTERVAL] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL] =
 	    g_param_spec_int (NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL, "", "",
-	                      G_MININT32, G_MAXINT32, 0,
+	                      G_MININT32, G_MAXINT32, -1,
 	                      G_PARAM_READWRITE |
 	                      G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL], "i", FALSE);
 
 	/**
 	 * NMSettingTeam:runner-active:
@@ -1836,11 +1662,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_RUNNER_ACTIVE] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE] =
 	    g_param_spec_boolean (NM_SETTING_TEAM_RUNNER_ACTIVE, "", "",
-	                          FALSE,
+	                          TRUE,
 	                          G_PARAM_READWRITE |
 	                          G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE], "b", FALSE);
 
 	/**
 	 * NMSettingTeam:runner-fast-rate:
@@ -1849,11 +1676,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_RUNNER_FAST_RATE] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE] =
 	    g_param_spec_boolean (NM_SETTING_TEAM_RUNNER_FAST_RATE, "", "",
 	                          FALSE,
 	                          G_PARAM_READWRITE |
 	                          G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE], "b", FALSE);
 
 	/**
 	 * NMSettingTeam:runner-sys-prio:
@@ -1862,11 +1690,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_RUNNER_SYS_PRIO] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO] =
 	    g_param_spec_int (NM_SETTING_TEAM_RUNNER_SYS_PRIO, "", "",
-	                      G_MININT32, G_MAXINT32, 0,
+	                      G_MININT32, G_MAXINT32, -1,
 	                      G_PARAM_READWRITE |
 	                      G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO], "i", FALSE);
 
 	/**
 	 * NMSettingTeam:runner-min-ports:
@@ -1875,11 +1704,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_RUNNER_MIN_PORTS] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS] =
 	    g_param_spec_int (NM_SETTING_TEAM_RUNNER_MIN_PORTS, "", "",
-	                      G_MININT32, G_MAXINT32, 0,
+	                      G_MININT32, G_MAXINT32, -1,
 	                      G_PARAM_READWRITE |
 	                      G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS], "i", FALSE);
 
 	/**
 	 * NMSettingTeam:runner-agg-select-policy:
@@ -1888,11 +1718,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_RUNNER_AGG_SELECT_POLICY] =
+	obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY] =
 	    g_param_spec_string (NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY, "", "",
 	                         NULL,
 	                         G_PARAM_READWRITE |
 	                         G_PARAM_STATIC_STRINGS);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY], "s", FALSE);
 
 	/**
 	 * NMSettingTeam:link-watchers: (type GPtrArray(NMTeamLinkWatcher))
@@ -1908,17 +1739,12 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	 *
 	 * Since: 1.12
 	 **/
-	obj_properties[PROP_LINK_WATCHERS] =
+	obj_properties[NM_TEAM_ATTRIBUTE_LINK_WATCHERS] =
 	    g_param_spec_boxed (NM_SETTING_TEAM_LINK_WATCHERS, "", "",
 	                        G_TYPE_PTR_ARRAY,
 	                        G_PARAM_READWRITE |
 	                        G_PARAM_STATIC_STRINGS);
-
-	_properties_override_add_transform (properties_override,
-	                                    obj_properties[PROP_LINK_WATCHERS],
-	                                    G_VARIANT_TYPE ("aa{sv}"),
-	                                    team_link_watchers_to_dbus,
-	                                    team_link_watchers_from_dbus);
+	_property_override (properties_override, obj_properties[NM_TEAM_ATTRIBUTE_LINK_WATCHERS], "aa{sv}", TRUE);
 
 	/* ---dbus---
 	 * property: interface-name
@@ -1934,7 +1760,7 @@ nm_setting_team_class_init (NMSettingTeamClass *klass)
 	                                    _nm_setting_get_deprecated_virtual_interface_name,
 	                                    NULL);
 
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+	g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_properties), obj_properties);
 
 	_nm_setting_class_commit_full (setting_class, NM_META_SETTING_TYPE_TEAM,
 	                               NULL, properties_override);
diff --git a/libnm-core/nm-setting-team.h b/libnm-core/nm-setting-team.h
index a6ef387f..75943b20 100644
--- a/libnm-core/nm-setting-team.h
+++ b/libnm-core/nm-setting-team.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -88,29 +87,30 @@ void nm_team_link_watcher_ref                    (NMTeamLinkWatcher *watcher);
 NM_AVAILABLE_IN_1_12
 void nm_team_link_watcher_unref                  (NMTeamLinkWatcher *watcher);
 NM_AVAILABLE_IN_1_12
-gboolean nm_team_link_watcher_equal              (NMTeamLinkWatcher *watcher, NMTeamLinkWatcher *other);
+gboolean nm_team_link_watcher_equal              (const NMTeamLinkWatcher *watcher,
+                                                  const NMTeamLinkWatcher *other);
 NM_AVAILABLE_IN_1_12
-NMTeamLinkWatcher *nm_team_link_watcher_dup      (NMTeamLinkWatcher *watcher);
+NMTeamLinkWatcher *nm_team_link_watcher_dup      (const NMTeamLinkWatcher *watcher);
 NM_AVAILABLE_IN_1_12
-const char *nm_team_link_watcher_get_name        (NMTeamLinkWatcher *watcher);
+const char *nm_team_link_watcher_get_name        (const NMTeamLinkWatcher *watcher);
 NM_AVAILABLE_IN_1_12
-int nm_team_link_watcher_get_delay_up            (NMTeamLinkWatcher *watcher);
+int nm_team_link_watcher_get_delay_up            (const NMTeamLinkWatcher *watcher);
 NM_AVAILABLE_IN_1_12
-int nm_team_link_watcher_get_delay_down          (NMTeamLinkWatcher *watcher);
+int nm_team_link_watcher_get_delay_down          (const NMTeamLinkWatcher *watcher);
 NM_AVAILABLE_IN_1_12
-int nm_team_link_watcher_get_init_wait           (NMTeamLinkWatcher *watcher);
+int nm_team_link_watcher_get_init_wait           (const NMTeamLinkWatcher *watcher);
 NM_AVAILABLE_IN_1_12
-int nm_team_link_watcher_get_interval            (NMTeamLinkWatcher *watcher);
+int nm_team_link_watcher_get_interval            (const NMTeamLinkWatcher *watcher);
 NM_AVAILABLE_IN_1_12
-int nm_team_link_watcher_get_missed_max          (NMTeamLinkWatcher *watcher);
+int nm_team_link_watcher_get_missed_max          (const NMTeamLinkWatcher *watcher);
 NM_AVAILABLE_IN_1_12
-const char *nm_team_link_watcher_get_target_host (NMTeamLinkWatcher *watcher);
+const char *nm_team_link_watcher_get_target_host (const NMTeamLinkWatcher *watcher);
 NM_AVAILABLE_IN_1_12
-const char *nm_team_link_watcher_get_source_host (NMTeamLinkWatcher *watcher);
+const char *nm_team_link_watcher_get_source_host (const NMTeamLinkWatcher *watcher);
 NM_AVAILABLE_IN_1_12
-NMTeamLinkWatcherArpPingFlags nm_team_link_watcher_get_flags (NMTeamLinkWatcher *watcher);
+NMTeamLinkWatcherArpPingFlags nm_team_link_watcher_get_flags (const NMTeamLinkWatcher *watcher);
 NM_AVAILABLE_IN_1_16
-int nm_team_link_watcher_get_vlanid              (NMTeamLinkWatcher *watcher);
+int nm_team_link_watcher_get_vlanid              (const NMTeamLinkWatcher *watcher);
 
 #define NM_TYPE_SETTING_TEAM            (nm_setting_team_get_type ())
 #define NM_SETTING_TEAM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTING_TEAM, NMSettingTeam))
diff --git a/libnm-core/nm-setting-tun.c b/libnm-core/nm-setting-tun.c
index 9801f5cf..649c32a9 100644
--- a/libnm-core/nm-setting-tun.c
+++ b/libnm-core/nm-setting-tun.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-tun.h b/libnm-core/nm-setting-tun.h
index d977e381..0be19754 100644
--- a/libnm-core/nm-setting-tun.h
+++ b/libnm-core/nm-setting-tun.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-user.c b/libnm-core/nm-setting-user.c
index 01ac671b..6cdf8ca2 100644
--- a/libnm-core/nm-setting-user.c
+++ b/libnm-core/nm-setting-user.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -398,8 +396,10 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	NMSettingUserPrivate *priv, *pri2;
@@ -409,19 +409,21 @@ compare_property (const NMSettInfoSetting *sett_info,
 		if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE))
 			return NM_TERNARY_DEFAULT;
 
-		if (!other)
+		if (!set_b)
 			return TRUE;
 
-		priv = NM_SETTING_USER_GET_PRIVATE (NM_SETTING_USER (setting));
-		pri2 = NM_SETTING_USER_GET_PRIVATE (NM_SETTING_USER (other));
+		priv = NM_SETTING_USER_GET_PRIVATE (NM_SETTING_USER (set_a));
+		pri2 = NM_SETTING_USER_GET_PRIVATE (NM_SETTING_USER (set_b));
 		return    nm_utils_hash_table_equal (priv->data, pri2->data, TRUE, g_str_equal)
 		       && nm_utils_hash_table_equal (priv->data_invalid, pri2->data_invalid, TRUE, g_str_equal);
 	}
 
 	return NM_SETTING_CLASS (nm_setting_user_parent_class)->compare_property (sett_info,
 	                                                                          property_idx,
-	                                                                          setting,
-	                                                                          other,
+	                                                                          con_a,
+	                                                                          set_a,
+	                                                                          con_b,
+	                                                                          set_b,
 	                                                                          flags);
 }
 
diff --git a/libnm-core/nm-setting-user.h b/libnm-core/nm-setting-user.h
index 5a2e2cfd..588b841d 100644
--- a/libnm-core/nm-setting-user.h
+++ b/libnm-core/nm-setting-user.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-vlan.c b/libnm-core/nm-setting-vlan.c
index 8a220ad8..a7debbf1 100644
--- a/libnm-core/nm-setting-vlan.c
+++ b/libnm-core/nm-setting-vlan.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -679,7 +677,12 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 }
 
 static GVariant *
-_override_flags_get (NMSetting *setting, const char *property)
+_override_flags_get (const NMSettInfoSetting *sett_info,
+                     guint property_idx,
+                     NMConnection *connection,
+                     NMSetting *setting,
+                     NMConnectionSerializationFlags flags,
+                     const NMConnectionSerializationOptions *options)
 {
 	return g_variant_new_uint32 (nm_setting_vlan_get_flags ((NMSettingVlan *) setting));
 }
@@ -921,7 +924,7 @@ nm_setting_vlan_class_init (NMSettingVlanClass *klass)
 
 	_properties_override_add_override (properties_override,
 	                                   obj_properties[PROP_FLAGS],
-	                                   NULL,
+	                                   G_VARIANT_TYPE_UINT32,
 	                                   _override_flags_get,
 	                                   NULL,
 	                                   _override_flags_not_set);
diff --git a/libnm-core/nm-setting-vlan.h b/libnm-core/nm-setting-vlan.h
index 63492ebf..8d5ece60 100644
--- a/libnm-core/nm-setting-vlan.h
+++ b/libnm-core/nm-setting-vlan.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-vpn.c b/libnm-core/nm-setting-vpn.c
index 606b9d7f..56098392 100644
--- a/libnm-core/nm-setting-vpn.c
+++ b/libnm-core/nm-setting-vpn.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -839,20 +838,24 @@ compare_property_secrets (NMSettingVpn *a,
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_VPN_SECRETS)) {
 		if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE))
 			return NM_TERNARY_DEFAULT;
-		return compare_property_secrets (NM_SETTING_VPN (setting), NM_SETTING_VPN (other), flags);
+		return compare_property_secrets (NM_SETTING_VPN (set_a), NM_SETTING_VPN (set_b), flags);
 	}
 
 	return NM_SETTING_CLASS (nm_setting_vpn_parent_class)->compare_property (sett_info,
 	                                                                         property_idx,
-	                                                                         setting,
-	                                                                         other,
+	                                                                         con_a,
+	                                                                         set_a,
+	                                                                         con_b,
+	                                                                         set_b,
 	                                                                         flags);
 }
 
diff --git a/libnm-core/nm-setting-vpn.h b/libnm-core/nm-setting-vpn.h
index be14e7c0..fa46af93 100644
--- a/libnm-core/nm-setting-vpn.h
+++ b/libnm-core/nm-setting-vpn.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-vxlan.c b/libnm-core/nm-setting-vxlan.c
index 0c116f06..4f788eb5 100644
--- a/libnm-core/nm-setting-vxlan.c
+++ b/libnm-core/nm-setting-vxlan.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-vxlan.h b/libnm-core/nm-setting-vxlan.h
index ce266941..081e6166 100644
--- a/libnm-core/nm-setting-vxlan.h
+++ b/libnm-core/nm-setting-vxlan.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-wimax.c b/libnm-core/nm-setting-wimax.c
index a700f7fe..ecb5a77a 100644
--- a/libnm-core/nm-setting-wimax.c
+++ b/libnm-core/nm-setting-wimax.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-wimax.h b/libnm-core/nm-setting-wimax.h
index fb0abbac..22450b8d 100644
--- a/libnm-core/nm-setting-wimax.h
+++ b/libnm-core/nm-setting-wimax.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-wired.c b/libnm-core/nm-setting-wired.c
index 182e7193..379bd173 100644
--- a/libnm-core/nm-setting-wired.c
+++ b/libnm-core/nm-setting-wired.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -75,7 +73,11 @@ typedef struct {
 	guint32 mtu;
 	char **s390_subchannels;
 	char *s390_nettype;
-	GHashTable *s390_options;
+	struct {
+		NMUtilsNamedValue *arr;
+		guint len;
+		guint n_alloc;
+	} s390_options;
 	NMSettingWiredWakeOnLan wol;
 	char *wol_password;
 } NMSettingWiredPrivate;
@@ -87,15 +89,68 @@ G_DEFINE_TYPE (NMSettingWired, nm_setting_wired, NM_TYPE_SETTING)
 /*****************************************************************************/
 
 static const char *valid_s390_opts[] = {
-	"portno", "layer2", "portname", "protocol", "priority_queueing",
-	"buffer_count", "isolation", "total", "inter", "inter_jumbo", "route4",
-	"route6", "fake_broadcast", "broadcast_mode", "canonical_macaddr",
-	"checksumming", "sniffer", "large_send", "ipato_enable", "ipato_invert4",
-	"ipato_add4", "ipato_invert6", "ipato_add6", "vipa_add4", "vipa_add6",
-	"rxip_add4", "rxip_add6", "lancmd_timeout", "ctcprot",
-	NULL
+	"broadcast_mode",
+	"buffer_count",
+	"canonical_macaddr",
+	"checksumming",
+	"ctcprot",
+	"fake_broadcast",
+	"inter",
+	"inter_jumbo",
+	"ipato_add4",
+	"ipato_add6",
+	"ipato_enable",
+	"ipato_invert4",
+	"ipato_invert6",
+	"isolation",
+	"lancmd_timeout",
+	"large_send",
+	"layer2",
+	"portname",
+	"portno",
+	"priority_queueing",
+	"protocol",
+	"route4",
+	"route6",
+	"rxip_add4",
+	"rxip_add6",
+	"sniffer",
+	"total",
+	"vipa_add4",
+	"vipa_add6",
+	NULL,
 };
 
+static gboolean
+valid_s390_opts_check (const char *option)
+{
+#if NM_MORE_ASSERTS > 5
+	nm_assert (NM_PTRARRAY_LEN (valid_s390_opts) + 1 == G_N_ELEMENTS (valid_s390_opts));
+	{
+		gsize i;
+
+		for (i = 0; i < G_N_ELEMENTS (valid_s390_opts); i++) {
+			if (i == G_N_ELEMENTS (valid_s390_opts) - 1)
+				nm_assert (!valid_s390_opts[i]);
+			else {
+				nm_assert (valid_s390_opts[i]);
+				nm_assert (valid_s390_opts[i][0] != '\0');
+				if (i > 0)
+					g_assert (strcmp (valid_s390_opts[i - 1], valid_s390_opts[i]) < 0);
+			}
+		}
+	}
+#endif
+
+	return    option
+	       && (nm_utils_array_find_binary_search (valid_s390_opts,
+	                                              sizeof (const char *),
+	                                              G_N_ELEMENTS (valid_s390_opts) - 1,
+	                                              &option,
+	                                              nm_strcmp_p_with_data,
+	                                              NULL) >= 0);
+}
+
 /**
  * nm_setting_wired_get_port:
  * @setting: the #NMSettingWired
@@ -417,7 +472,7 @@ nm_setting_wired_get_num_s390_options (NMSettingWired *setting)
 {
 	g_return_val_if_fail (NM_IS_SETTING_WIRED (setting), 0);
 
-	return g_hash_table_size (NM_SETTING_WIRED_GET_PRIVATE (setting)->s390_options);
+	return NM_SETTING_WIRED_GET_PRIVATE (setting)->s390_options.len;
 }
 
 /**
@@ -447,24 +502,17 @@ nm_setting_wired_get_s390_option (NMSettingWired *setting,
                                   const char **out_key,
                                   const char **out_value)
 {
-	const char *_key, *_value;
-	GHashTableIter iter;
-	guint i = 0;
+	NMSettingWiredPrivate *priv;
 
 	g_return_val_if_fail (NM_IS_SETTING_WIRED (setting), FALSE);
 
-	g_hash_table_iter_init (&iter, NM_SETTING_WIRED_GET_PRIVATE (setting)->s390_options);
-	while (g_hash_table_iter_next (&iter, (gpointer) &_key, (gpointer) &_value)) {
-		if (i == idx) {
-			if (out_key)
-				*out_key = _key;
-			if (out_value)
-				*out_value = _value;
-			return TRUE;
-		}
-		i++;
-	}
-	g_return_val_if_reached (FALSE);
+	priv = NM_SETTING_WIRED_GET_PRIVATE (setting);
+
+	g_return_val_if_fail (idx < priv->s390_options.len, FALSE);
+
+	NM_SET_OUT (out_key,   priv->s390_options.arr[idx].name);
+	NM_SET_OUT (out_value, priv->s390_options.arr[idx].value_str);
+	return TRUE;
 }
 
 /**
@@ -482,10 +530,21 @@ const char *
 nm_setting_wired_get_s390_option_by_key (NMSettingWired *setting,
                                          const char *key)
 {
+	NMSettingWiredPrivate *priv;
+	gssize idx;
+
 	g_return_val_if_fail (NM_IS_SETTING_WIRED (setting), NULL);
 	g_return_val_if_fail (key && key[0], NULL);
 
-	return g_hash_table_lookup (NM_SETTING_WIRED_GET_PRIVATE (setting)->s390_options, key);
+	priv = NM_SETTING_WIRED_GET_PRIVATE (setting);
+
+	idx = nm_utils_named_value_list_find (priv->s390_options.arr,
+	                                      priv->s390_options.len,
+	                                      key,
+	                                      TRUE);
+	if (idx < 0)
+		return NULL;
+	return priv->s390_options.arr[idx].value_str;
 }
 
 /**
@@ -507,14 +566,51 @@ nm_setting_wired_add_s390_option (NMSettingWired *setting,
                                   const char *key,
                                   const char *value)
 {
+	NMSettingWiredPrivate *priv;
+	gssize idx;
+	NMUtilsNamedValue *v;
+
 	g_return_val_if_fail (NM_IS_SETTING_WIRED (setting), FALSE);
-	g_return_val_if_fail (key && key[0], FALSE);
-	g_return_val_if_fail (g_strv_contains (valid_s390_opts, key), FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
+	g_return_val_if_fail (value, FALSE);
+
+	if (!valid_s390_opts_check (key)) {
+		g_return_val_if_fail (key, FALSE);
+		return FALSE;
+	}
+
+	priv = NM_SETTING_WIRED_GET_PRIVATE (setting);
+
+	idx = nm_utils_named_value_list_find (priv->s390_options.arr,
+	                                      priv->s390_options.len,
+	                                      key,
+	                                      TRUE);
+	if (idx < 0) {
+		gsize dst_idx = ~idx;
+
+		if (priv->s390_options.n_alloc < priv->s390_options.len + 1) {
+			priv->s390_options.n_alloc = NM_MAX (4,
+			                                     (priv->s390_options.len + 1) * 2);
+			priv->s390_options.arr = g_realloc (priv->s390_options.arr,
+			                                    priv->s390_options.n_alloc * sizeof (NMUtilsNamedValue));
+		}
+		if (dst_idx < priv->s390_options.len) {
+			memmove (&priv->s390_options.arr[dst_idx + 1],
+			         &priv->s390_options.arr[dst_idx],
+			         (priv->s390_options.len - dst_idx) * sizeof (NMUtilsNamedValue));
+		}
+		priv->s390_options.arr[dst_idx] = (NMUtilsNamedValue) {
+			.name      = g_strdup (key),
+			.value_str = g_strdup (value),
+		};
+		priv->s390_options.len++;
+	} else {
+		v = &priv->s390_options.arr[idx];
+		if (nm_streq (value, v->value_str))
+			return TRUE;
+		g_free ((char *) v->value_str);
+		v->value_str = g_strdup (value);
+	}
 
-	g_hash_table_insert (NM_SETTING_WIRED_GET_PRIVATE (setting)->s390_options,
-	                     g_strdup (key),
-	                     g_strdup (value));
 	_notify (setting, PROP_S390_OPTIONS);
 	return TRUE;
 }
@@ -534,20 +630,64 @@ gboolean
 nm_setting_wired_remove_s390_option (NMSettingWired *setting,
                                      const char *key)
 {
-	gboolean found;
+	NMSettingWiredPrivate *priv;
+	gsize dst_idx;
+	gssize idx;
 
 	g_return_val_if_fail (NM_IS_SETTING_WIRED (setting), FALSE);
-	g_return_val_if_fail (key && key[0], FALSE);
+	g_return_val_if_fail (key, FALSE);
+
+	priv = NM_SETTING_WIRED_GET_PRIVATE (setting);
+
+	idx = nm_utils_named_value_list_find (priv->s390_options.arr,
+	                                      priv->s390_options.len,
+	                                      key,
+	                                      TRUE);
+	if (idx < 0)
+		return FALSE;
+
+	dst_idx = idx;
+
+	g_free ((char *) priv->s390_options.arr[dst_idx].name);
+	g_free ((char *) priv->s390_options.arr[dst_idx].value_str);
+	if (dst_idx + 1 != priv->s390_options.len) {
+		memmove (&priv->s390_options.arr[dst_idx],
+		         &priv->s390_options.arr[dst_idx + 1],
+		         (priv->s390_options.len - dst_idx - 1) * sizeof (NMUtilsNamedValue));
+	}
+
+	priv->s390_options.len--;
 
-	found = g_hash_table_remove (NM_SETTING_WIRED_GET_PRIVATE (setting)->s390_options, key);
-	if (found)
-		_notify (setting, PROP_S390_OPTIONS);
-	return found;
+	_notify (setting, PROP_S390_OPTIONS);
+	return TRUE;
+}
+
+static void
+_s390_options_clear (NMSettingWiredPrivate *priv)
+{
+	guint i;
+
+	for (i = 0; i < priv->s390_options.len; i++) {
+		g_free ((char *) priv->s390_options.arr[i].name);
+		g_free ((char *) priv->s390_options.arr[i].value_str);
+	}
+	nm_clear_g_free (&priv->s390_options.arr);
+	priv->s390_options.len = 0;
+	priv->s390_options.n_alloc = 0;
+}
+
+void
+_nm_setting_wired_clear_s390_options (NMSettingWired *setting)
+{
+	g_return_if_fail (NM_IS_SETTING_WIRED (setting));
+
+	_s390_options_clear (NM_SETTING_WIRED_GET_PRIVATE (setting));
 }
 
 /**
  * nm_setting_wired_get_valid_s390_options:
- * @setting: (allow-none): the #NMSettingWired
+ * @setting: (allow-none): the #NMSettingWired. This argument is unused
+ *   and you may pass %NULL.
  *
  * Returns a list of valid s390 options.
  *
@@ -602,10 +742,8 @@ static gboolean
 verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
 	NMSettingWiredPrivate *priv = NM_SETTING_WIRED_GET_PRIVATE (setting);
-	GHashTableIter iter;
-	const char *key, *value;
-	int i;
 	GError *local = NULL;
+	guint i;
 
 	if (!NM_IN_STRSET (priv->port, NULL, "tp", "aui", "bnc", "mii")) {
 		g_set_error (error,
@@ -628,10 +766,11 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	}
 
 	if (priv->device_mac_address && !nm_utils_hwaddr_valid (priv->device_mac_address, ETH_ALEN)) {
-		g_set_error_literal (error,
-		                     NM_CONNECTION_ERROR,
-		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-		                     _("is not a valid MAC address"));
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             _("'%s' is not a valid MAC address"),
+		             priv->device_mac_address);
 		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_MAC_ADDRESS);
 		return FALSE;
 	}
@@ -672,16 +811,19 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	g_hash_table_iter_init (&iter, priv->s390_options);
-	while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value)) {
-		if (   !g_strv_contains (valid_s390_opts, key)
-		    || value[0] == '\0'
-		    || (strlen (value) > 200)) {
+	for (i = 0; i < priv->s390_options.len; i++) {
+		const NMUtilsNamedValue *v = &priv->s390_options.arr[i];
+
+		nm_assert (v->name);
+
+		if (   !valid_s390_opts_check (v->name)
+		    || v->value_str[0] == '\0'
+		    || strlen (v->value_str) > 200) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
 			             _("invalid '%s' or its value '%s'"),
-			             key, value);
+			             v->name, v->value_str);
 			g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_S390_OPTIONS);
 			return FALSE;
 		}
@@ -690,10 +832,11 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	if (   priv->cloned_mac_address
 	    && !NM_CLONED_MAC_IS_SPECIAL (priv->cloned_mac_address)
 	    && !nm_utils_hwaddr_valid (priv->cloned_mac_address, ETH_ALEN)) {
-		g_set_error_literal (error,
-		                     NM_CONNECTION_ERROR,
-		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-		                     _("is not a valid MAC address"));
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             _("'%s' is not a valid MAC address"),
+		             priv->cloned_mac_address);
 		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_CLONED_MAC_ADDRESS);
 		return FALSE;
 	}
@@ -732,10 +875,11 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	}
 
 	if (priv->wol_password && !nm_utils_hwaddr_valid (priv->wol_password, ETH_ALEN)) {
-		g_set_error_literal (error,
-		                     NM_CONNECTION_ERROR,
-		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-		                     _("is not a valid MAC address"));
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             _("'%s' is not a valid MAC address"),
+		             priv->wol_password);
 		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD);
 		return FALSE;
 	}
@@ -762,26 +906,35 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_WIRED_CLONED_MAC_ADDRESS)) {
-		return    !other
-		       || nm_streq0 (NM_SETTING_WIRED_GET_PRIVATE (setting)->cloned_mac_address,
-		                     NM_SETTING_WIRED_GET_PRIVATE (other)->cloned_mac_address);
+		return    !set_b
+		       || nm_streq0 (NM_SETTING_WIRED_GET_PRIVATE (set_a)->cloned_mac_address,
+		                     NM_SETTING_WIRED_GET_PRIVATE (set_b)->cloned_mac_address);
 	}
 
 	return NM_SETTING_CLASS (nm_setting_wired_parent_class)->compare_property (sett_info,
 	                                                                           property_idx,
-	                                                                           setting,
-	                                                                           other,
+	                                                                           con_a,
+	                                                                           set_a,
+	                                                                           con_b,
+	                                                                           set_b,
 	                                                                           flags);
 }
 
 static GVariant *
-_override_autoneg_get (NMSetting *setting, const char *property)
+_override_autoneg_get (const NMSettInfoSetting *sett_info,
+                       guint property_idx,
+                       NMConnection *connection,
+                       NMSetting *setting,
+                       NMConnectionSerializationFlags flags,
+                       const NMConnectionSerializationOptions *options)
 {
 	return g_variant_new_boolean (nm_setting_wired_get_auto_negotiate ((NMSettingWired *) setting));
 }
@@ -802,6 +955,8 @@ get_property (GObject *object, guint prop_id,
 {
 	NMSettingWired *setting = NM_SETTING_WIRED (object);
 	NMSettingWiredPrivate *priv = NM_SETTING_WIRED_GET_PRIVATE (setting);
+	GHashTable *hash;
+	guint i;
 
 	switch (prop_id) {
 	case PROP_PORT:
@@ -838,7 +993,16 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_string (value, nm_setting_wired_get_s390_nettype (setting));
 		break;
 	case PROP_S390_OPTIONS:
-		g_value_take_boxed (value, _nm_utils_copy_strdict (priv->s390_options));
+		hash = g_hash_table_new_full (nm_str_hash,
+		                              g_str_equal,
+		                              g_free,
+		                              g_free);
+		for (i = 0; i < priv->s390_options.len; i++) {
+			g_hash_table_insert (hash,
+			                     g_strdup (priv->s390_options.arr[i].name),
+			                     g_strdup (priv->s390_options.arr[i].value_str));
+		}
+		g_value_take_boxed (value, hash);
 		break;
 	case PROP_WAKE_ON_LAN:
 		g_value_set_uint (value, priv->wol);
@@ -859,7 +1023,6 @@ set_property (GObject *object, guint prop_id,
 	NMSettingWiredPrivate *priv = NM_SETTING_WIRED_GET_PRIVATE (object);
 	const char * const *blacklist;
 	const char *mac;
-	int i;
 
 	switch (prop_id) {
 	case PROP_PORT:
@@ -894,6 +1057,8 @@ set_property (GObject *object, guint prop_id,
 		blacklist = g_value_get_boxed (value);
 		g_array_set_size (priv->mac_address_blacklist, 0);
 		if (blacklist && *blacklist) {
+			guint i;
+
 			for (i = 0; blacklist[i]; i++) {
 				mac = _nm_utils_hwaddr_canonical_or_invalid (blacklist[i], ETH_ALEN);
 				g_array_append_val (priv->mac_address_blacklist, mac);
@@ -913,8 +1078,63 @@ set_property (GObject *object, guint prop_id,
 		priv->s390_nettype = g_value_dup_string (value);
 		break;
 	case PROP_S390_OPTIONS:
-		g_hash_table_unref (priv->s390_options);
-		priv->s390_options = _nm_utils_copy_strdict (g_value_get_boxed (value));
+		{
+			GHashTable *hash;
+
+			_s390_options_clear (priv);
+
+			hash = g_value_get_boxed (value);
+
+			priv->s390_options.n_alloc = hash ? g_hash_table_size (hash) : 0u;
+
+			if (priv->s390_options.n_alloc > 0) {
+				gboolean invalid_content = FALSE;
+				GHashTableIter iter;
+				const char *key;
+				const char *val;
+				guint i, j;
+
+				priv->s390_options.arr = g_new (NMUtilsNamedValue, priv->s390_options.n_alloc);
+				g_hash_table_iter_init (&iter, hash);
+				while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val)) {
+					if (!key || !val) {
+						invalid_content = TRUE;
+						continue;
+					}
+					nm_assert (priv->s390_options.len < priv->s390_options.n_alloc);
+					priv->s390_options.arr[priv->s390_options.len] = (NMUtilsNamedValue) {
+						.name      = g_strdup (key),
+						.value_str = g_strdup (val),
+					};
+					priv->s390_options.len++;
+				}
+				if (priv->s390_options.len > 1) {
+					nm_utils_named_value_list_sort (priv->s390_options.arr,
+					                                priv->s390_options.len,
+					                                NULL,
+					                                NULL);
+					/* prune duplicate keys. This is only possible if @hash does not use
+					 * g_str_equal() as compare function (which would be a bug).
+					 * Still, handle this, because we use later binary sort and rely
+					 * on unique names. One bug here, should not bork the remainder
+					 * of the program. */
+					j = 1;
+					for (i = 1; i < priv->s390_options.len; i++) {
+						if (nm_streq (priv->s390_options.arr[j - 1].name,
+						              priv->s390_options.arr[i].name)) {
+							g_free ((char *) priv->s390_options.arr[i].name);
+							g_free ((char *) priv->s390_options.arr[i].value_str);
+							invalid_content = TRUE;
+							continue;
+						}
+						priv->s390_options.arr[j++] = priv->s390_options.arr[i];
+					}
+					priv->s390_options.len = j;
+				}
+
+				g_return_if_fail (!invalid_content);
+			}
+		}
 		break;
 	case PROP_WAKE_ON_LAN:
 		priv->wol = g_value_get_uint (value);
@@ -936,8 +1156,6 @@ nm_setting_wired_init (NMSettingWired *setting)
 {
 	NMSettingWiredPrivate *priv = NM_SETTING_WIRED_GET_PRIVATE (setting);
 
-	priv->s390_options = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
-
 	/* 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);
@@ -965,7 +1183,7 @@ finalize (GObject *object)
 	g_free (priv->duplex);
 	g_free (priv->s390_nettype);
 
-	g_hash_table_destroy (priv->s390_options);
+	_s390_options_clear (priv);
 
 	g_free (priv->device_mac_address);
 	g_free (priv->cloned_mac_address);
@@ -1198,11 +1416,11 @@ nm_setting_wired_class_init (NMSettingWiredClass *klass)
 	                         G_PARAM_STATIC_STRINGS);
 
 	_properties_override_add_override (properties_override,
-	                                     obj_properties[PROP_CLONED_MAC_ADDRESS],
-	                                     G_VARIANT_TYPE_BYTESTRING,
-	                                     _nm_utils_hwaddr_cloned_get,
-	                                     _nm_utils_hwaddr_cloned_set,
-	                                     _nm_utils_hwaddr_cloned_not_set);
+	                                   obj_properties[PROP_CLONED_MAC_ADDRESS],
+	                                   G_VARIANT_TYPE_BYTESTRING,
+	                                   _nm_utils_hwaddr_cloned_get,
+	                                   _nm_utils_hwaddr_cloned_set,
+	                                   _nm_utils_hwaddr_cloned_not_set);
 
 	/* ---dbus---
 	 * property: assigned-mac-address
diff --git a/libnm-core/nm-setting-wired.h b/libnm-core/nm-setting-wired.h
index 8e707b1b..a2d8555a 100644
--- a/libnm-core/nm-setting-wired.h
+++ b/libnm-core/nm-setting-wired.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-wireguard.c b/libnm-core/nm-setting-wireguard.c
index 861b2abd..07a841f4 100644
--- a/libnm-core/nm-setting-wireguard.c
+++ b/libnm-core/nm-setting-wireguard.c
@@ -907,6 +907,8 @@ typedef struct {
 
 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 	PROP_FWMARK,
+	PROP_IP4_AUTO_DEFAULT_ROUTE,
+	PROP_IP6_AUTO_DEFAULT_ROUTE,
 	PROP_LISTEN_PORT,
 	PROP_MTU,
 	PROP_PEER_ROUTES,
@@ -919,6 +921,8 @@ typedef struct {
 	GPtrArray *peers_arr;
 	GHashTable *peers_hash;
 	NMSettingSecretFlags private_key_flags;
+	NMTernary ip4_auto_default_route;
+	NMTernary ip6_auto_default_route;
 	guint32 fwmark;
 	guint32 mtu;
 	guint16 listen_port;
@@ -929,7 +933,7 @@ typedef struct {
 /**
  * NMSettingWireGuard:
  *
- * WireGuard Ethernet Settings
+ * WireGuard Settings
  *
  * Since: 1.16
  */
@@ -1070,6 +1074,38 @@ nm_setting_wireguard_get_mtu (NMSettingWireGuard *self)
 	return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->mtu;
 }
 
+/**
+ * nm_setting_wireguard_get_ip4_auto_default_route:
+ * @self: the #NMSettingWireGuard setting.
+ *
+ * Returns: the "ip4-auto-default-route" property of the setting.
+ *
+ * Since: 1.20
+ */
+NMTernary
+nm_setting_wireguard_get_ip4_auto_default_route (NMSettingWireGuard *self)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), NM_TERNARY_DEFAULT);
+
+	return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->ip4_auto_default_route;
+}
+
+/**
+ * nm_setting_wireguard_get_ip6_auto_default_route:
+ * @self: the #NMSettingWireGuard setting.
+ *
+ * Returns: the "ip6-auto-default-route" property of the setting.
+ *
+ * Since: 1.20
+ */
+NMTernary
+nm_setting_wireguard_get_ip6_auto_default_route (NMSettingWireGuard *self)
+{
+	g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), NM_TERNARY_DEFAULT);
+
+	return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->ip6_auto_default_route;
+}
+
 /*****************************************************************************/
 
 static void
@@ -1460,7 +1496,8 @@ _peers_dbus_only_synth (const NMSettInfoSetting *sett_info,
                         guint property_idx,
                         NMConnection *connection,
                         NMSetting *setting,
-                        NMConnectionSerializationFlags flags)
+                        NMConnectionSerializationFlags flags,
+                        const NMConnectionSerializationOptions *options)
 {
 	NMSettingWireGuard *self = NM_SETTING_WIREGUARD (setting);
 	NMSettingWireGuardPrivate *priv;
@@ -1490,7 +1527,7 @@ _peers_dbus_only_synth (const NMSettInfoSetting *sett_info,
 		    && peer->endpoint)
 			g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_ENDPOINT, g_variant_new_string (nm_sock_addr_endpoint_get_endpoint (peer->endpoint)));
 
-		if (   !NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_NO_SECRETS)
+		if (   _nm_connection_serialize_secrets (flags, peer->preshared_key_flags)
 		    && peer->preshared_key)
 			g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, g_variant_new_string (peer->preshared_key));
 
@@ -1712,7 +1749,8 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		    && (method = nm_setting_ip_config_get_method (s_ip6))
 		    && !NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
 		                              NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
-		                              NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
+		                              NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+		                              NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
 			g_set_error (error,
 			             NM_CONNECTION_ERROR,
 			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -1945,8 +1983,10 @@ update_one_secret (NMSetting *setting,
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	NMSettingWireGuardPrivate *a_priv;
@@ -1958,11 +1998,11 @@ compare_property (const NMSettInfoSetting *sett_info,
 		if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE))
 			return NM_TERNARY_DEFAULT;
 
-		if (!other)
+		if (!set_b)
 			return TRUE;
 
-		a_priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
-		b_priv = NM_SETTING_WIREGUARD_GET_PRIVATE (other);
+		a_priv = NM_SETTING_WIREGUARD_GET_PRIVATE (set_a);
+		b_priv = NM_SETTING_WIREGUARD_GET_PRIVATE (set_b);
 
 		if (a_priv->peers_arr->len != b_priv->peers_arr->len)
 			return FALSE;
@@ -1981,8 +2021,10 @@ compare_property (const NMSettInfoSetting *sett_info,
 
 	return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->compare_property (sett_info,
 	                                                                               property_idx,
-	                                                                               setting,
-	                                                                               other,
+	                                                                               con_a,
+	                                                                               set_a,
+	                                                                               con_b,
+	                                                                               set_b,
 	                                                                               flags);
 }
 
@@ -2254,6 +2296,12 @@ get_property (GObject *object, guint prop_id,
 	case PROP_FWMARK:
 		g_value_set_uint (value, priv->fwmark);
 		break;
+	case PROP_IP4_AUTO_DEFAULT_ROUTE:
+		g_value_set_enum (value, priv->ip4_auto_default_route);
+		break;
+	case PROP_IP6_AUTO_DEFAULT_ROUTE:
+		g_value_set_enum (value, priv->ip6_auto_default_route);
+		break;
 	case PROP_LISTEN_PORT:
 		g_value_set_uint (value, priv->listen_port);
 		break;
@@ -2286,6 +2334,12 @@ set_property (GObject *object, guint prop_id,
 	case PROP_FWMARK:
 		priv->fwmark = g_value_get_uint (value);
 		break;
+	case PROP_IP4_AUTO_DEFAULT_ROUTE:
+		priv->ip4_auto_default_route = g_value_get_enum (value);
+		break;
+	case PROP_IP6_AUTO_DEFAULT_ROUTE:
+		priv->ip6_auto_default_route = g_value_get_enum (value);
+		break;
 	case PROP_LISTEN_PORT:
 		priv->listen_port = g_value_get_uint (value);
 		break;
@@ -2328,6 +2382,8 @@ nm_setting_wireguard_init (NMSettingWireGuard *setting)
 	priv->peers_arr = g_ptr_array_new ();
 	priv->peers_hash = g_hash_table_new (nm_pstr_hash, nm_pstr_equal);
 	priv->peer_routes = TRUE;
+	priv->ip4_auto_default_route = NM_TERNARY_DEFAULT;
+	priv->ip6_auto_default_route = NM_TERNARY_DEFAULT;
 }
 
 /**
@@ -2418,6 +2474,9 @@ nm_setting_wireguard_class_init (NMSettingWireGuardClass *klass)
 	 * The use of fwmark is optional and is by default off. Setting it to 0
 	 * disables it. Otherwise it is a 32-bit fwmark for outgoing packets.
 	 *
+	 * Note that "ip4-auto-default-route" or "ip6-auto-default-route" enabled,
+	 * implies to automatically choose a fwmark.
+	 *
 	 * Since: 1.16
 	 **/
 	obj_properties[PROP_FWMARK] =
@@ -2481,6 +2540,45 @@ nm_setting_wireguard_class_init (NMSettingWireGuardClass *klass)
 	                       | NM_SETTING_PARAM_INFERRABLE
 	                       | G_PARAM_STATIC_STRINGS);
 
+	/**
+	 * NMSettingWireGuard:ip4-auto-default-route:
+	 *
+	 * Whether to enable special handling of the IPv4 default route.
+	 * If enabled, the IPv4 default route will be placed to a dedicated
+	 * routing-table and two policy routing rules will be added.
+	 * The fwmark number is also used as routing-table for the default-route,
+	 * and if fwmark is zero, a unused fwmark/table is chosen automatically.
+	 * This corresponds to what wg-quick does with Table=auto.
+	 *
+	 * Leaving this at the default will enable this option automatically
+	 * if ipv4.never-default is not set and there are any peers that use
+	 * a default-route as allowed-ips.
+	 *
+	 * Since: 1.20
+	 **/
+	obj_properties[PROP_IP4_AUTO_DEFAULT_ROUTE] =
+	    g_param_spec_enum (NM_SETTING_WIREGUARD_IP4_AUTO_DEFAULT_ROUTE, "", "",
+	                       NM_TYPE_TERNARY,
+	                       NM_TERNARY_DEFAULT,
+	                       NM_SETTING_PARAM_FUZZY_IGNORE |
+	                       G_PARAM_READWRITE |
+	                       G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * NMSettingWireGuard:ip6-auto-default-route:
+	 *
+	 * Like ip4-auto-default-route, but for the IPv6 default route.
+	 *
+	 * Since: 1.20
+	 **/
+	obj_properties[PROP_IP6_AUTO_DEFAULT_ROUTE] =
+	    g_param_spec_enum (NM_SETTING_WIREGUARD_IP6_AUTO_DEFAULT_ROUTE, "", "",
+	                       NM_TYPE_TERNARY,
+	                       NM_TERNARY_DEFAULT,
+	                       NM_SETTING_PARAM_FUZZY_IGNORE |
+	                       G_PARAM_READWRITE |
+	                       G_PARAM_STATIC_STRINGS);
+
 	/* ---dbus---
 	 * property: peers
 	 * format: array of 'a{sv}'
diff --git a/libnm-core/nm-setting-wireguard.h b/libnm-core/nm-setting-wireguard.h
index 017eb1f6..1f81422f 100644
--- a/libnm-core/nm-setting-wireguard.h
+++ b/libnm-core/nm-setting-wireguard.h
@@ -138,6 +138,8 @@ int nm_wireguard_peer_cmp (const NMWireGuardPeer *a,
 
 #define NM_SETTING_WIREGUARD_MTU               "mtu"
 #define NM_SETTING_WIREGUARD_PEER_ROUTES       "peer-routes"
+#define NM_SETTING_WIREGUARD_IP4_AUTO_DEFAULT_ROUTE "ip4-auto-default-route"
+#define NM_SETTING_WIREGUARD_IP6_AUTO_DEFAULT_ROUTE "ip6-auto-default-route"
 
 #define NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS          "allowed-ips"
 #define NM_WIREGUARD_PEER_ATTR_ENDPOINT             "endpoint"
@@ -206,6 +208,12 @@ gboolean nm_setting_wireguard_get_peer_routes (NMSettingWireGuard *self);
 NM_AVAILABLE_IN_1_16
 guint32 nm_setting_wireguard_get_mtu (NMSettingWireGuard *self);
 
+NM_AVAILABLE_IN_1_20
+NMTernary nm_setting_wireguard_get_ip4_auto_default_route (NMSettingWireGuard *self);
+
+NM_AVAILABLE_IN_1_20
+NMTernary nm_setting_wireguard_get_ip6_auto_default_route (NMSettingWireGuard *self);
+
 /*****************************************************************************/
 
 G_END_DECLS
diff --git a/libnm-core/nm-setting-wireless-security.c b/libnm-core/nm-setting-wireless-security.c
index f689751e..efb4860b 100644
--- a/libnm-core/nm-setting-wireless-security.c
+++ b/libnm-core/nm-setting-wireless-security.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -906,6 +904,11 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	const char *valid_protos[] = { "wpa", "rsn", NULL };
 	const char *valid_pairwise[] = { "tkip", "ccmp", NULL };
 	const char *valid_groups[] = { "wep40", "wep104", "tkip", "ccmp", NULL };
+	NMSettingWireless *s_wifi;
+	const char *wifi_mode;
+
+	s_wifi = connection ? nm_connection_get_setting_wireless (connection) : NULL;
+	wifi_mode = s_wifi ? nm_setting_wireless_get_mode (s_wifi) : NULL;
 
 	if (!priv->key_mgmt) {
 		g_set_error_literal (error,
@@ -916,14 +919,27 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		return FALSE;
 	}
 
-	if (!g_strv_contains (valid_key_mgmt, priv->key_mgmt)) {
-		g_set_error (error,
-		             NM_CONNECTION_ERROR,
-		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-		             _("'%s' is not a valid value for the property"),
-		             priv->key_mgmt);
-		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT);
-		return FALSE;
+	if (g_strcmp0 (wifi_mode, NM_SETTING_WIRELESS_MODE_MESH) == 0) {
+		if (   (strcmp (priv->key_mgmt, "none") == 0)
+		    || (strcmp (priv->key_mgmt, "sae") == 0)) {
+			g_set_error (error,
+			             NM_CONNECTION_ERROR,
+			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("'%s' is not a valid value for '%s' mode connections"),
+			             priv->key_mgmt, NM_SETTING_WIRELESS_MODE_MESH);
+			g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT);
+			return FALSE;
+		}
+	} else {
+		if (!g_strv_contains (valid_key_mgmt, priv->key_mgmt)) {
+			g_set_error (error,
+			             NM_CONNECTION_ERROR,
+			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("'%s' is not a valid value for the property"),
+			             priv->key_mgmt);
+			g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT);
+			return FALSE;
+		}
 	}
 
 	if (priv->auth_alg && !strcmp (priv->auth_alg, "leap")) {
diff --git a/libnm-core/nm-setting-wireless-security.h b/libnm-core/nm-setting-wireless-security.h
index c560f348..d5c069cf 100644
--- a/libnm-core/nm-setting-wireless-security.h
+++ b/libnm-core/nm-setting-wireless-security.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-setting-wireless.c b/libnm-core/nm-setting-wireless.c
index d8056c6c..cc04e1a0 100644
--- a/libnm-core/nm-setting-wireless.c
+++ b/libnm-core/nm-setting-wireless.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -73,8 +71,8 @@ typedef struct {
 	char *cloned_mac_address;
 	char *generate_mac_address_mask;
 	GArray *mac_address_blacklist;
+	GPtrArray *seen_bssids;
 	guint32 mtu;
-	GSList *seen_bssids;
 	gboolean hidden;
 	guint32 powersave;
 	NMSettingMacRandomization mac_address_randomization;
@@ -660,33 +658,27 @@ nm_setting_wireless_add_seen_bssid (NMSettingWireless *setting,
                                     const char *bssid)
 {
 	NMSettingWirelessPrivate *priv;
-	char *lower_bssid;
-	GSList *iter;
-	gboolean found = FALSE;
+	gs_free char *lower_bssid = NULL;
 
 	g_return_val_if_fail (NM_IS_SETTING_WIRELESS (setting), FALSE);
 	g_return_val_if_fail (bssid != NULL, FALSE);
 
-	lower_bssid = g_ascii_strdown (bssid, -1);
-	if (!lower_bssid)
-		return FALSE;
-
 	priv = NM_SETTING_WIRELESS_GET_PRIVATE (setting);
 
-	for (iter = priv->seen_bssids; iter; iter = iter->next) {
-		if (!strcmp ((char *) iter->data, lower_bssid)) {
-			found = TRUE;
-			break;
-		}
-	}
+	lower_bssid = g_ascii_strdown (bssid, -1);
 
-	if (!found) {
-		priv->seen_bssids = g_slist_prepend (priv->seen_bssids, lower_bssid);
-		_notify (setting, PROP_SEEN_BSSIDS);
-	} else
-		g_free (lower_bssid);
+	if (!priv->seen_bssids) {
+		priv->seen_bssids = g_ptr_array_new_with_free_func (g_free);
+	} else {
+		if (nm_utils_strv_find_first ((char **) priv->seen_bssids->pdata,
+		                              priv->seen_bssids->len,
+		                              lower_bssid) >= 0)
+			return FALSE;
+	}
 
-	return !found;
+	g_ptr_array_add (priv->seen_bssids, g_steal_pointer (&lower_bssid));
+	_notify (setting, PROP_SEEN_BSSIDS);
+	return TRUE;
 }
 
 /**
@@ -698,9 +690,15 @@ nm_setting_wireless_add_seen_bssid (NMSettingWireless *setting,
 guint32
 nm_setting_wireless_get_num_seen_bssids (NMSettingWireless *setting)
 {
+	NMSettingWirelessPrivate *priv;
+
 	g_return_val_if_fail (NM_IS_SETTING_WIRELESS (setting), 0);
 
-	return g_slist_length (NM_SETTING_WIRELESS_GET_PRIVATE (setting)->seen_bssids);
+	priv = NM_SETTING_WIRELESS_GET_PRIVATE (setting);
+
+	return   priv->seen_bssids
+	       ? priv->seen_bssids->len
+	       : 0u;
 }
 
 /**
@@ -714,19 +712,60 @@ const char *
 nm_setting_wireless_get_seen_bssid (NMSettingWireless *setting,
                                     guint32 i)
 {
-	g_return_val_if_fail (NM_IS_SETTING_WIRELESS (setting), NULL);
+	NMSettingWirelessPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_SETTING_WIRELESS (setting), 0);
+
+	priv = NM_SETTING_WIRELESS_GET_PRIVATE (setting);
+
+	if (   !priv->seen_bssids
+	    || i >= priv->seen_bssids->len)
+		return NULL;
+
+	return priv->seen_bssids->pdata[i];
+}
+
+static GVariant *
+_to_dbus_fcn_seen_bssids (const NMSettInfoSetting *sett_info,
+                          guint property_idx,
+                          NMConnection *connection,
+                          NMSetting *setting,
+                          NMConnectionSerializationFlags flags,
+                          const NMConnectionSerializationOptions *options)
+{
+	NMSettingWirelessPrivate *priv;
+
+	if (   options
+	    && options->seen_bssids) {
+		return   options->seen_bssids[0]
+		       ? g_variant_new_strv (options->seen_bssids, -1)
+		       : NULL;
+	}
 
-	return (const char *) g_slist_nth_data (NM_SETTING_WIRELESS_GET_PRIVATE (setting)->seen_bssids, i);
+	priv = NM_SETTING_WIRELESS_GET_PRIVATE (setting);
+
+	if (   !priv->seen_bssids
+	    || priv->seen_bssids->len == 0)
+		return NULL;
+
+	return g_variant_new_strv ((const char *const*) priv->seen_bssids->pdata, priv->seen_bssids->len);
 }
 
+/*****************************************************************************/
+
 static gboolean
 verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
 	NMSettingWirelessPrivate *priv = NM_SETTING_WIRELESS_GET_PRIVATE (setting);
-	const char *valid_modes[] = { NM_SETTING_WIRELESS_MODE_INFRA, NM_SETTING_WIRELESS_MODE_ADHOC, NM_SETTING_WIRELESS_MODE_AP, NULL };
+	const char *valid_modes[] = {
+		NM_SETTING_WIRELESS_MODE_INFRA,
+		NM_SETTING_WIRELESS_MODE_ADHOC,
+		NM_SETTING_WIRELESS_MODE_AP,
+		NM_SETTING_WIRELESS_MODE_MESH,
+		NULL
+	};
 	const char *valid_bands[] = { "a", "bg", NULL };
-	GSList *iter;
-	int i;
+	guint i;
 	gsize length;
 	GError *local = NULL;
 
@@ -791,6 +830,16 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		}
 	}
 
+	if ((g_strcmp0 (priv->mode, NM_SETTING_WIRELESS_MODE_MESH) == 0) && !(priv->channel && priv->band)) {
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_MISSING_PROPERTY,
+		             _("'%s' requires '%s' and '%s' property"),
+		             priv->mode, NM_SETTING_WIRELESS_BAND, NM_SETTING_WIRELESS_CHANNEL);
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_MODE);
+		return FALSE;
+	}
+
 	if (priv->bssid && !nm_utils_hwaddr_valid (priv->bssid, ETH_ALEN)) {
 		g_set_error_literal (error,
 		                     NM_CONNECTION_ERROR,
@@ -848,15 +897,20 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		}
 	}
 
-	for (iter = priv->seen_bssids; iter; iter = iter->next) {
-		if (!nm_utils_hwaddr_valid (iter->data, ETH_ALEN)) {
-			g_set_error (error,
-			             NM_CONNECTION_ERROR,
-			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			             _("'%s' is not a valid MAC address"),
-			             (const char *) iter->data);
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_SEEN_BSSIDS);
-			return FALSE;
+	if (priv->seen_bssids) {
+		for (i = 0; i < priv->seen_bssids->len; i++) {
+			const char *b;
+
+			b = priv->seen_bssids->pdata[i];
+			if (!nm_utils_hwaddr_valid (b, ETH_ALEN)) {
+				g_set_error (error,
+				             NM_CONNECTION_ERROR,
+				             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				             _("'%s' is not a valid MAC address"),
+				             b);
+				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_SEEN_BSSIDS);
+				return FALSE;
+			}
 		}
 	}
 
@@ -919,20 +973,24 @@ mac_addr_rand_ok:
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS)) {
-		return    !other
-		       || nm_streq0 (NM_SETTING_WIRELESS_GET_PRIVATE (setting)->cloned_mac_address,
-		                     NM_SETTING_WIRELESS_GET_PRIVATE (other)->cloned_mac_address);
+		return    !set_b
+		       || nm_streq0 (NM_SETTING_WIRELESS_GET_PRIVATE (set_a)->cloned_mac_address,
+		                     NM_SETTING_WIRELESS_GET_PRIVATE (set_b)->cloned_mac_address);
 	}
 
 	return NM_SETTING_CLASS (nm_setting_wireless_parent_class)->compare_property (sett_info,
 	                                                                              property_idx,
-	                                                                              setting,
-	                                                                              other,
+	                                                                              con_a,
+	                                                                              set_a,
+	                                                                              con_b,
+	                                                                              set_b,
 	                                                                              flags);
 }
 
@@ -943,7 +1001,8 @@ nm_setting_wireless_get_security (const NMSettInfoSetting *sett_info,
                                   guint property_idx,
                                   NMConnection *connection,
                                   NMSetting *setting,
-                                  NMConnectionSerializationFlags flags)
+                                  NMConnectionSerializationFlags flags,
+                                  const NMConnectionSerializationOptions *options)
 {
 	if (flags & NM_CONNECTION_SERIALIZE_ONLY_SECRETS)
 		return NULL;
@@ -1028,7 +1087,11 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_uint (value, nm_setting_wireless_get_mtu (setting));
 		break;
 	case PROP_SEEN_BSSIDS:
-		g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->seen_bssids, TRUE));
+		g_value_take_boxed (value,
+		                      priv->seen_bssids
+		                    ? nm_utils_strv_dup (priv->seen_bssids->pdata,
+		                                         priv->seen_bssids->len)
+		                    : NULL);
 		break;
 	case PROP_HIDDEN:
 		g_value_set_boolean (value, nm_setting_wireless_get_hidden (setting));
@@ -1056,7 +1119,6 @@ set_property (GObject *object, guint prop_id,
 	const char * const *blacklist;
 	const char *mac;
 	gboolean bool_val;
-	int i;
 
 	switch (prop_id) {
 	case PROP_SSID:
@@ -1111,7 +1173,9 @@ set_property (GObject *object, guint prop_id,
 	case PROP_MAC_ADDRESS_BLACKLIST:
 		blacklist = g_value_get_boxed (value);
 		g_array_set_size (priv->mac_address_blacklist, 0);
-		if (blacklist && *blacklist) {
+		if (blacklist && blacklist[0]) {
+			gsize i;
+
 			for (i = 0; blacklist[i]; i++) {
 				mac = _nm_utils_hwaddr_canonical_or_invalid (blacklist[i], ETH_ALEN);
 				g_array_append_val (priv->mac_address_blacklist, mac);
@@ -1121,10 +1185,23 @@ set_property (GObject *object, guint prop_id,
 	case PROP_MTU:
 		priv->mtu = g_value_get_uint (value);
 		break;
-	case PROP_SEEN_BSSIDS:
-		g_slist_free_full (priv->seen_bssids, g_free);
-		priv->seen_bssids = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE);
+	case PROP_SEEN_BSSIDS: {
+		gs_unref_ptrarray GPtrArray *arr_old = NULL;
+		const char *const*strv;
+
+		arr_old = g_steal_pointer (&priv->seen_bssids);
+
+		strv = g_value_get_boxed (value);
+		if (strv && strv[0]) {
+			gsize i, l;
+
+			l = NM_PTRARRAY_LEN (strv);
+			priv->seen_bssids = g_ptr_array_new_full (l, g_free);
+			for (i = 0; i < l; i++)
+				g_ptr_array_add (priv->seen_bssids, g_strdup (strv[i]));
+		}
 		break;
+	}
 	case PROP_HIDDEN:
 		priv->hidden = g_value_get_boolean (value);
 		break;
@@ -1183,7 +1260,7 @@ finalize (GObject *object)
 	g_free (priv->cloned_mac_address);
 	g_free (priv->generate_mac_address_mask);
 	g_array_unref (priv->mac_address_blacklist);
-	g_slist_free_full (priv->seen_bssids, g_free);
+	nm_clear_pointer (&priv->seen_bssids, g_ptr_array_unref);
 
 	G_OBJECT_CLASS (nm_setting_wireless_parent_class)->finalize (object);
 }
@@ -1231,7 +1308,7 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *klass)
 	/**
 	 * NMSettingWireless:mode:
 	 *
-	 * Wi-Fi network mode; one of "infrastructure", "adhoc" or "ap".  If blank,
+	 * Wi-Fi network mode; one of "infrastructure", "mesh", "adhoc" or "ap".  If blank,
 	 * infrastructure is assumed.
 	 **/
 	/* ---ifcfg-rh---
@@ -1572,6 +1649,13 @@ nm_setting_wireless_class_init (NMSettingWirelessClass *klass)
 	                        NM_SETTING_PARAM_FUZZY_IGNORE |
 	                        G_PARAM_STATIC_STRINGS);
 
+	_properties_override_add_override (properties_override,
+	                                   obj_properties[PROP_SEEN_BSSIDS],
+	                                   G_VARIANT_TYPE_STRING_ARRAY,
+	                                   _to_dbus_fcn_seen_bssids,
+	                                   NULL,
+	                                   NULL);
+
 	/**
 	 * NMSettingWireless:mtu:
 	 *
diff --git a/libnm-core/nm-setting-wireless.h b/libnm-core/nm-setting-wireless.h
index a2ae38a4..dcb11e11 100644
--- a/libnm-core/nm-setting-wireless.h
+++ b/libnm-core/nm-setting-wireless.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -125,6 +123,13 @@ typedef enum { /*< flags >*/
 #define NM_SETTING_WIRELESS_MODE_INFRA  "infrastructure"
 
 /**
+ * NM_SETTING_WIRELESS_MODE_MESH:
+ *
+ * Indicates that the connection should create a mesh point.
+ */
+#define NM_SETTING_WIRELESS_MODE_MESH   "mesh"
+
+/**
  * NMSettingWirelessPowersave:
  * @NM_SETTING_WIRELESS_POWERSAVE_DEFAULT: use the default value
  * @NM_SETTING_WIRELESS_POWERSAVE_IGNORE: don't touch existing setting
diff --git a/libnm-core/nm-setting.c b/libnm-core/nm-setting.c
index 2e9081df..4323b83c 100644
--- a/libnm-core/nm-setting.c
+++ b/libnm-core/nm-setting.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -199,10 +197,31 @@ _nm_sett_info_property_find_in_array (const NMSettInfoProperty *properties, guin
 	return NULL;
 }
 
+static GVariant *
+_gprop_to_dbus_fcn_bytes (const GValue *val)
+{
+	nm_assert (G_VALUE_HOLDS (val, G_TYPE_BYTES));
+	return nm_utils_gbytes_to_variant_ay (g_value_get_boxed (val));
+}
+
+static GVariant *
+_gprop_to_dbus_fcn_enum (const GValue *val)
+{
+	return g_variant_new_int32 (g_value_get_enum (val));
+}
+
+static GVariant *
+_gprop_to_dbus_fcn_flags (const GValue *val)
+{
+	return g_variant_new_uint32 (g_value_get_flags (val));
+}
+
 void
 _properties_override_add_struct (GArray *properties_override,
                                  const NMSettInfoProperty *prop_info)
 {
+	NMSettInfoProperty *p;
+
 	nm_assert (properties_override);
 	nm_assert (prop_info);
 	nm_assert (prop_info->name || prop_info->param_spec);
@@ -211,16 +230,25 @@ _properties_override_add_struct (GArray *properties_override,
 	                                                  properties_override->len,
 	                                                  prop_info->name ?: prop_info->param_spec->name));
 
-	nm_assert (!prop_info->from_dbus || prop_info->dbus_type);
-	nm_assert (!prop_info->set_func || prop_info->dbus_type);
+	nm_assert (!prop_info->gprop_from_dbus_fcn || prop_info->dbus_type);
+	nm_assert (!prop_info->from_dbus_fcn || prop_info->dbus_type);
+	nm_assert (!prop_info->to_dbus_fcn || prop_info->dbus_type);
+
+	nm_assert (!prop_info->to_dbus_fcn   || !prop_info->gprop_to_dbus_fcn);
+	nm_assert (!prop_info->from_dbus_fcn || !prop_info->gprop_from_dbus_fcn);
+
+	nm_assert (!prop_info->gprop_to_dbus_fcn   || prop_info->param_spec);
+	nm_assert (!prop_info->gprop_from_dbus_fcn || prop_info->param_spec);
 
 	g_array_append_vals (properties_override, prop_info, 1);
 
 	if (!prop_info->name) {
 		/* for convenience, allow omitting "name" if "param_spec" is given. */
-		g_array_index (properties_override,
-		               NMSettInfoProperty,
-		               properties_override->len - 1).name = prop_info->param_spec->name;
+		p = &g_array_index (properties_override,
+		                    NMSettInfoProperty,
+		                    properties_override->len - 1);
+		nm_assert (p->param_spec);
+		p->name = p->param_spec->name;
 	}
 }
 
@@ -229,34 +257,34 @@ _properties_override_add_struct (GArray *properties_override,
  * @properties_override: an array collecting the overrides
  * @property_name: the name of the property to override
  * @dbus_type: the type of the property (in its D-Bus representation)
- * @synth_func: (allow-none): function to call to synthesize a value for the property
- * @set_func: (allow-none): function to call to set the value of the property
+ * @to_dbus_fcn: (allow-none): function to call to synthesize a value for the property
+ * @from_dbus_fcn: (allow-none): function to call to set the value of the property
  *
  * Registers a property named @property_name, which will be used in the D-Bus
  * serialization of objects of this setting type, but which does not correspond to
  * a #GObject property.
  *
- * When serializing a setting to D-Bus, @synth_func will be called to synthesize
+ * When serializing a setting to D-Bus, @to_dbus_fcn will be called to synthesize
  * a value for the property. (If it returns %NULL, no value will be added to the
- * serialization. If @synth_func is %NULL, the property will always be omitted
+ * serialization. If @to_dbus_fcn is %NULL, the property will always be omitted
  * in the serialization.)
  *
  * When deserializing a D-Bus representation into a setting, if @property_name
- * is present, then @set_func will be called to set it. (If @set_func is %NULL
+ * is present, then @from_dbus_fcn will be called to set it. (If @from_dbus_fcn is %NULL
  * then the property will be ignored when deserializing.)
  */
 void
 _properties_override_add_dbus_only (GArray *properties_override,
                                     const char *property_name,
                                     const GVariantType *dbus_type,
-                                    NMSettingPropertySynthFunc synth_func,
-                                    NMSettingPropertySetFunc set_func)
+                                    NMSettInfoPropToDBusFcn to_dbus_fcn,
+                                    NMSettInfoPropFromDBusFcn from_dbus_fcn)
 {
 	_properties_override_add (properties_override,
-	                          .name = property_name,
-	                          .dbus_type = dbus_type,
-	                          .synth_func = synth_func,
-	                          .set_func = set_func);
+	                          .name          = property_name,
+	                          .dbus_type     = dbus_type,
+	                          .to_dbus_fcn   = to_dbus_fcn,
+	                          .from_dbus_fcn = from_dbus_fcn);
 }
 
 /**
@@ -264,26 +292,26 @@ _properties_override_add_dbus_only (GArray *properties_override,
  * @properties_override: an array collecting the overrides
  * @param_spec: the name of the property to override
  * @dbus_type: the type of the property (in its D-Bus representation)
- * @get_func: (allow-none): function to call to get the value of the property
- * @set_func: (allow-none): function to call to set the value of the property
- * @not_set_func: (allow-none): function to call to indicate the property was not set
+ * @to_dbus_fcn: (allow-none): function to call to get the value of the property
+ * @from_dbus_fcn: (allow-none): function to call to set the value of the property
+ * @missing_from_dbus_fcn: (allow-none): function to call to indicate the property was not set
  *
  * Overrides the D-Bus representation of the #GObject property that shares the
  * same name as @param_spec.
  *
- * When serializing a setting to D-Bus, if @get_func is non-%NULL, then it will
+ * When serializing a setting to D-Bus, if @to_dbus_fcn is non-%NULL, then it will
  * be called to get the property's value. If it returns a #GVariant, the
  * property will be added to the hash, and if it returns %NULL, the property
- * will be omitted. (If @get_func is %NULL, the property will be read normally
+ * will be omitted. (If @to_dbus_fcn is %NULL, the property will be read normally
  * with g_object_get_property(), and added to the hash if it is not the default
  * value.)
  *
  * When deserializing a D-Bus representation into a setting, if a value with
- * the name of @param_spec is present, then @set_func will be called to set it.
- * (If @set_func is %NULL then the property will be set normally with
+ * the name of @param_spec is present, then @from_dbus_fcn will be called to set it.
+ * (If @from_dbus_fcn is %NULL then the property will be set normally with
  * g_object_set_property().)
  *
- * If @not_set_func is non-%NULL, then it will be called when deserializing a
+ * If @missing_from_dbus_fcn is non-%NULL, then it will be called when deserializing a
  * representation that does NOT contain a value for the property. This can be used,
  * eg, if a new property needs to be initialized from some older deprecated property
  * when it is not present.
@@ -292,18 +320,18 @@ void
 _properties_override_add_override (GArray *properties_override,
                                    GParamSpec *param_spec,
                                    const GVariantType *dbus_type,
-                                   NMSettingPropertyGetFunc get_func,
-                                   NMSettingPropertySetFunc set_func,
-                                   NMSettingPropertyNotSetFunc not_set_func)
+                                   NMSettInfoPropToDBusFcn to_dbus_fcn,
+                                   NMSettInfoPropFromDBusFcn from_dbus_fcn,
+                                   NMSettInfoPropMissingFromDBusFcn missing_from_dbus_fcn)
 {
 	nm_assert (param_spec);
 
 	_properties_override_add (properties_override,
-	                          .param_spec = param_spec,
-	                          .dbus_type = dbus_type,
-	                          .get_func = get_func,
-	                          .set_func = set_func,
-	                          .not_set_func = not_set_func);
+	                          .param_spec             = param_spec,
+	                          .dbus_type              = dbus_type,
+	                          .to_dbus_fcn            = to_dbus_fcn,
+	                          .from_dbus_fcn          = from_dbus_fcn,
+	                          .missing_from_dbus_fcn  = missing_from_dbus_fcn);
 }
 
 /**
@@ -311,8 +339,8 @@ _properties_override_add_override (GArray *properties_override,
  * @properties_override: an array collecting the overrides
  * @param_spec: the param spec of the property to transform.
  * @dbus_type: the type of the property (in its D-Bus representation)
- * @to_dbus: function to convert from object to D-Bus format
- * @from_dbus: function to convert from D-Bus to object format
+ * @gprop_to_dbus_fcn: function to convert from object to D-Bus format
+ * @gprop_from_dbus_fcn: function to convert from D-Bus to object format
  *
  * Indicates that @property on @setting_class does not have the same format as
  * its corresponding D-Bus representation, and so must be transformed when
@@ -326,16 +354,16 @@ void
 _properties_override_add_transform (GArray *properties_override,
                                     GParamSpec *param_spec,
                                     const GVariantType *dbus_type,
-                                    NMSettingPropertyTransformToFunc to_dbus,
-                                    NMSettingPropertyTransformFromFunc from_dbus)
+                                    NMSettInfoPropGPropToDBusFcn gprop_to_dbus_fcn,
+                                    NMSettInfoPropGPropFromDBusFcn gprop_from_dbus_fcn)
 {
 	nm_assert (param_spec);
 
 	_properties_override_add (properties_override,
-	                          .param_spec = param_spec,
-	                          .dbus_type = dbus_type,
-	                          .to_dbus = to_dbus,
-	                          .from_dbus = from_dbus);
+	                          .param_spec          = param_spec,
+	                          .dbus_type           = dbus_type,
+	                          .gprop_to_dbus_fcn   = gprop_to_dbus_fcn,
+	                          .gprop_from_dbus_fcn = gprop_from_dbus_fcn);
 }
 
 static NMSettInfoSetting _sett_info_settings[_NM_META_SETTING_TYPE_NUM];
@@ -478,6 +506,49 @@ _nm_setting_class_commit_full (NMSettingClass *setting_class,
 		p->param_spec = property_specs[i];
 	}
 
+	for (i = 0; i < properties_override->len; i++) {
+		NMSettInfoProperty *p = &g_array_index (properties_override, NMSettInfoProperty, i);
+		GType vtype;
+
+		if (p->dbus_type)
+			continue;
+
+		nm_assert (p->param_spec);
+		nm_assert (!p->gprop_to_dbus_fcn);
+
+		vtype = p->param_spec->value_type;
+		if (vtype == G_TYPE_BOOLEAN)
+			p->dbus_type = G_VARIANT_TYPE_BOOLEAN;
+		else if (vtype == G_TYPE_UCHAR)
+			p->dbus_type = G_VARIANT_TYPE_BYTE;
+		else if (vtype == G_TYPE_INT)
+			p->dbus_type = G_VARIANT_TYPE_INT32;
+		else if (vtype == G_TYPE_UINT)
+			p->dbus_type = G_VARIANT_TYPE_UINT32;
+		else if (vtype == G_TYPE_INT64)
+			p->dbus_type = G_VARIANT_TYPE_INT64;
+		else if (vtype == G_TYPE_UINT64)
+			p->dbus_type = G_VARIANT_TYPE_UINT64;
+		else if (vtype == G_TYPE_STRING)
+			p->dbus_type = G_VARIANT_TYPE_STRING;
+		else if (vtype == G_TYPE_DOUBLE)
+			p->dbus_type = G_VARIANT_TYPE_DOUBLE;
+		else if (vtype == G_TYPE_STRV)
+			p->dbus_type = G_VARIANT_TYPE_STRING_ARRAY;
+		else if (vtype == G_TYPE_BYTES) {
+			p->dbus_type = G_VARIANT_TYPE_BYTESTRING;
+			p->gprop_to_dbus_fcn = _gprop_to_dbus_fcn_bytes;
+		} else if (g_type_is_a (vtype, G_TYPE_ENUM)) {
+			p->dbus_type = G_VARIANT_TYPE_INT32;
+			p->gprop_to_dbus_fcn = _gprop_to_dbus_fcn_enum;
+		} else if (g_type_is_a (vtype, G_TYPE_FLAGS)) {
+			p->dbus_type = G_VARIANT_TYPE_UINT32;
+			p->gprop_to_dbus_fcn = _gprop_to_dbus_fcn_flags;
+		}
+
+		nm_assert (p->dbus_type);
+	}
+
 	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMSettInfoProperty, name) == 0);
 	g_array_sort (properties_override, nm_strcmp_p);
 
@@ -609,73 +680,80 @@ _nm_setting_use_legacy_property (NMSetting *setting,
 
 /*****************************************************************************/
 
-static const GVariantType *
-variant_type_for_gtype (GType type)
-{
-	if (type == G_TYPE_BOOLEAN)
-		return G_VARIANT_TYPE_BOOLEAN;
-	else if (type == G_TYPE_UCHAR)
-		return G_VARIANT_TYPE_BYTE;
-	else if (type == G_TYPE_INT)
-		return G_VARIANT_TYPE_INT32;
-	else if (type == G_TYPE_UINT)
-		return G_VARIANT_TYPE_UINT32;
-	else if (type == G_TYPE_INT64)
-		return G_VARIANT_TYPE_INT64;
-	else if (type == G_TYPE_UINT64)
-		return G_VARIANT_TYPE_UINT64;
-	else if (type == G_TYPE_STRING)
-		return G_VARIANT_TYPE_STRING;
-	else if (type == G_TYPE_DOUBLE)
-		return G_VARIANT_TYPE_DOUBLE;
-	else if (type == G_TYPE_STRV)
-		return G_VARIANT_TYPE_STRING_ARRAY;
-	else if (type == G_TYPE_BYTES)
-		return G_VARIANT_TYPE_BYTESTRING;
-	else if (g_type_is_a (type, G_TYPE_ENUM))
-		return G_VARIANT_TYPE_INT32;
-	else if (g_type_is_a (type, G_TYPE_FLAGS))
-		return G_VARIANT_TYPE_UINT32;
-	else
-		g_assert_not_reached ();
-}
-
 static GVariant *
-get_property_for_dbus (NMSetting *setting,
-                       const NMSettInfoProperty *property,
-                       gboolean ignore_default)
+property_to_dbus (const NMSettInfoSetting *sett_info,
+                  guint property_idx,
+                  NMConnection *connection,
+                  NMSetting *setting,
+                  NMConnectionSerializationFlags flags,
+                  const NMConnectionSerializationOptions *options,
+                  gboolean ignore_flags,
+                  gboolean ignore_default)
 {
-	GValue prop_value = { 0, };
-	GVariant *dbus_value;
+	const NMSettInfoProperty *property = &sett_info->property_infos[property_idx];
+	GVariant *variant;
 
-	if (property->get_func)
-		return property->get_func (setting, property->name);
-	else
-		g_return_val_if_fail (property->param_spec != NULL, NULL);
+	nm_assert (property->dbus_type);
 
-	g_value_init (&prop_value, property->param_spec->value_type);
-	g_object_get_property (G_OBJECT (setting), property->param_spec->name, &prop_value);
+	if (!property->param_spec) {
+		if (!property->to_dbus_fcn)
+			return NULL;
+	} else if (!ignore_flags) {
+		if (!NM_FLAGS_HAS (property->param_spec->flags, G_PARAM_WRITABLE))
+			return NULL;
 
-	if (ignore_default && g_param_value_defaults (property->param_spec, &prop_value)) {
-		g_value_unset (&prop_value);
-		return NULL;
+		if (NM_FLAGS_ANY (property->param_spec->flags, NM_SETTING_PARAM_GENDATA_BACKED))
+			return NULL;
+
+		if (   NM_FLAGS_HAS (property->param_spec->flags, NM_SETTING_PARAM_LEGACY)
+		    && !_nm_utils_is_manager_process)
+			return NULL;
+
+		if (NM_FLAGS_HAS (property->param_spec->flags, NM_SETTING_PARAM_SECRET)) {
+			if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_NO_SECRETS))
+				return NULL;
+			if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED)) {
+				NMSettingSecretFlags f;
+
+				/* see also _nm_connection_serialize_secrets() */
+				if (!nm_setting_get_secret_flags (setting, property->param_spec->name, &f, NULL))
+					return NULL;
+				if (!NM_FLAGS_HAS (f, NM_SETTING_SECRET_FLAG_AGENT_OWNED))
+					return NULL;
+			}
+		} else {
+			if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS))
+				return NULL;
+		}
 	}
 
-	if (property->to_dbus)
-		dbus_value = property->to_dbus (&prop_value);
-	else if (property->dbus_type)
-		dbus_value = g_dbus_gvalue_to_gvariant (&prop_value, property->dbus_type);
-	else if (g_type_is_a (prop_value.g_type, G_TYPE_ENUM))
-		dbus_value = g_variant_new_int32 (g_value_get_enum (&prop_value));
-	else if (g_type_is_a (prop_value.g_type, G_TYPE_FLAGS))
-		dbus_value = g_variant_new_uint32 (g_value_get_flags (&prop_value));
-	else if (prop_value.g_type == G_TYPE_BYTES)
-		dbus_value = nm_utils_gbytes_to_variant_ay (g_value_get_boxed (&prop_value));
-	else
-		dbus_value = g_dbus_gvalue_to_gvariant (&prop_value, variant_type_for_gtype (prop_value.g_type));
-	g_value_unset (&prop_value);
+	if (property->to_dbus_fcn) {
+		variant = property->to_dbus_fcn (sett_info, property_idx, connection, setting, flags, options);
+		nm_g_variant_take_ref (variant);
+	} else {
+		nm_auto_unset_gvalue GValue prop_value = { 0, };
+
+		nm_assert (property->param_spec);
+
+		g_value_init (&prop_value, property->param_spec->value_type);
+
+		g_object_get_property (G_OBJECT (setting), property->param_spec->name, &prop_value);
 
-	return dbus_value;
+		if (   ignore_default
+		    && g_param_value_defaults (property->param_spec, &prop_value))
+			return NULL;
+
+		if (property->gprop_to_dbus_fcn) {
+			variant = property->gprop_to_dbus_fcn (&prop_value);
+			nm_g_variant_take_ref (variant);
+		} else
+			variant = g_dbus_gvalue_to_gvariant (&prop_value, property->dbus_type);
+	}
+
+	nm_assert (!variant || !g_variant_is_floating (variant));
+	nm_assert (!variant || g_variant_is_of_type (variant, property->dbus_type));
+
+	return variant;
 }
 
 static gboolean
@@ -683,13 +761,13 @@ set_property_from_dbus (const NMSettInfoProperty *property,
                         GVariant *src_value,
                         GValue *dst_value)
 {
-	g_return_val_if_fail (property->param_spec != NULL, FALSE);
+	nm_assert (property->param_spec);
+	nm_assert (property->dbus_type);
 
-	if (property->from_dbus) {
+	if (property->gprop_from_dbus_fcn) {
 		if (!g_variant_type_equal (g_variant_get_type (src_value), property->dbus_type))
 			return FALSE;
-
-		property->from_dbus (src_value, dst_value);
+		property->gprop_from_dbus_fcn (src_value, dst_value);
 	} else if (dst_value->g_type == G_TYPE_BYTES) {
 		if (!g_variant_is_of_type (src_value, G_VARIANT_TYPE_BYTESTRING))
 			return FALSE;
@@ -719,6 +797,8 @@ set_property_from_dbus (const NMSettInfoProperty *property,
  * @setting: the #NMSetting
  * @connection: the #NMConnection containing @setting
  * @flags: hash flags, e.g. %NM_CONNECTION_SERIALIZE_ALL
+ * @options: the #NMConnectionSerializationOptions options to control
+ *   what/how gets serialized.
  *
  * Converts the #NMSetting into a #GVariant of type #NM_VARIANT_TYPE_SETTING
  * mapping each setting property name to a value describing that property,
@@ -728,11 +808,13 @@ set_property_from_dbus (const NMSettInfoProperty *property,
  * properties
  **/
 GVariant *
-_nm_setting_to_dbus (NMSetting *setting, NMConnection *connection, NMConnectionSerializationFlags flags)
+_nm_setting_to_dbus (NMSetting *setting,
+                     NMConnection *connection,
+                     NMConnectionSerializationFlags flags,
+                     const NMConnectionSerializationOptions *options)
 {
 	NMSettingPrivate *priv;
 	GVariantBuilder builder;
-	GVariant *dbus_value;
 	const NMSettInfoSetting *sett_info;
 	guint n_properties, i;
 	const char *const*gendata_keys;
@@ -753,49 +835,14 @@ _nm_setting_to_dbus (NMSetting *setting, NMConnection *connection, NMConnectionS
 
 	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
 	for (i = 0; i < sett_info->property_infos_len; i++) {
-		const NMSettInfoProperty *property = &sett_info->property_infos[i];
-		GParamSpec *prop_spec = property->param_spec;
-
-		if (!prop_spec) {
-			if (!property->synth_func)
-				continue;
-		} else {
-
-			/* For the moment, properties backed by a GObject property don't
-			 * define a synth function. There is no problem supporting that,
-			 * however, for now just disallow it. */
-			nm_assert (!property->synth_func);
-
-			if (!(prop_spec->flags & G_PARAM_WRITABLE))
-				continue;
-
-			if (NM_FLAGS_ANY (prop_spec->flags, NM_SETTING_PARAM_GENDATA_BACKED))
-				continue;
-
-			if (   (prop_spec->flags & NM_SETTING_PARAM_LEGACY)
-			    && !_nm_utils_is_manager_process)
-				continue;
-
-			if (   (flags & NM_CONNECTION_SERIALIZE_NO_SECRETS)
-			    && (prop_spec->flags & NM_SETTING_PARAM_SECRET))
-				continue;
-
-			if (   (flags & NM_CONNECTION_SERIALIZE_ONLY_SECRETS)
-			    && !(prop_spec->flags & NM_SETTING_PARAM_SECRET))
-				continue;
-		}
-
-		if (property->synth_func)
-			dbus_value = property->synth_func (sett_info, i, connection, setting, flags);
-		else
-			dbus_value = get_property_for_dbus (setting, property, TRUE);
+		gs_unref_variant GVariant *dbus_value = NULL;
 
+		dbus_value = property_to_dbus (sett_info, i, connection, setting, flags, options, FALSE, TRUE);
 		if (dbus_value) {
-			/* Allow dbus_value to be either floating or not. */
-			g_variant_take_ref (dbus_value);
-
-			g_variant_builder_add (&builder, "{sv}", property->name, dbus_value);
-			g_variant_unref (dbus_value);
+			g_variant_builder_add (&builder,
+			                       "{sv}",
+			                       sett_info->property_infos[i].name,
+			                       dbus_value);
 		}
 	}
 
@@ -829,10 +876,9 @@ _nm_setting_new_from_dbus (GType setting_type,
                            NMSettingParseFlags parse_flags,
                            GError **error)
 {
+	gs_unref_ptrarray GPtrArray *keys_keep_variant = NULL;
 	gs_unref_object NMSetting *setting = NULL;
 	gs_unref_hashtable GHashTable *keys = NULL;
-	const NMSettInfoSetting *sett_info;
-	guint i;
 
 	g_return_val_if_fail (G_TYPE_IS_INSTANTIATABLE (setting_type), NULL);
 	g_return_val_if_fail (g_variant_is_of_type (setting_dict, NM_VARIANT_TYPE_SETTING), NULL);
@@ -856,18 +902,19 @@ _nm_setting_new_from_dbus (GType setting_type,
 	if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
 		GVariantIter iter;
 		GVariant *entry, *entry_key;
-		char *key;
+		const char *key;
 
-		keys = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
+		keys_keep_variant = g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_unref);
+		keys = g_hash_table_new (nm_str_hash, g_str_equal);
 
 		g_variant_iter_init (&iter, setting_dict);
 		while ((entry = g_variant_iter_next_value (&iter))) {
 			entry_key = g_variant_get_child_value (entry, 0);
-			key = g_strdup (g_variant_get_string (entry_key, NULL));
-			g_variant_unref (entry_key);
+			g_ptr_array_add (keys_keep_variant, entry_key);
 			g_variant_unref (entry);
 
-			if (!g_hash_table_add (keys, key)) {
+			key = g_variant_get_string (entry_key, NULL);
+			if (!g_hash_table_add (keys, (char *) key)) {
 				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
 				             _("duplicate property"));
 				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), key);
@@ -876,6 +923,47 @@ _nm_setting_new_from_dbus (GType setting_type,
 		}
 	}
 
+	if (!NM_SETTING_GET_CLASS (setting)->init_from_dbus (setting,
+	                                                     keys,
+	                                                     setting_dict,
+	                                                     connection_dict,
+	                                                     parse_flags,
+	                                                     error))
+		return NULL;
+
+	if (   NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)
+	    && g_hash_table_size (keys) > 0) {
+		GHashTableIter iter;
+		const char *key;
+
+		g_hash_table_iter_init (&iter, keys);
+		if (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) {
+			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("unknown property"));
+			g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), key);
+			return NULL;
+		}
+	}
+
+	return g_steal_pointer (&setting);
+}
+
+static gboolean
+init_from_dbus (NMSetting *setting,
+                GHashTable *keys,
+                GVariant *setting_dict,
+                GVariant *connection_dict,
+                guint /* NMSettingParseFlags */ parse_flags,
+                GError **error)
+{
+	const NMSettInfoSetting *sett_info;
+
+	guint i;
+
+	nm_assert (NM_IS_SETTING (setting));
+	nm_assert (!NM_FLAGS_ANY (parse_flags, ~NM_SETTING_PARSE_FLAGS_ALL));
+	nm_assert (!NM_FLAGS_ALL (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT | NM_SETTING_PARSE_FLAGS_BEST_EFFORT));
+
 	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
 
 	if (sett_info->detail.gendata_info) {
@@ -891,10 +979,12 @@ _nm_setting_new_from_dbus (GType setting_type,
 			g_hash_table_insert (hash,
 			                     key,
 			                     val);
+			if (keys)
+				g_hash_table_remove (keys, key);
 		}
 
 		_nm_setting_gendata_notify (setting, TRUE);
-		return g_steal_pointer (&setting);
+		return TRUE;
 	}
 
 	for (i = 0; i < sett_info->property_infos_len; i++) {
@@ -908,11 +998,12 @@ _nm_setting_new_from_dbus (GType setting_type,
 
 		value = g_variant_lookup_value (setting_dict, property_info->name, NULL);
 
-		if (value && keys)
+		if (   value
+		    && keys)
 			g_hash_table_remove (keys, property_info->name);
 
 		if (   value
-		    && property_info->set_func) {
+		    && property_info->from_dbus_fcn) {
 
 			if (!g_variant_type_equal (g_variant_get_type (value), property_info->dbus_type)) {
 				/* for backward behavior, fail unless best-effort is chosen. */
@@ -926,37 +1017,37 @@ _nm_setting_new_from_dbus (GType setting_type,
 				                     g_type_name (property_info->param_spec->value_type) : "(unknown)",
 				             g_variant_get_type_string (value));
 				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
-				return NULL;
+				return FALSE;
 			}
 
-			if (!property_info->set_func (setting,
-			                             connection_dict,
-			                             property_info->name,
-			                             value,
-			                             parse_flags,
-			                             &local)) {
+			if (!property_info->from_dbus_fcn (setting,
+			                                   connection_dict,
+			                                   property_info->name,
+			                                   value,
+			                                   parse_flags,
+			                                   &local)) {
 				if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
 					continue;
 				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
 				             _("failed to set property: %s"),
 				             local->message);
 				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
-				return NULL;
+				return FALSE;
 			}
 		} else if (   !value
-		           && property_info->not_set_func) {
-			if (!property_info->not_set_func (setting,
-			                                  connection_dict,
-			                                  property_info->name,
-			                                  parse_flags,
-			                                  &local)) {
+		           && property_info->missing_from_dbus_fcn) {
+			if (!property_info->missing_from_dbus_fcn (setting,
+			                                           connection_dict,
+			                                           property_info->name,
+			                                           parse_flags,
+			                                           &local)) {
 				if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
 					continue;
 				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
 				             _("failed to set property: %s"),
 				             local->message);
 				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
-				return NULL;
+				return FALSE;
 			}
 		} else if (   value
 		           && property_info->param_spec) {
@@ -976,7 +1067,7 @@ _nm_setting_new_from_dbus (GType setting_type,
 				                : "(unknown)"),
 				             g_variant_get_type_string (value));
 				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
-				return NULL;
+				return FALSE;
 			}
 
 			if (!nm_g_object_set_property (G_OBJECT (setting), property_info->param_spec->name, &object_value, &local)) {
@@ -986,26 +1077,12 @@ _nm_setting_new_from_dbus (GType setting_type,
 				             _("can not set property: %s"),
 				             local->message);
 				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
-				return NULL;
+				return FALSE;
 			}
 		}
 	}
 
-	if (   NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)
-	    && g_hash_table_size (keys) > 0) {
-		GHashTableIter iter;
-		const char *key;
-
-		g_hash_table_iter_init (&iter, keys);
-		if (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) {
-			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			             _("unknown property"));
-			g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), key);
-			return NULL;
-		}
-	}
-
-	return g_steal_pointer (&setting);
+	return TRUE;
 }
 
 /**
@@ -1028,12 +1105,12 @@ nm_setting_get_dbus_property_type (NMSetting *setting,
 	g_return_val_if_fail (property_name != NULL, NULL);
 
 	property = _nm_setting_class_get_property_info (NM_SETTING_GET_CLASS (setting), property_name);
+
 	g_return_val_if_fail (property != NULL, NULL);
 
-	if (property->dbus_type)
-		return property->dbus_type;
-	else
-		return variant_type_for_gtype (property->param_spec->value_type);
+	nm_assert (property->dbus_type);
+
+	return property->dbus_type;
 }
 
 gboolean
@@ -1342,8 +1419,10 @@ _nm_setting_should_compare_secret_property (NMSetting *setting,
 static NMTernary
 compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
-                  NMSetting *setting,
-                  NMSetting *other,
+                  NMConnection *con_a,
+                  NMSetting *set_a,
+                  NMConnection *con_b,
+                  NMSetting *set_b,
                   NMSettingCompareFlags flags)
 {
 	const NMSettInfoProperty *property_info = &sett_info->property_infos[property_idx];
@@ -1372,19 +1451,18 @@ compare_property (const NMSettInfoSetting *sett_info,
 		return NM_TERNARY_DEFAULT;
 
 	if (   NM_FLAGS_HAS (param_spec->flags, NM_SETTING_PARAM_SECRET)
-	    && !_nm_setting_should_compare_secret_property (setting,
-	                                                    other,
+	    && !_nm_setting_should_compare_secret_property (set_a,
+	                                                    set_b,
 	                                                    param_spec->name,
 	                                                    flags))
 		return NM_TERNARY_DEFAULT;
 
-	if (other) {
+	if (set_b) {
 		gs_unref_variant GVariant *value1  = NULL;
 		gs_unref_variant GVariant *value2  = NULL;
 
-		value1 = get_property_for_dbus (setting, property_info, TRUE);
-		value2 = get_property_for_dbus (other, property_info, TRUE);
-
+		value1 = property_to_dbus (sett_info, property_idx, con_a, set_a, NM_CONNECTION_SERIALIZE_ALL, NULL, TRUE, TRUE);
+		value2 = property_to_dbus (sett_info, property_idx, con_b, set_b, NM_CONNECTION_SERIALIZE_ALL, NULL, TRUE, TRUE);
 		if (nm_property_compare (value1, value2) != 0)
 			return NM_TERNARY_FALSE;
 	}
@@ -1395,8 +1473,10 @@ compare_property (const NMSettInfoSetting *sett_info,
 static NMTernary
 _compare_property (const NMSettInfoSetting *sett_info,
                    guint property_idx,
-                   NMSetting *setting,
-                   NMSetting *other,
+                   NMConnection *con_a,
+                   NMSetting *set_a,
+                   NMConnection *con_b,
+                   NMSetting *set_b,
                    NMSettingCompareFlags flags)
 {
 	NMTernary compare_result;
@@ -1404,14 +1484,16 @@ _compare_property (const NMSettInfoSetting *sett_info,
 	nm_assert (sett_info);
 	nm_assert (NM_IS_SETTING_CLASS (sett_info->setting_class));
 	nm_assert (property_idx < sett_info->property_infos_len);
-	nm_assert (NM_SETTING_GET_CLASS (setting) == sett_info->setting_class);
-	nm_assert (!other || NM_SETTING_GET_CLASS (other) == sett_info->setting_class);
+	nm_assert (NM_SETTING_GET_CLASS (set_a) == sett_info->setting_class);
+	nm_assert (!set_b || NM_SETTING_GET_CLASS (set_b) == sett_info->setting_class);
 
-	compare_result = NM_SETTING_GET_CLASS (setting)->compare_property (sett_info,
-	                                                                   property_idx,
-	                                                                   setting,
-	                                                                   other,
-	                                                                   flags);
+	compare_result = NM_SETTING_GET_CLASS (set_a)->compare_property (sett_info,
+	                                                                 property_idx,
+	                                                                 con_a,
+	                                                                 set_a,
+	                                                                 con_b,
+	                                                                 set_b,
+	                                                                 flags);
 
 	nm_assert (NM_IN_SET (compare_result, NM_TERNARY_DEFAULT,
 	                                      NM_TERNARY_FALSE,
@@ -1443,12 +1525,25 @@ nm_setting_compare (NMSetting *a,
                     NMSetting *b,
                     NMSettingCompareFlags flags)
 {
+	return _nm_setting_compare (NULL, a, NULL, b, flags);
+}
+
+gboolean
+_nm_setting_compare (NMConnection *con_a,
+                     NMSetting *a,
+                     NMConnection *con_b,
+                     NMSetting *b,
+                     NMSettingCompareFlags flags)
+{
 	const NMSettInfoSetting *sett_info;
 	guint i;
 
 	g_return_val_if_fail (NM_IS_SETTING (a), FALSE);
 	g_return_val_if_fail (NM_IS_SETTING (b), FALSE);
 
+	nm_assert (!con_a || NM_IS_CONNECTION (con_a));
+	nm_assert (!con_b || NM_IS_CONNECTION (con_b));
+
 	/* First check that both have the same type */
 	if (G_OBJECT_TYPE (a) != G_OBJECT_TYPE (b))
 		return FALSE;
@@ -1466,7 +1561,7 @@ nm_setting_compare (NMSetting *a,
 	}
 
 	for (i = 0; i < sett_info->property_infos_len; i++) {
-		if (_compare_property (sett_info, i, a, b, flags) == NM_TERNARY_FALSE)
+		if (_compare_property (sett_info, i, con_a, a, con_b, b, flags) == NM_TERNARY_FALSE)
 			return FALSE;
 	}
 
@@ -1516,6 +1611,18 @@ nm_setting_diff (NMSetting *a,
                  gboolean invert_results,
                  GHashTable **results)
 {
+	return _nm_setting_diff (NULL, a, NULL, b, flags, invert_results, results);
+}
+
+gboolean
+_nm_setting_diff (NMConnection *con_a,
+                  NMSetting *a,
+                  NMConnection *con_b,
+                  NMSetting *b,
+                  NMSettingCompareFlags flags,
+                  gboolean invert_results,
+                  GHashTable **results)
+{
 	const NMSettInfoSetting *sett_info;
 	guint i;
 	NMSettingDiffResult a_result = NM_SETTING_DIFF_RESULT_IN_A;
@@ -1533,6 +1640,9 @@ nm_setting_diff (NMSetting *a,
 		g_return_val_if_fail (G_OBJECT_TYPE (a) == G_OBJECT_TYPE (b), FALSE);
 	}
 
+	nm_assert (!con_a || NM_IS_CONNECTION (con_a));
+	nm_assert (!con_b || NM_IS_CONNECTION (con_b));
+
 	if ((flags & (NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT)) ==
 	             (NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT)) {
 		/* conflicting flags: default to WITH_DEFAULT (clearing NO_DEFAULT). */
@@ -1609,7 +1719,7 @@ nm_setting_diff (NMSetting *a,
 			NMTernary compare_result;
 			GParamSpec *prop_spec;
 
-			compare_result = _compare_property (sett_info, i, a, b, flags);
+			compare_result = _compare_property (sett_info, i, con_a, a, con_b, b, flags);
 			if (compare_result == NM_TERNARY_DEFAULT)
 				continue;
 
@@ -1630,7 +1740,7 @@ nm_setting_diff (NMSetting *a,
 				 *
 				 * We need to double-check whether the property should be ignored by
 				 * looking at @a alone. */
-				if (_compare_property (sett_info, i, a, NULL, flags) == NM_TERNARY_DEFAULT)
+				if (_compare_property (sett_info, i, con_a, a, NULL, NULL, flags) == NM_TERNARY_DEFAULT)
 					continue;
 			}
 
@@ -2278,7 +2388,7 @@ nm_setting_to_string (NMSetting *setting)
 	string = g_string_new (nm_setting_get_name (setting));
 	g_string_append_c (string, '\n');
 
-	variant = _nm_setting_to_dbus (setting, NULL, NM_CONNECTION_SERIALIZE_ALL);
+	variant = _nm_setting_to_dbus (setting, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL);
 
 	g_variant_iter_init (&iter, variant);
 	while ((child = g_variant_iter_next_value (&iter))) {
@@ -2300,7 +2410,8 @@ _nm_setting_get_deprecated_virtual_interface_name (const NMSettInfoSetting *sett
                                                    guint property_idx,
                                                    NMConnection *connection,
                                                    NMSetting *setting,
-                                                   NMConnectionSerializationFlags flags)
+                                                   NMConnectionSerializationFlags flags,
+                                                   const NMConnectionSerializationOptions *options)
 {
 	NMSettingConnection *s_con;
 
@@ -2612,21 +2723,6 @@ static void
 nm_setting_class_init (NMSettingClass *setting_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (setting_class);
-	GModule *self_module;
-	gpointer func;
-
-	/* loading libnm and legacy libraries libnm-util/libnm-glib at the same
-	 * time is not supported. The reason is, that both libraries use the same
-	 * glib type names ("NMSetting"), and glib does not support namespacing
-	 * to allow for that.
-	 *
-	 * Arbitrarily, add a check here, see whether a known symbol from libnm-util
-	 * is present. If it is, it indicates that the process is borked and we
-	 * abort. */
-	self_module = g_module_open (NULL, 0);
-	if (g_module_symbol (self_module, "nm_util_get_private", &func))
-		g_error ("libnm-util symbols detected; Mixing libnm with libnm-util/libnm-glib is not supported");
-	g_module_close (self_module);
 
 	g_type_class_add_private (setting_class, sizeof (NMSettingPrivate));
 
@@ -2642,6 +2738,7 @@ nm_setting_class_init (NMSettingClass *setting_class)
 	setting_class->duplicate_copy_properties = duplicate_copy_properties;
 	setting_class->enumerate_values          = enumerate_values;
 	setting_class->aggregate                 = aggregate;
+	setting_class->init_from_dbus            = init_from_dbus;
 
 	/**
 	 * NMSetting:name:
diff --git a/libnm-core/nm-setting.h b/libnm-core/nm-setting.h
index fdf4a4c5..34586cce 100644
--- a/libnm-core/nm-setting.h
+++ b/libnm-core/nm-setting.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -236,8 +234,10 @@ typedef struct {
 	/*< private >*/
 	NMTernary  (*compare_property)  (const struct _NMSettInfoSetting *sett_info,
 	                                 guint property_idx,
-	                                 NMSetting *setting,
-	                                 NMSetting *other,
+	                                 NMConnection *con_a,
+	                                 NMSetting *set_a,
+	                                 NMConnection *con_b,
+	                                 NMSetting *set_b,
 	                                 NMSettingCompareFlags flags);
 
 	/*< private >*/
@@ -266,7 +266,15 @@ typedef struct {
 	                         GVariantBuilder *setting_builder);
 
 	/*< private >*/
-	gpointer padding[2];
+	gboolean (*init_from_dbus) (NMSetting *setting,
+	                            GHashTable *keys,
+	                            GVariant *setting_dict,
+	                            GVariant *connection_dict,
+	                            guint /* NMSettingParseFlags */ parse_flags,
+	                            GError **error);
+
+	/*< private >*/
+	gpointer padding[1];
 
 	/*< private >*/
 	const struct _NMMetaSettingInfo *setting_info;
diff --git a/libnm-core/nm-simple-connection.c b/libnm-core/nm-simple-connection.c
index 55e8229f..f58f145f 100644
--- a/libnm-core/nm-simple-connection.c
+++ b/libnm-core/nm-simple-connection.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -142,6 +141,10 @@ nm_simple_connection_new_clone (NMConnection *connection)
 static void
 dispose (GObject *object)
 {
+#if NM_MORE_ASSERTS
+	g_signal_handlers_disconnect_by_data (object, (gpointer) &_nmtst_connection_unchanging_user_data);
+#endif
+
 	nm_connection_clear_secrets (NM_CONNECTION (object));
 
 	G_OBJECT_CLASS (nm_simple_connection_parent_class)->dispose (object);
diff --git a/libnm-core/nm-simple-connection.h b/libnm-core/nm-simple-connection.h
index 4166d4ca..65c12fad 100644
--- a/libnm-core/nm-simple-connection.h
+++ b/libnm-core/nm-simple-connection.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-team-utils.c b/libnm-core/nm-team-utils.c
new file mode 100644
index 00000000..ac834d74
--- /dev/null
+++ b/libnm-core/nm-team-utils.c
@@ -0,0 +1,2542 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2019 Red Hat, Inc.
+ */
+
+#define NM_VALUE_TYPE_DEFINE_FUNCTIONS
+
+#include "nm-default.h"
+
+#include "nm-team-utils.h"
+
+#include "nm-errors.h"
+#include "nm-utils-private.h"
+#include "nm-json.h"
+#include "nm-glib-aux/nm-json-aux.h"
+#include "nm-core-internal.h"
+#include "nm-setting-team.h"
+#include "nm-setting-team-port.h"
+
+/*****************************************************************************/
+
+typedef enum {
+	SET_FIELD_MODE_UNSET              = 0,
+	SET_FIELD_MODE_SET                = 1,
+
+	/* Sets the field as set, unless the field is at the default.
+	 * This is the case for API that is called from NMSettingTeam/NMSettingTeamPort.
+	 * This means, using libnm API to reset the value of a NMSetting to the default,
+	 * will mark the field as unset.
+	 * This is different from initializing the field when parsing JSON/GVariant. In
+	 * that case an explicitly set field (even set to the default value) will be remembered
+	 * to be set. */
+	SET_FIELD_MODE_SET_UNLESS_DEFAULT = 2,
+} SetFieldModeEnum;
+
+typedef enum {
+	RESET_JSON_NO  = FALSE,
+	RESET_JSON_YES = TRUE,
+} ResetJsonEnum;
+
+/* we rely on "config" being the first. At various places we iterate over attribute types,
+ * starting after "config".*/
+G_STATIC_ASSERT (_NM_TEAM_ATTRIBUTE_0     == 0);
+G_STATIC_ASSERT (NM_TEAM_ATTRIBUTE_CONFIG == 1);
+
+static const char *const _valid_names_runner[] = {
+	NM_SETTING_TEAM_RUNNER_BROADCAST,
+	NM_SETTING_TEAM_RUNNER_ROUNDROBIN,
+	NM_SETTING_TEAM_RUNNER_RANDOM,
+	NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP,
+	NM_SETTING_TEAM_RUNNER_LOADBALANCE,
+	NM_SETTING_TEAM_RUNNER_LACP,
+	NULL,
+};
+
+static const char *const _valid_names_runner_hwaddr_policy[] = {
+	NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_SAME_ALL,
+	NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_BY_ACTIVE,
+	NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_ONLY_ACTIVE,
+	NULL,
+};
+
+static const char *const _valid_names_runner_tx_balancer[] = {
+	"basic",
+	NULL,
+};
+
+static const char *const _valid_names_runner_tx_hash[] = {
+	"eth",
+	"vlan",
+	"ipv4",
+	"ipv6",
+	"ip",
+	"l3",
+	"l4",
+	"tcp",
+	"udp",
+	"sctp",
+	NULL,
+};
+
+static const char *const _valid_names_runner_agg_select_policy[] = {
+	"lacp_prio",
+	"lacp_prio_stable",
+	"bandwidth",
+	"count",
+	"port_config",
+	NULL,
+};
+
+typedef struct {
+	NMTeamAttribute team_attr;
+	const char *const*valid_runners;
+} RunnerCompatElem;
+
+static const RunnerCompatElem _runner_compat_lst[] = {
+	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY,        NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP), },
+	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH,              NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LOADBALANCE,
+	                                                                      NM_SETTING_TEAM_RUNNER_LACP), },
+	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER,          NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LOADBALANCE,
+	                                                                      NM_SETTING_TEAM_RUNNER_LACP), },
+	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL, NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LOADBALANCE,
+	                                                                      NM_SETTING_TEAM_RUNNER_LACP), },
+	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE,               NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LACP), },
+	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE,            NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LACP), },
+	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO,             NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LACP), },
+	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS,            NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LACP), },
+	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY,    NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LACP), },
+};
+
+typedef struct {
+	const char *const*js_keys;
+	const char *property_name;
+	NMValueTypUnion default_val;
+	union {
+		struct {
+			gint32 min;
+			gint32 max;
+		} r_int32;
+		struct {
+			const char *const*valid_names;
+		} r_string;
+	} range;
+	NMTeamAttribute team_attr;
+	NMValueType value_type;
+	guint8 field_offset;
+	guint8 js_keys_len;
+	bool for_master:1;
+	bool for_port:1;
+	bool has_range:1;
+} TeamAttrData;
+
+#define TEAM_ATTR_IDX(_is_port, _team_attr) \
+	((  (!(_is_port) || (_team_attr) < _NM_TEAM_ATTRIBUTE_START) \
+	  ? (int) (_team_attr) \
+	  : (((int) (_NM_TEAM_ATTRIBUTE_MASTER_NUM - _NM_TEAM_ATTRIBUTE_START)) + ((int) (_team_attr)))) - 1)
+
+#define TEAM_ATTR_IDX_CONFIG (TEAM_ATTR_IDX (FALSE, NM_TEAM_ATTRIBUTE_CONFIG))
+
+static const TeamAttrData team_attr_datas[] = {
+
+#define _JS_KEYS(...) \
+		.js_keys = NM_MAKE_STRV (__VA_ARGS__), \
+		.js_keys_len = NM_NARG (__VA_ARGS__)
+
+#define _VAL_BOOL(_default) \
+		.default_val.v_bool = (_default)
+
+#define _VAL_INT32(_default) \
+		.default_val.v_int32 = (_default)
+
+#define _VAL_INT32_RANGE(_default, _min,_max) \
+		_VAL_INT32 (_default), \
+		.has_range = TRUE, \
+		.range.r_int32 = { .min = _min, .max = _max, }
+
+#define _VAL_STRING() \
+		.default_val.v_string = NULL
+
+#define _VAL_STRING_RANGE(_valid_names) \
+		_VAL_STRING (), \
+		.has_range = TRUE, \
+		.range.r_string = { .valid_names = (_valid_names), }
+
+#define _VAL_UNSPEC() \
+		.default_val.v_string = (NULL)
+
+#define _INIT(_is_port, _team_attr, field, _value_type, _property_name, ...) \
+	[TEAM_ATTR_IDX (_is_port, _team_attr)] = { \
+		.for_master    = (_team_attr) < _NM_TEAM_ATTRIBUTE_START || !(_is_port), \
+		.for_port      = (_team_attr) < _NM_TEAM_ATTRIBUTE_START ||  (_is_port), \
+		.team_attr     = (_team_attr), \
+		.field_offset  = G_STRUCT_OFFSET (NMTeamSetting, _data_priv.field), \
+		.value_type    = (_value_type), \
+		.property_name = ""_property_name"", \
+		__VA_ARGS__ \
+	}
+
+	_INIT (0, NM_TEAM_ATTRIBUTE_CONFIG,                             _js_str,                            NM_VALUE_TYPE_UNSPEC, NM_SETTING_TEAM_CONFIG,                                                                                                                                           ),
+
+	_INIT (0, NM_TEAM_ATTRIBUTE_LINK_WATCHERS,                      link_watchers,                      NM_VALUE_TYPE_UNSPEC, NM_SETTING_TEAM_LINK_WATCHERS,               _JS_KEYS ("link_watch"),                                  _VAL_UNSPEC (),                                            ),
+
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT,          master.notify_peers_count,          NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_NOTIFY_PEERS_COUNT,          _JS_KEYS ("notify_peers", "count"),                       _VAL_INT32_RANGE (-1, 0, G_MAXINT32),                      ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL,       master.notify_peers_interval,       NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL,       _JS_KEYS ("notify_peers", "interval"),                    _VAL_INT32_RANGE (-1, 0, G_MAXINT32),                      ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT,          master.mcast_rejoin_count,          NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_MCAST_REJOIN_COUNT,          _JS_KEYS ("mcast_rejoin", "count"),                       _VAL_INT32_RANGE (-1, 0, G_MAXINT32),                      ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL,       master.mcast_rejoin_interval,       NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL,       _JS_KEYS ("mcast_rejoin", "interval"),                    _VAL_INT32_RANGE (-1, 0, G_MAXINT32),                      ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER,                      master.runner,                      NM_VALUE_TYPE_STRING, NM_SETTING_TEAM_RUNNER,                      _JS_KEYS ("runner", "name"),                              _VAL_STRING_RANGE (_valid_names_runner),                   ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY,        master.runner_hwaddr_policy,        NM_VALUE_TYPE_STRING, NM_SETTING_TEAM_RUNNER_HWADDR_POLICY,        _JS_KEYS ("runner", "hwaddr_policy"),                     _VAL_STRING_RANGE (_valid_names_runner_hwaddr_policy),     ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH,              master.runner_tx_hash,              NM_VALUE_TYPE_UNSPEC, NM_SETTING_TEAM_RUNNER_TX_HASH,              _JS_KEYS ("runner", "tx_hash"),                           _VAL_UNSPEC (),                                            ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER,          master.runner_tx_balancer,          NM_VALUE_TYPE_STRING, NM_SETTING_TEAM_RUNNER_TX_BALANCER,          _JS_KEYS ("runner", "tx_balancer", "name"),               _VAL_STRING_RANGE (_valid_names_runner_tx_balancer),       ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL, master.runner_tx_balancer_interval, NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL, _JS_KEYS ("runner", "tx_balancer", "balancing_interval"), _VAL_INT32_RANGE (-1, 0, G_MAXINT32),                      ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE,               master.runner_active,               NM_VALUE_TYPE_BOOL,   NM_SETTING_TEAM_RUNNER_ACTIVE,               _JS_KEYS ("runner", "active"),                            _VAL_BOOL (TRUE),                                          ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE,            master.runner_fast_rate,            NM_VALUE_TYPE_BOOL,   NM_SETTING_TEAM_RUNNER_FAST_RATE,            _JS_KEYS ("runner", "fast_rate"),                         _VAL_BOOL (FALSE),                                         ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO,             master.runner_sys_prio,             NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_RUNNER_SYS_PRIO,             _JS_KEYS ("runner", "sys_prio"),                          _VAL_INT32_RANGE (-1, 0, USHRT_MAX + 1),                   ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS,            master.runner_min_ports,            NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_RUNNER_MIN_PORTS,            _JS_KEYS ("runner", "min_ports"),                         _VAL_INT32_RANGE (-1, 1, UCHAR_MAX + 1),                   ),
+	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY,    master.runner_agg_select_policy,    NM_VALUE_TYPE_STRING, NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY,    _JS_KEYS ("runner", "agg_select_policy"),                 _VAL_STRING_RANGE (_valid_names_runner_agg_select_policy), ),
+
+	_INIT (1, NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID,                      port.queue_id,                      NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_PORT_QUEUE_ID,               _JS_KEYS ("queue_id"),                                    _VAL_INT32_RANGE (-1, 0, G_MAXINT32),                      ),
+	_INIT (1, NM_TEAM_ATTRIBUTE_PORT_PRIO,                          port.prio,                          NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_PORT_PRIO,                   _JS_KEYS ("prio"),                                        _VAL_INT32 (0),                                            ),
+	_INIT (1, NM_TEAM_ATTRIBUTE_PORT_STICKY,                        port.sticky,                        NM_VALUE_TYPE_BOOL,   NM_SETTING_TEAM_PORT_STICKY,                 _JS_KEYS ("sticky"),                                      _VAL_BOOL (FALSE),                                         ),
+	_INIT (1, NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO,                     port.lacp_prio,                     NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_PORT_LACP_PRIO,              _JS_KEYS ("lacp_prio"),                                   _VAL_INT32_RANGE (-1, 0, USHRT_MAX + 1),                   ),
+	_INIT (1, NM_TEAM_ATTRIBUTE_PORT_LACP_KEY,                      port.lacp_key,                      NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_PORT_LACP_KEY,               _JS_KEYS ("lacp_key"),                                    _VAL_INT32_RANGE (-1, 0, USHRT_MAX + 1),                   ),
+
+#undef _INIT
+};
+
+/*****************************************************************************/
+
+typedef enum {
+	LINK_WATCHER_ATTRIBUTE_NAME,
+	LINK_WATCHER_ATTRIBUTE_DELAY_UP,
+	LINK_WATCHER_ATTRIBUTE_DELAY_DOWN,
+	LINK_WATCHER_ATTRIBUTE_INTERVAL,
+	LINK_WATCHER_ATTRIBUTE_INIT_WAIT,
+	LINK_WATCHER_ATTRIBUTE_MISSED_MAX,
+	LINK_WATCHER_ATTRIBUTE_SOURCE_HOST,
+	LINK_WATCHER_ATTRIBUTE_TARGET_HOST,
+	LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE,
+	LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE,
+	LINK_WATCHER_ATTRIBUTE_VLANID,
+	LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS,
+} LinkWatcherAttribute;
+
+#define _EXPECTED_LINK_WATCHER_ATTRIBUTES_ETHTOOL    LINK_WATCHER_ATTRIBUTE_NAME, \
+                                                     LINK_WATCHER_ATTRIBUTE_DELAY_UP, \
+                                                     LINK_WATCHER_ATTRIBUTE_DELAY_DOWN
+#define _EXPECTED_LINK_WATCHER_ATTRIBUTES_NSNA_PING  LINK_WATCHER_ATTRIBUTE_NAME, \
+                                                     LINK_WATCHER_ATTRIBUTE_INTERVAL, \
+                                                     LINK_WATCHER_ATTRIBUTE_INIT_WAIT, \
+                                                     LINK_WATCHER_ATTRIBUTE_MISSED_MAX, \
+                                                     LINK_WATCHER_ATTRIBUTE_TARGET_HOST
+#define _EXPECTED_LINK_WATCHER_ATTRIBUTES_ARP_PING   LINK_WATCHER_ATTRIBUTE_NAME, \
+                                                     LINK_WATCHER_ATTRIBUTE_INTERVAL, \
+                                                     LINK_WATCHER_ATTRIBUTE_INIT_WAIT, \
+                                                     LINK_WATCHER_ATTRIBUTE_MISSED_MAX, \
+                                                     LINK_WATCHER_ATTRIBUTE_SOURCE_HOST, \
+                                                     LINK_WATCHER_ATTRIBUTE_TARGET_HOST, \
+                                                     LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE, \
+                                                     LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, \
+                                                     LINK_WATCHER_ATTRIBUTE_VLANID, \
+                                                     LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS
+
+typedef struct {
+	const char *js_key;
+	const char *dbus_name;
+	NMValueTypUnion default_val;
+	LinkWatcherAttribute link_watcher_attr;
+	NMValueType value_type;
+} LinkWatcherAttrData;
+
+static const LinkWatcherAttrData link_watcher_attr_datas[] = {
+#define _INIT(_link_watcher_attr, _js_key, _dbus_name, _value_type, ...) \
+	[_link_watcher_attr] = { \
+		.link_watcher_attr = (_link_watcher_attr), \
+		.value_type = (_value_type), \
+		.js_key = (""_js_key""), \
+		.dbus_name = (""_dbus_name""), \
+		__VA_ARGS__ \
+	}
+	_INIT (LINK_WATCHER_ATTRIBUTE_NAME,              "name",              "name",              NM_VALUE_TYPE_STRING,                          ),
+	_INIT (LINK_WATCHER_ATTRIBUTE_DELAY_UP,          "delay_up",          "delay-up",          NM_VALUE_TYPE_INT,                             ),
+	_INIT (LINK_WATCHER_ATTRIBUTE_DELAY_DOWN,        "delay_down",        "delay-down",        NM_VALUE_TYPE_INT,                             ),
+	_INIT (LINK_WATCHER_ATTRIBUTE_INTERVAL,          "interval",          "interval",          NM_VALUE_TYPE_INT,                             ),
+	_INIT (LINK_WATCHER_ATTRIBUTE_INIT_WAIT,         "init_wait",         "init-wait",         NM_VALUE_TYPE_INT,                             ),
+	_INIT (LINK_WATCHER_ATTRIBUTE_MISSED_MAX,        "missed_max",        "missed-max",        NM_VALUE_TYPE_INT,    .default_val.v_int =  3, ),
+	_INIT (LINK_WATCHER_ATTRIBUTE_SOURCE_HOST,       "source_host",       "source-host",       NM_VALUE_TYPE_STRING,                          ),
+	_INIT (LINK_WATCHER_ATTRIBUTE_TARGET_HOST,       "target_host",       "target-host",       NM_VALUE_TYPE_STRING,                          ),
+	_INIT (LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE,   "validate_active",   "validate-active",   NM_VALUE_TYPE_BOOL,                            ),
+	_INIT (LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, "validate_inactive", "validate-inactive", NM_VALUE_TYPE_BOOL,                            ),
+	_INIT (LINK_WATCHER_ATTRIBUTE_VLANID,            "vlanid",            "vlanid",            NM_VALUE_TYPE_INT,    .default_val.v_int = -1, ),
+	_INIT (LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS,       "send_always",       "send-always",       NM_VALUE_TYPE_BOOL,                            ),
+#undef _INIT
+};
+
+/*****************************************************************************/
+
+static const TeamAttrData *_team_attr_data_get (gboolean is_port,
+                                                NMTeamAttribute team_attr);
+static gpointer _team_setting_get_field (const NMTeamSetting *self,
+                                         const TeamAttrData *attr_data);
+static void _link_watcher_to_json (const NMTeamLinkWatcher *link_watcher,
+                                   GString *gstr);
+
+/*****************************************************************************/
+
+static void
+_team_attr_data_ASSERT (const TeamAttrData *attr_data)
+{
+#if NM_MORE_ASSERTS > 5
+	nm_assert (attr_data);
+	if (attr_data->for_port)
+		nm_assert (attr_data == _team_attr_data_get (TRUE, attr_data->team_attr));
+	if (attr_data->for_master)
+		nm_assert (attr_data == _team_attr_data_get (FALSE, attr_data->team_attr));
+	nm_assert ((attr_data - team_attr_datas) == TEAM_ATTR_IDX (attr_data->for_port, attr_data->team_attr));
+	nm_assert (attr_data->value_type > 0);
+	nm_assert (attr_data->field_offset < sizeof (NMTeamSetting));
+	nm_assert (attr_data->js_keys_len == NM_PTRARRAY_LEN (attr_data->js_keys));
+	nm_assert (attr_data->property_name);
+	{
+		static int checked = 0;
+
+		if (checked == 0) {
+			checked = 1;
+
+			for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++)
+				_team_attr_data_ASSERT (attr_data);
+		}
+	}
+#endif
+}
+
+static gboolean
+_team_attr_data_is_relevant (const TeamAttrData *attr_data,
+                             gboolean is_port)
+{
+	return   is_port
+	       ? attr_data->for_port
+	       : attr_data->for_master;
+}
+
+static const TeamAttrData *
+_team_attr_data_get (gboolean is_port,
+                     NMTeamAttribute team_attr)
+{
+	const int idx = TEAM_ATTR_IDX (is_port, team_attr);
+
+	nm_assert (   idx >= 0
+	           && idx < G_N_ELEMENTS (team_attr_datas));
+	nm_assert (team_attr_datas[idx].team_attr == team_attr);
+	nm_assert (_team_attr_data_is_relevant (&team_attr_datas[idx], is_port));
+
+	return &team_attr_datas[idx];
+}
+
+static const TeamAttrData *
+_team_attr_data_find_for_property_name (gboolean is_port,
+                                        const char *property_name)
+{
+	const TeamAttrData *attr_data;
+
+	for (attr_data = team_attr_datas; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
+		if (   _team_attr_data_is_relevant (attr_data, is_port)
+		    && nm_streq (property_name, attr_data->property_name))
+			return attr_data;
+	}
+	return NULL;
+}
+
+static int
+_team_attr_data_cmp (const TeamAttrData *attr_data,
+                     gboolean is_port,
+                     gconstpointer val_a,
+                     gconstpointer val_b)
+{
+	const GPtrArray *v_ptrarray_a;
+	const GPtrArray *v_ptrarray_b;
+	guint len;
+
+	_team_attr_data_ASSERT (attr_data);
+	nm_assert (val_a);
+	nm_assert (val_b);
+
+	if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC)
+		NM_CMP_RETURN (nm_value_type_cmp (attr_data->value_type, val_a, val_b));
+	else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) {
+		v_ptrarray_a = *((const GPtrArray *const*) val_a);
+		v_ptrarray_b = *((const GPtrArray *const*) val_b);
+		len = v_ptrarray_a ? v_ptrarray_a->len : 0u;
+		NM_CMP_DIRECT (len, (v_ptrarray_b ? v_ptrarray_b->len : 0u));
+		if (len > 0) {
+			NM_CMP_RETURN (nm_team_link_watchers_cmp ((const NMTeamLinkWatcher *const*) v_ptrarray_a->pdata,
+		                                              (const NMTeamLinkWatcher *const*) v_ptrarray_b->pdata,
+		                                              len,
+		                                              FALSE));
+		}
+	} else if (   !is_port
+	           && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
+		v_ptrarray_a = *((const GPtrArray *const*) val_a);
+		v_ptrarray_b = *((const GPtrArray *const*) val_b);
+		NM_CMP_RETURN (_nm_utils_strv_cmp_n (v_ptrarray_a ? (const char *const*) v_ptrarray_a->pdata : NULL,
+		                                     v_ptrarray_a ? v_ptrarray_a->len : 0u,
+		                                     v_ptrarray_b ? (const char *const*) v_ptrarray_b->pdata : NULL,
+		                                     v_ptrarray_b ? v_ptrarray_b->len : 0u));
+	} else
+		nm_assert_not_reached ();
+	return 0;
+}
+
+static gboolean
+_team_attr_data_equal (const TeamAttrData *attr_data,
+                       gboolean is_port,
+                       gconstpointer val_a,
+                       gconstpointer val_b)
+{
+	return _team_attr_data_cmp (attr_data, is_port, val_a, val_b) == 0;
+}
+
+static void
+_team_attr_data_copy (const TeamAttrData *attr_data,
+                      gboolean is_port,
+                      gpointer dst,
+                      gconstpointer src)
+{
+	GPtrArray *v_ptrarray_dst;
+	const GPtrArray *v_ptrarray_src;
+	GPtrArray *dst_array;
+	guint i, len;
+
+	if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC)
+		nm_value_type_copy (attr_data->value_type, dst, src);
+	else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) {
+		v_ptrarray_src = *((const GPtrArray *const *) src);
+		v_ptrarray_dst = *((GPtrArray **) dst);
+		len = (v_ptrarray_src ? v_ptrarray_src->len : 0u);
+
+		if (len == 0) {
+			if (v_ptrarray_dst)
+				g_ptr_array_set_size (v_ptrarray_dst, 0);
+		} else {
+			dst_array = g_ptr_array_new_full (len, (GDestroyNotify) nm_team_link_watcher_unref);
+			for (i = 0; i < len; i++) {
+				if (v_ptrarray_src->pdata[i]) {
+					nm_team_link_watcher_ref (v_ptrarray_src->pdata[i]);
+					g_ptr_array_add (dst_array,v_ptrarray_src->pdata[i]);
+				}
+			}
+			if (v_ptrarray_dst)
+				g_ptr_array_unref (v_ptrarray_dst);
+			*((GPtrArray **) dst) = dst_array;
+		}
+	} else if (   !is_port
+	           && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
+		v_ptrarray_src = *((const GPtrArray *const *) src);
+		v_ptrarray_dst = *((GPtrArray **) dst);
+		len = (v_ptrarray_src ? v_ptrarray_src->len : 0u);
+
+		if (   v_ptrarray_src
+		    && v_ptrarray_src->len > 0) {
+			dst_array = g_ptr_array_new_full (v_ptrarray_src->len, g_free);
+			for (i = 0; i < v_ptrarray_src->len; i++)
+				g_ptr_array_add (dst_array, g_strdup (v_ptrarray_src->pdata[i]));
+		} else
+			dst_array = NULL;
+		if (v_ptrarray_dst)
+			g_ptr_array_unref (v_ptrarray_dst);
+		*((GPtrArray **) dst) = dst_array;
+	} else
+		nm_assert_not_reached ();
+}
+
+static void
+_team_attr_data_to_json (const TeamAttrData *attr_data,
+                         gboolean is_port,
+                         GString *gstr,
+                         gconstpointer p_field)
+{
+	guint i;
+
+	_team_attr_data_ASSERT (attr_data);
+	nm_assert (p_field);
+
+	nm_json_aux_gstr_append_obj_name (gstr,
+	                                  attr_data->js_keys[attr_data->js_keys_len - 1],
+	                                  '\0');
+
+	if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) {
+		nm_value_type_to_json (attr_data->value_type, gstr, p_field);
+		return;
+	}
+
+	if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) {
+		const GPtrArray *v_ptrarray = *((const GPtrArray *const*) p_field);
+
+		if (!v_ptrarray)
+			g_string_append (gstr, "null");
+		else if (v_ptrarray->len == 0)
+			g_string_append (gstr, "[ ]");
+		else if (v_ptrarray->len == 1)
+			_link_watcher_to_json (v_ptrarray->pdata[0], gstr);
+		else {
+			g_string_append (gstr, "[ ");
+			for (i = 0; i < v_ptrarray->len; i++) {
+				if (i > 0)
+					nm_json_aux_gstr_append_delimiter (gstr);
+				_link_watcher_to_json (v_ptrarray->pdata[i], gstr);
+			}
+			g_string_append (gstr, " ]");
+		}
+		return;
+	}
+
+	if (   !is_port
+	    && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
+		const GPtrArray *v_ptrarray = *((const GPtrArray *const*) p_field);
+
+		if (!v_ptrarray)
+			g_string_append (gstr, "null");
+		else {
+			g_string_append (gstr, "[ ");
+			for (i = 0; i < v_ptrarray->len; i++) {
+				if (i > 0)
+					nm_json_aux_gstr_append_delimiter (gstr);
+				nm_json_aux_gstr_append_string (gstr, v_ptrarray->pdata[i]);
+			}
+			g_string_append (gstr, i > 0 ? " ]" : "]");
+		}
+		return;
+	}
+
+	nm_assert_not_reached ();
+}
+
+/*****************************************************************************/
+
+static void
+_team_setting_ASSERT (const NMTeamSetting *self)
+{
+	nm_assert (self);
+	nm_assert (!self->d._js_str_need_synthetize || !self->d._js_str);
+#if NM_MORE_ASSERTS > 2
+	if (!self->d.strict_validated) {
+		nm_assert (!self->d._js_str_need_synthetize);
+		nm_assert (self->d._js_str);
+	}
+	nm_assert (self->d.link_watchers);
+	nm_assert (   self->d.is_port
+	           || !self->d.master.runner_tx_hash
+	           || self->d.master.runner_tx_hash->len > 0);
+#endif
+}
+
+static gboolean
+_team_setting_has_field (const NMTeamSetting *self,
+                         const TeamAttrData *attr_data)
+{
+	_team_setting_ASSERT (self);
+	return NM_FLAGS_ALL (self->d.has_fields_mask, nm_team_attribute_to_flags (attr_data->team_attr));
+}
+
+static gboolean
+_team_setting_has_fields_any_v (const NMTeamSetting *self,
+                                const NMTeamAttribute *team_attrs,
+                                gsize n_team_attrs)
+{
+	gsize i;
+
+	for (i = 0; i < n_team_attrs; i++) {
+		const TeamAttrData *attr_data = _team_attr_data_get (self->d.is_port, team_attrs[i]);
+
+		if (_team_setting_has_field (self, attr_data))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+#define _team_setting_has_fields_any(self, ...) \
+   _team_setting_has_fields_any_v ((self), ((const NMTeamAttribute []) { __VA_ARGS__ }), NM_NARG (__VA_ARGS__))
+
+static void
+_team_setting_has_field_set (NMTeamSetting *self,
+                             const TeamAttrData *attr_data,
+                             SetFieldModeEnum set_field_mode)
+{
+	guint32 mask = nm_team_attribute_to_flags (attr_data->team_attr);
+
+	_team_setting_ASSERT (self);
+
+	switch (set_field_mode) {
+	case SET_FIELD_MODE_UNSET:
+		goto do_unset;
+	case SET_FIELD_MODE_SET:
+		goto do_set;
+	case SET_FIELD_MODE_SET_UNLESS_DEFAULT:
+		if (_team_attr_data_equal (attr_data,
+		                           self->d.is_port,
+		                           _team_setting_get_field (self, attr_data),
+		                           &attr_data->default_val))
+			goto do_unset;
+		goto do_set;
+	}
+	nm_assert_not_reached ();
+
+do_unset:
+	self->_data_priv.has_fields_mask &= ~mask;
+	return;
+do_set:
+	self->_data_priv.has_fields_mask |= mask;
+}
+
+static gpointer
+_team_setting_get_field (const NMTeamSetting *self,
+                         const TeamAttrData *attr_data)
+{
+	_team_setting_ASSERT (self);
+	_team_attr_data_ASSERT (attr_data);
+	nm_assert (_team_attr_data_is_relevant (attr_data, self->d.is_port));
+
+#if NM_MORE_ASSERTS > 5
+	if (   attr_data->for_master
+	    && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO )
+		nm_assert ((gpointer) (((char *) self) + attr_data->field_offset) == &self->d.master.runner_sys_prio);
+#endif
+
+	return (((char *) self) + attr_data->field_offset);
+}
+
+static guint32
+_team_setting_attribute_changed (NMTeamSetting *self,
+                                 const TeamAttrData *attr_data,
+                                 gboolean changed,
+                                 SetFieldModeEnum set_field_mode,
+                                 ResetJsonEnum reset_json)
+{
+	guint32 changed_flags;
+
+	_team_setting_has_field_set (self, attr_data, set_field_mode);
+
+	if (!reset_json) {
+		return   changed
+		       ? nm_team_attribute_to_flags (attr_data->team_attr)
+		       : 0u;
+	}
+
+	if (!changed) {
+		/* a regular attribute was set, but the value did not change.
+		 *
+		 * If we previously were in non-strict mode, then
+		 *
+		 * - switch to strict-mode. Clearly the user set a regular attribute
+		 *   and hence now we want to validate the setting.
+		 *
+		 * - clear the JSON string. We need to regenerate it.
+		 */
+		if (self->_data_priv.strict_validated)
+			return 0;
+		changed_flags = nm_team_attribute_to_flags (NM_TEAM_ATTRIBUTE_CONFIG);
+	} else {
+		changed_flags =   nm_team_attribute_to_flags (attr_data->team_attr)
+		                | nm_team_attribute_to_flags (NM_TEAM_ATTRIBUTE_CONFIG);
+	}
+
+	nm_clear_g_free ((char **) &self->_data_priv._js_str);
+	self->_data_priv.strict_validated = TRUE;
+	self->_data_priv._js_str_need_synthetize = TRUE;
+
+	return changed_flags;
+}
+
+static guint32
+_team_setting_attribute_changed_attr (NMTeamSetting *self,
+                                      NMTeamAttribute team_attr,
+                                      gboolean changed,
+                                      SetFieldModeEnum set_field_mode,
+                                      ResetJsonEnum reset_json)
+{
+	return _team_setting_attribute_changed (self,
+	                                        _team_attr_data_get (self->d.is_port, team_attr),
+	                                        changed,
+	                                        set_field_mode,
+	                                        reset_json);
+}
+
+static gboolean
+_team_setting_field_to_json (const NMTeamSetting *self,
+                             GString *gstr,
+                             gboolean prepend_delimiter,
+                             const TeamAttrData *attr_data)
+{
+	if (!_team_setting_has_field (self, attr_data))
+		return FALSE;
+
+	if (prepend_delimiter)
+		nm_json_aux_gstr_append_delimiter (gstr);
+	_team_attr_data_to_json (attr_data,
+	                         self->d.is_port,
+	                         gstr,
+	                         _team_setting_get_field (self, attr_data));
+	return TRUE;
+}
+
+static gboolean
+_team_setting_fields_to_json_maybe (const NMTeamSetting *self,
+                                    GString *gstr,
+                                    gboolean prepend_delimiter,
+                                    const NMTeamAttribute *team_attrs_lst,
+                                    gsize team_attrs_lst_len)
+{
+	gsize i;
+	gboolean any_added = FALSE;
+
+	for (i = 0; i < team_attrs_lst_len; i++) {
+		if (_team_setting_field_to_json (self,
+		                                 gstr,
+		                                 prepend_delimiter,
+		                                 _team_attr_data_get (self->d.is_port, team_attrs_lst[i]))) {
+			any_added = TRUE;
+			prepend_delimiter = TRUE;
+		}
+	}
+	return any_added;
+}
+
+static guint32
+_team_setting_set (NMTeamSetting *self,
+                   gboolean modify,
+                   const bool *has_lst,
+                   const NMValueTypUnion *val_lst)
+{
+	guint32 changed_flags = 0;
+	const TeamAttrData *attr_data;
+
+	nm_assert ((!has_lst) == (!val_lst));
+
+	for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
+		const NMValueTypUnion *p_val;
+		gconstpointer p_field;
+		gboolean has_field;
+
+		if (!_team_attr_data_is_relevant (attr_data, self->d.is_port))
+			continue;
+
+		has_field = (has_lst && has_lst[attr_data->team_attr]);
+
+		p_val = has_field
+		        ? &val_lst[attr_data->team_attr]
+		        : &attr_data->default_val;
+
+		p_field = _team_setting_get_field (self, attr_data);
+
+		if (!_team_attr_data_equal (attr_data,
+		                            self->d.is_port,
+		                            p_val,
+		                            p_field)) {
+			if (modify) {
+				_team_attr_data_copy (attr_data,
+				                      self->d.is_port,
+				                      (gpointer) p_field,
+				                      p_val);
+			}
+			changed_flags |= nm_team_attribute_to_flags (attr_data->team_attr);
+		}
+
+		if (modify) {
+			_team_setting_has_field_set (self,
+			                             attr_data,
+			                               has_field
+			                             ? SET_FIELD_MODE_SET
+			                             : SET_FIELD_MODE_UNSET);
+		}
+	}
+
+	return changed_flags;
+}
+
+static guint32
+_team_setting_check_default (const NMTeamSetting *self)
+{
+	return _team_setting_set ((NMTeamSetting *) self, FALSE, NULL, NULL);
+}
+
+static guint32
+_team_setting_set_default (NMTeamSetting *self)
+{
+	return _team_setting_set (self, TRUE, NULL, NULL);
+}
+
+/*****************************************************************************/
+
+gconstpointer
+_nm_team_setting_value_get (const NMTeamSetting *self,
+                            NMTeamAttribute team_attr,
+                            NMValueType value_type)
+{
+	const TeamAttrData *attr_data = _team_attr_data_get (self->d.is_port, team_attr);
+
+	nm_assert (value_type == attr_data->value_type);
+
+	nm_assert (   _team_setting_has_field (self, attr_data)
+	           || _team_attr_data_equal (attr_data,
+	                                     self->d.is_port,
+	                                     _team_setting_get_field (self, attr_data),
+	                                     &attr_data->default_val));
+	return _team_setting_get_field (self, attr_data);
+}
+
+static guint32
+_team_setting_value_set (NMTeamSetting *self,
+                         const TeamAttrData *attr_data,
+                         gconstpointer val,
+                         SetFieldModeEnum set_field_mode,
+                         ResetJsonEnum reset_json)
+{
+	gpointer p_field;
+	gboolean changed;
+
+	nm_assert (self);
+	_team_attr_data_ASSERT (attr_data);
+	nm_assert (val);
+
+	p_field = _team_setting_get_field (self, attr_data);
+
+	changed = !_team_attr_data_equal (attr_data, self->d.is_port, p_field, val);
+	if (changed)
+		nm_value_type_copy (attr_data->value_type, p_field, val);
+	return _team_setting_attribute_changed (self, attr_data, changed, set_field_mode, reset_json);
+}
+
+guint32
+nm_team_setting_value_reset (NMTeamSetting *self,
+                             NMTeamAttribute team_attr,
+                             gboolean to_default /* or else unset */)
+{
+	const TeamAttrData *attr_data;
+
+	nm_assert (self);
+
+	attr_data = _team_attr_data_get (self->d.is_port, team_attr);
+
+	return _team_setting_value_set (self,
+	                                attr_data,
+	                                &attr_data->default_val,
+	                                  to_default
+	                                ? SET_FIELD_MODE_SET
+	                                : SET_FIELD_MODE_UNSET,
+	                                RESET_JSON_YES);
+}
+
+guint32
+_nm_team_setting_value_set (NMTeamSetting *self,
+                            NMTeamAttribute team_attr,
+                            NMValueType value_type,
+                            gconstpointer val)
+{
+	const TeamAttrData *attr_data;
+
+	nm_assert (self);
+
+	attr_data = _team_attr_data_get (self->d.is_port, team_attr);
+
+	nm_assert (value_type == attr_data->value_type);
+
+	return _team_setting_value_set (self,
+	                                attr_data,
+	                                val,
+	                                SET_FIELD_MODE_SET_UNLESS_DEFAULT,
+	                                RESET_JSON_YES);
+}
+
+guint32
+nm_team_setting_value_link_watchers_add (NMTeamSetting *self,
+                                         const NMTeamLinkWatcher *link_watcher)
+{
+	guint i;
+	gboolean changed;
+
+	for (i = 0; i < self->d.link_watchers->len; i++) {
+		if (nm_team_link_watcher_equal (self->d.link_watchers->pdata[i], link_watcher)) {
+			changed = FALSE;
+			goto out;
+		}
+	}
+	changed = TRUE;
+	g_ptr_array_add ((GPtrArray *) self->d.link_watchers,
+	                 _nm_team_link_watcher_ref ((NMTeamLinkWatcher *) link_watcher));
+out:
+	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, changed, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES);
+}
+
+guint32
+nm_team_setting_value_link_watchers_remove_by_value (NMTeamSetting *self,
+                                                     const NMTeamLinkWatcher *link_watcher)
+{
+	guint i;
+
+	for (i = 0; i < self->d.link_watchers->len; i++) {
+		if (nm_team_link_watcher_equal (self->d.link_watchers->pdata[i],
+		                                link_watcher))
+			return nm_team_setting_value_link_watchers_remove (self, i);
+	}
+	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, FALSE, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES);
+}
+
+guint32
+nm_team_setting_value_link_watchers_remove (NMTeamSetting *self,
+                                            guint idx)
+{
+	g_ptr_array_remove_index ((GPtrArray *) self->d.link_watchers, idx);
+	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, TRUE, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES);
+}
+
+static guint32
+_team_setting_value_link_watchers_set_list (NMTeamSetting *self,
+                                            const NMTeamLinkWatcher *const*arr,
+                                            guint len,
+                                            SetFieldModeEnum set_field_mode,
+                                            ResetJsonEnum reset_json)
+{
+	gboolean changed;
+
+	if (   self->d.link_watchers->len == len
+	    && nm_team_link_watchers_cmp ((const NMTeamLinkWatcher *const*) self->d.link_watchers->pdata,
+	                                  arr,
+	                                  len,
+	                                  FALSE) == 0) {
+		changed = FALSE;
+		goto out;
+	}
+
+	changed = TRUE;
+	if (len == 0)
+		g_ptr_array_set_size ((GPtrArray *) self->d.link_watchers, 0);
+	else {
+		_nm_unused gs_unref_ptrarray GPtrArray *old_val_destroy = NULL;
+		guint i;
+
+		old_val_destroy = (GPtrArray *) g_steal_pointer (&self->_data_priv.link_watchers);
+
+		self->_data_priv.link_watchers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref);
+
+		for (i = 0; i < len; i++) {
+			if (arr[i]) {
+				g_ptr_array_add ((GPtrArray *) self->d.link_watchers,
+				                 _nm_team_link_watcher_ref ((NMTeamLinkWatcher *) arr[i]));
+			}
+		}
+	}
+
+out:
+	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, changed, set_field_mode, reset_json);
+}
+
+guint32
+nm_team_setting_value_link_watchers_set_list (NMTeamSetting *self,
+                                              const NMTeamLinkWatcher *const*arr,
+                                              guint len)
+{
+	return _team_setting_value_link_watchers_set_list (self,
+	                                                   arr,
+	                                                   len,
+	                                                   SET_FIELD_MODE_SET_UNLESS_DEFAULT,
+	                                                   RESET_JSON_YES);
+}
+
+/*****************************************************************************/
+
+guint32
+nm_team_setting_value_master_runner_tx_hash_add (NMTeamSetting *self,
+                                                 const char *txhash)
+{
+	gboolean changed;
+	guint i;
+
+	if (!self->d.master.runner_tx_hash)
+		self->_data_priv.master.runner_tx_hash = g_ptr_array_new_with_free_func (g_free);
+	else {
+		for (i = 0; i < self->d.master.runner_tx_hash->len; i++) {
+			if (nm_streq (txhash, self->d.master.runner_tx_hash->pdata[i])) {
+				changed = FALSE;
+				goto out;
+			}
+		}
+	}
+	changed = TRUE;
+	g_ptr_array_add ((GPtrArray *) self->d.master.runner_tx_hash, g_strdup (txhash));
+out:
+	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, changed, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES);
+}
+
+guint32
+nm_team_setting_value_master_runner_tx_hash_remove (NMTeamSetting *self,
+                                                    guint idx)
+{
+	g_ptr_array_remove_index ((GPtrArray *) self->d.master.runner_tx_hash, idx);
+	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, TRUE, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES);
+}
+
+static guint32
+_team_setting_value_master_runner_tx_hash_set_list (NMTeamSetting *self,
+                                                    const char *const*arr,
+                                                    guint len,
+                                                    SetFieldModeEnum set_field_mode,
+                                                    ResetJsonEnum reset_json)
+{
+	_nm_unused gs_unref_ptrarray GPtrArray *old_val_destroy = NULL;
+	gboolean changed;
+	guint i;
+
+	if (_nm_utils_strv_cmp_n (self->d.master.runner_tx_hash ? (const char *const*) self->d.master.runner_tx_hash->pdata : NULL,
+	                          self->d.master.runner_tx_hash ? self->d.master.runner_tx_hash->len : 0u,
+	                          arr,
+	                          len) == 0) {
+		changed = FALSE;
+		goto out;
+	}
+
+	changed = TRUE;
+
+	old_val_destroy = (GPtrArray *) g_steal_pointer (&self->_data_priv.master.runner_tx_hash);
+
+	for (i = 0; i < len; i++) {
+		if (!arr[i])
+			continue;
+		if (!self->d.master.runner_tx_hash)
+			self->_data_priv.master.runner_tx_hash = g_ptr_array_new_with_free_func (g_free);
+		g_ptr_array_add ((GPtrArray *) self->d.master.runner_tx_hash, g_strdup (arr[i]));
+	}
+
+out:
+	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, changed, set_field_mode, reset_json);
+}
+
+guint32
+nm_team_setting_value_master_runner_tx_hash_set_list (NMTeamSetting *self,
+                                                      const char *const*arr,
+                                                      guint len)
+{
+	return _team_setting_value_master_runner_tx_hash_set_list (self,
+	                                                           arr,
+	                                                           len,
+	                                                           SET_FIELD_MODE_SET_UNLESS_DEFAULT,
+	                                                           RESET_JSON_YES);
+}
+
+/*****************************************************************************/
+
+#define _LINK_WATCHER_ATTR_GET(args, link_watcher_attribute, _value_type) \
+	({ \
+		const NMValueTypUnioMaybe *const _args = (args); \
+		\
+		nm_assert (link_watcher_attr_datas[(link_watcher_attribute)].value_type == (_value_type)); \
+		\
+		  _args[(link_watcher_attribute)].has \
+		? &_args[(link_watcher_attribute)].val \
+		: &link_watcher_attr_datas[(link_watcher_attribute)].default_val; \
+	})
+#define _LINK_WATCHER_ATTR_GET_BOOL(args, link_watcher_attribute)   (_LINK_WATCHER_ATTR_GET (args, link_watcher_attribute, NM_VALUE_TYPE_BOOL   )->v_bool)
+#define _LINK_WATCHER_ATTR_GET_INT(args, link_watcher_attribute)    (_LINK_WATCHER_ATTR_GET (args, link_watcher_attribute, NM_VALUE_TYPE_INT    )->v_int)
+#define _LINK_WATCHER_ATTR_GET_STRING(args, link_watcher_attribute) (_LINK_WATCHER_ATTR_GET (args, link_watcher_attribute, NM_VALUE_TYPE_STRING )->v_string)
+
+#define _LINK_WATCHER_ATTR_SET(args, link_watcher_attribute, _value_type, c_type, val) \
+	({ \
+		nm_assert (link_watcher_attr_datas[(link_watcher_attribute)].value_type == (_value_type)); \
+		\
+		NM_VALUE_TYP_UNIO_MAYBE_SET (&(args)[(link_watcher_attribute)], c_type, (val)); \
+	})
+#define _LINK_WATCHER_ATTR_SET_BOOL(args, link_watcher_attribute, val)   _LINK_WATCHER_ATTR_SET((args), (link_watcher_attribute), NM_VALUE_TYPE_BOOL,   v_bool,   (val))
+#define _LINK_WATCHER_ATTR_SET_INT(args, link_watcher_attribute, val)    _LINK_WATCHER_ATTR_SET((args), (link_watcher_attribute), NM_VALUE_TYPE_INT,    v_int,    (val))
+#define _LINK_WATCHER_ATTR_SET_STRING(args, link_watcher_attribute, val) _LINK_WATCHER_ATTR_SET((args), (link_watcher_attribute), NM_VALUE_TYPE_STRING, v_string, (val))
+
+static void
+_link_watcher_unpack (const NMTeamLinkWatcher *link_watcher,
+                      NMValueTypUnioMaybe args[static G_N_ELEMENTS (link_watcher_attr_datas)])
+{
+	const char *v_name = nm_team_link_watcher_get_name (link_watcher);
+	NMTeamLinkWatcherArpPingFlags v_arp_ping_flags;
+
+	memset (args, 0, sizeof (args[0]) * G_N_ELEMENTS (link_watcher_attr_datas));
+
+	_LINK_WATCHER_ATTR_SET_STRING (args, LINK_WATCHER_ATTRIBUTE_NAME, v_name);
+
+	if (nm_streq (v_name, NM_TEAM_LINK_WATCHER_ETHTOOL)) {
+		_LINK_WATCHER_ATTR_SET_INT (args, LINK_WATCHER_ATTRIBUTE_DELAY_UP,   nm_team_link_watcher_get_delay_up (link_watcher));
+		_LINK_WATCHER_ATTR_SET_INT (args, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN, nm_team_link_watcher_get_delay_down (link_watcher));
+	} else if (NM_IN_STRSET (v_name, NM_TEAM_LINK_WATCHER_NSNA_PING,
+	                                 NM_TEAM_LINK_WATCHER_ARP_PING)) {
+		_LINK_WATCHER_ATTR_SET_INT    (args, LINK_WATCHER_ATTRIBUTE_INIT_WAIT,   nm_team_link_watcher_get_init_wait (link_watcher));
+		_LINK_WATCHER_ATTR_SET_INT    (args, LINK_WATCHER_ATTRIBUTE_INTERVAL,    nm_team_link_watcher_get_interval (link_watcher));
+		_LINK_WATCHER_ATTR_SET_INT    (args, LINK_WATCHER_ATTRIBUTE_MISSED_MAX,  nm_team_link_watcher_get_missed_max (link_watcher));
+		_LINK_WATCHER_ATTR_SET_STRING (args, LINK_WATCHER_ATTRIBUTE_TARGET_HOST, nm_team_link_watcher_get_target_host (link_watcher));
+		if (nm_streq (v_name, NM_TEAM_LINK_WATCHER_ARP_PING)) {
+			v_arp_ping_flags = nm_team_link_watcher_get_flags (link_watcher);
+			_LINK_WATCHER_ATTR_SET_INT    (args, LINK_WATCHER_ATTRIBUTE_VLANID,            nm_team_link_watcher_get_vlanid (link_watcher));
+			_LINK_WATCHER_ATTR_SET_STRING (args, LINK_WATCHER_ATTRIBUTE_SOURCE_HOST,       nm_team_link_watcher_get_source_host (link_watcher));
+			_LINK_WATCHER_ATTR_SET_BOOL   (args, LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE,   NM_FLAGS_HAS (v_arp_ping_flags, NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE));
+			_LINK_WATCHER_ATTR_SET_BOOL   (args, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, NM_FLAGS_HAS (v_arp_ping_flags, NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE));
+			_LINK_WATCHER_ATTR_SET_BOOL   (args, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS,       NM_FLAGS_HAS (v_arp_ping_flags, NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS));
+		}
+	}
+}
+
+static void
+_link_watcher_to_json (const NMTeamLinkWatcher *link_watcher,
+                       GString *gstr)
+{
+	NMValueTypUnioMaybe args[G_N_ELEMENTS (link_watcher_attr_datas)];
+	int i;
+	gboolean is_first = TRUE;
+
+	if (!link_watcher) {
+		g_string_append (gstr, "null");
+		return;
+	}
+
+	_link_watcher_unpack (link_watcher, args);
+
+	g_string_append (gstr, "{ ");
+
+	for (i = 0; i < (int) G_N_ELEMENTS (link_watcher_attr_datas); i++) {
+		const NMValueTypUnioMaybe *p_val = &args[i];
+		const LinkWatcherAttrData *attr_data = &link_watcher_attr_datas[i];
+
+		if (!p_val->has)
+			continue;
+		if (nm_value_type_equal (attr_data->value_type, &attr_data->default_val, &p_val->val))
+			continue;
+
+		if (is_first)
+			is_first = FALSE;
+		else
+			nm_json_aux_gstr_append_delimiter (gstr);
+		nm_json_aux_gstr_append_obj_name (gstr, attr_data->js_key, '\0');
+		nm_value_type_to_json (attr_data->value_type, gstr, &p_val->val);
+	}
+
+	g_string_append (gstr, " }");
+}
+
+#if WITH_JSON_VALIDATION
+static NMTeamLinkWatcher *
+_link_watcher_from_json (const json_t *root_js_obj,
+                         gboolean *out_unrecognized_content)
+{
+	NMValueTypUnioMaybe args[G_N_ELEMENTS (link_watcher_attr_datas)] = { };
+	const char *j_key;
+	json_t *j_val;
+	const char *v_name;
+	NMTeamLinkWatcher *result = NULL;
+
+	if (!json_is_object (root_js_obj))
+		goto fail;
+
+	json_object_foreach ((json_t *) root_js_obj, j_key, j_val) {
+		const LinkWatcherAttrData *attr_data = NULL;
+		NMValueTypUnioMaybe *parse_result;
+
+		if (j_key) {
+			int i;
+
+			for (i = 0; i < (int) G_N_ELEMENTS (link_watcher_attr_datas); i++) {
+				if (nm_streq (link_watcher_attr_datas[i].js_key, j_key)) {
+					attr_data = &link_watcher_attr_datas[i];
+					break;
+				}
+			}
+		}
+		if (!attr_data) {
+			*out_unrecognized_content = TRUE;
+			continue;
+		}
+
+		parse_result = &args[attr_data->link_watcher_attr];
+
+		if (parse_result->has)
+			*out_unrecognized_content = TRUE;
+
+		if (!nm_value_type_from_json (attr_data->value_type, j_val, &parse_result->val))
+			*out_unrecognized_content = TRUE;
+		else
+			parse_result->has = TRUE;
+	}
+
+#define _PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES(_parse_results, ...) \
+	({ \
+		int _i; \
+		\
+		for (_i = 0; _i < (int) G_N_ELEMENTS ((_parse_results)); _i++) { \
+			if (   (_parse_results)[_i].has \
+			    && !NM_IN_SET ((LinkWatcherAttribute) _i, __VA_ARGS__)) \
+				break; \
+		} \
+		\
+		(_i == (int) G_N_ELEMENTS ((_parse_results))); \
+	})
+
+	v_name = _LINK_WATCHER_ATTR_GET_STRING (args, LINK_WATCHER_ATTRIBUTE_NAME);
+
+	if (nm_streq0 (v_name, NM_TEAM_LINK_WATCHER_ETHTOOL)) {
+		if (_PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES (args, _EXPECTED_LINK_WATCHER_ATTRIBUTES_ETHTOOL))
+			*out_unrecognized_content = TRUE;
+		result = nm_team_link_watcher_new_ethtool (_LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_DELAY_UP),
+		                                           _LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN),
+		                                           NULL);
+	} else if (nm_streq0 (v_name, NM_TEAM_LINK_WATCHER_NSNA_PING)) {
+		if (_PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES (args, _EXPECTED_LINK_WATCHER_ATTRIBUTES_NSNA_PING))
+			*out_unrecognized_content = TRUE;
+		result = nm_team_link_watcher_new_nsna_ping (_LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_INIT_WAIT),
+		                                             _LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_INTERVAL),
+		                                             _LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_MISSED_MAX),
+		                                             _LINK_WATCHER_ATTR_GET_STRING (args, LINK_WATCHER_ATTRIBUTE_TARGET_HOST),
+		                                             NULL);
+	} else if (nm_streq0 (v_name, NM_TEAM_LINK_WATCHER_ARP_PING)) {
+		NMTeamLinkWatcherArpPingFlags v_flags = NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE;
+
+		if (_PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES (args, _EXPECTED_LINK_WATCHER_ATTRIBUTES_ARP_PING))
+			*out_unrecognized_content = TRUE;
+
+		if (_LINK_WATCHER_ATTR_GET_BOOL (args, LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE))
+			v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE;
+		if (_LINK_WATCHER_ATTR_GET_BOOL (args, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE))
+			v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE;
+		if (_LINK_WATCHER_ATTR_GET_BOOL (args, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS))
+			v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS;
+
+		result = nm_team_link_watcher_new_arp_ping2 (_LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_INIT_WAIT),
+		                                             _LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_INTERVAL),
+		                                             _LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_MISSED_MAX),
+		                                             _LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_VLANID),
+		                                             _LINK_WATCHER_ATTR_GET_STRING (args, LINK_WATCHER_ATTRIBUTE_TARGET_HOST),
+		                                             _LINK_WATCHER_ATTR_GET_STRING (args, LINK_WATCHER_ATTRIBUTE_SOURCE_HOST),
+		                                             v_flags,
+		                                             NULL);
+	}
+
+	if (result)
+		return result;
+fail:
+	*out_unrecognized_content = TRUE;
+	return NULL;
+}
+#endif
+
+/*****************************************************************************/
+
+static GVariant *
+_link_watcher_to_variant (const NMTeamLinkWatcher *link_watcher)
+{
+	NMValueTypUnioMaybe args[G_N_ELEMENTS (link_watcher_attr_datas)];
+	GVariantBuilder builder;
+	int i;
+
+	if (!link_watcher)
+		return NULL;
+
+	_link_watcher_unpack (link_watcher, args);
+
+	if (!args[LINK_WATCHER_ATTRIBUTE_NAME].has)
+		return NULL;
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+
+	for (i = 0; i < (int) G_N_ELEMENTS (link_watcher_attr_datas); i++) {
+		const NMValueTypUnioMaybe *p_val = &args[i];
+		const LinkWatcherAttrData *attr_data = &link_watcher_attr_datas[i];
+		GVariant *v;
+
+		if (!p_val->has)
+			continue;
+		if (nm_value_type_equal (attr_data->value_type, &attr_data->default_val, &p_val->val))
+			continue;
+
+		if (attr_data->value_type == NM_VALUE_TYPE_INT)
+			v = g_variant_new_int32 (p_val->val.v_int);
+		else {
+			v = nm_value_type_to_variant (attr_data->value_type,
+			                              &p_val->val);
+		}
+		if (!v)
+			continue;
+
+		nm_assert (g_variant_is_floating (v));
+		g_variant_builder_add (&builder,
+		                       "{sv}",
+		                       attr_data->dbus_name,
+		                       v);
+	}
+
+	return g_variant_builder_end (&builder);
+}
+
+#define _LINK_WATCHER_ATTR_VARGET(variants, link_watcher_attribute, _value_type, c_type, _cmd) \
+	({ \
+		GVariant *const*_variants = (variants); \
+		GVariant *_cc; \
+		\
+		nm_assert (link_watcher_attr_datas[(link_watcher_attribute)].value_type == (_value_type)); \
+		\
+		  (_cc = _variants[(link_watcher_attribute)]) \
+		? (_cmd) \
+		: link_watcher_attr_datas[(link_watcher_attribute)].default_val.c_type; \
+	})
+#define _LINK_WATCHER_ATTR_VARGET_BOOL(variants, link_watcher_attribute)   (_LINK_WATCHER_ATTR_VARGET (variants, link_watcher_attribute, NM_VALUE_TYPE_BOOL,   v_bool,   g_variant_get_boolean (_cc)      ))
+#define _LINK_WATCHER_ATTR_VARGET_INT(variants, link_watcher_attribute)    (_LINK_WATCHER_ATTR_VARGET (variants, link_watcher_attribute, NM_VALUE_TYPE_INT,    v_int,    g_variant_get_int32 (_cc)        ))
+#define _LINK_WATCHER_ATTR_VARGET_STRING(variants, link_watcher_attribute) (_LINK_WATCHER_ATTR_VARGET (variants, link_watcher_attribute, NM_VALUE_TYPE_STRING, v_string, g_variant_get_string (_cc, NULL) ))
+
+static void
+_variants_list_link_watcher_unref_auto (GVariant *(*p_variants)[])
+{
+	int i;
+
+	for (i = 0; i < (int) G_N_ELEMENTS (link_watcher_attr_datas); i++)
+		nm_g_variant_unref ((*p_variants)[i]);
+}
+
+static NMTeamLinkWatcher *
+_link_watcher_from_variant (GVariant *watcher_var,
+                            gboolean strict_parsing,
+                            GError **error)
+{
+	nm_auto (_variants_list_link_watcher_unref_auto) GVariant *variants[G_N_ELEMENTS (link_watcher_attr_datas)] = { NULL, };
+	const char *v_key;
+	GVariant *v_val;
+	const char *v_name;
+	GVariantIter iter;
+
+	g_return_val_if_fail (g_variant_is_of_type (watcher_var, G_VARIANT_TYPE ("a{sv}")), NULL);
+
+	g_variant_iter_init (&iter, watcher_var);
+	while (g_variant_iter_next (&iter, "{&sv}", &v_key, &v_val)) {
+		_nm_unused gs_unref_variant GVariant *v_val_free = v_val;
+		const LinkWatcherAttrData *attr_data = NULL;
+		const GVariantType *variant_type;
+		int i;
+
+		for (i = 0; i < (int) G_N_ELEMENTS (link_watcher_attr_datas); i++) {
+			if (nm_streq (link_watcher_attr_datas[i].dbus_name, v_key)) {
+				attr_data = &link_watcher_attr_datas[i];
+				break;
+			}
+		}
+		if (!attr_data) {
+			if (strict_parsing) {
+				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				             _("invalid D-Bus property \"%s\""),
+				             v_key);
+				return NULL;
+			}
+			continue;
+		}
+
+		if (attr_data->value_type == NM_VALUE_TYPE_INT)
+			variant_type = G_VARIANT_TYPE_INT32;
+		else
+			variant_type = nm_value_type_get_variant_type (attr_data->value_type);
+
+		if (!g_variant_is_of_type (v_val, variant_type)) {
+			if (strict_parsing) {
+				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				             _("invalid D-Bus property \"%s\""),
+				             v_key);
+				return NULL;
+			}
+			continue;
+		}
+
+		if (variants[attr_data->link_watcher_attr]) {
+			if (strict_parsing) {
+				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				             _("duplicate D-Bus property \"%s\""),
+				             v_key);
+				return NULL;
+			}
+			g_variant_unref (variants[attr_data->link_watcher_attr]);
+		}
+		variants[attr_data->link_watcher_attr] = g_steal_pointer (&v_val_free);
+	}
+
+#define _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES(_type, _variants, _error, ...) \
+	({ \
+		int _i; \
+		gboolean _has_error = FALSE; \
+		\
+		for (_i = 0; _i < (int) G_N_ELEMENTS ((_variants)); _i++) { \
+			if (   (_variants)[_i] \
+			    && !NM_IN_SET ((LinkWatcherAttribute) _i, __VA_ARGS__)) { \
+				_has_error = TRUE; \
+				g_set_error (_error, \
+				             NM_CONNECTION_ERROR, \
+				             NM_CONNECTION_ERROR_INVALID_PROPERTY, \
+				             _("invalid D-Bus property \"%s\" for \"%s\""), \
+				             link_watcher_attr_datas[_i].dbus_name, \
+				             _type); \
+				break; \
+			} \
+		} \
+		\
+		_has_error; \
+	})
+
+	v_name = _LINK_WATCHER_ATTR_VARGET_STRING (variants, LINK_WATCHER_ATTRIBUTE_NAME);
+
+	if (nm_streq0 (v_name, NM_TEAM_LINK_WATCHER_ETHTOOL)) {
+		if (   strict_parsing
+		    && _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES (v_name, variants, error, _EXPECTED_LINK_WATCHER_ATTRIBUTES_ETHTOOL))
+			return NULL;
+		return nm_team_link_watcher_new_ethtool (_LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_DELAY_UP),
+		                                         _LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN),
+		                                         strict_parsing ? error : NULL);
+	}
+
+	if (nm_streq0 (v_name, NM_TEAM_LINK_WATCHER_NSNA_PING)) {
+		if (   strict_parsing
+		    && _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES (v_name, variants, error, _EXPECTED_LINK_WATCHER_ATTRIBUTES_NSNA_PING))
+			return NULL;
+		return nm_team_link_watcher_new_nsna_ping (_LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_INIT_WAIT),
+		                                           _LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_INTERVAL),
+		                                           _LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_MISSED_MAX),
+		                                           _LINK_WATCHER_ATTR_VARGET_STRING (variants, LINK_WATCHER_ATTRIBUTE_TARGET_HOST),
+		                                           strict_parsing ? error : NULL);
+	}
+
+	if (nm_streq0 (v_name, NM_TEAM_LINK_WATCHER_ARP_PING)) {
+		NMTeamLinkWatcherArpPingFlags v_flags = NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE;
+
+		if (   strict_parsing
+		    && _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES (v_name, variants, error, _EXPECTED_LINK_WATCHER_ATTRIBUTES_ARP_PING))
+			return NULL;
+
+		if (_LINK_WATCHER_ATTR_VARGET_BOOL (variants, LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE))
+			v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE;
+		if (_LINK_WATCHER_ATTR_VARGET_BOOL (variants, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE))
+			v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE;
+		if (_LINK_WATCHER_ATTR_VARGET_BOOL (variants, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS))
+			v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS;
+
+		return nm_team_link_watcher_new_arp_ping2 (_LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_INIT_WAIT),
+		                                           _LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_INTERVAL),
+		                                           _LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_MISSED_MAX),
+		                                           _LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_VLANID),
+		                                           _LINK_WATCHER_ATTR_VARGET_STRING (variants, LINK_WATCHER_ATTRIBUTE_TARGET_HOST),
+		                                           _LINK_WATCHER_ATTR_VARGET_STRING (variants, LINK_WATCHER_ATTRIBUTE_SOURCE_HOST),
+		                                           v_flags,
+		                                           strict_parsing ? error : NULL);
+	}
+
+	if (strict_parsing) {
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             _("unknown link-watcher name \"%s\""),
+		             v_name);
+	}
+	return NULL;
+}
+
+/*****************************************************************************/
+
+/**
+ * _nm_utils_team_link_watchers_to_variant:
+ * @link_watchers: (element-type NMTeamLinkWatcher): array of #NMTeamLinkWatcher
+ *
+ * Utility function to convert a #GPtrArray of #NMTeamLinkWatcher objects
+ * representing link watcher configuration for team devices into a #GVariant
+ * of type 'aa{sv}' representing an array of link watchers.
+ *
+ * Returns: (transfer full): a new floating #GVariant representing link watchers.
+ **/
+GVariant *
+_nm_utils_team_link_watchers_to_variant (const GPtrArray *link_watchers)
+{
+	GVariantBuilder builder;
+	guint i;
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+	if (link_watchers) {
+		for (i = 0; i < link_watchers->len; i++) {
+			g_variant_builder_add (&builder,
+			                       "@a{sv}",
+			                       _link_watcher_to_variant (link_watchers->pdata[i]));
+		}
+	}
+	return g_variant_builder_end (&builder);
+}
+
+/**
+ * _nm_utils_team_link_watchers_from_variant:
+ * @value: a #GVariant of type 'aa{sv}'
+ * @strict_parsing: whether to parse strictly or ignore everything invalid.
+ * @error: error reason.
+ *
+ * Utility function to convert a #GVariant representing a list of team link
+ * watchers int a #GPtrArray of #NMTeamLinkWatcher objects.
+ *
+ * Returns: (transfer full) (element-type NMTeamLinkWatcher): a newly allocated
+ *   #GPtrArray of #NMTeamLinkWatcher objects.
+ *
+ * Note that if you provide an @error, then the function can only fail (and return %NULL)
+ * or succeed (and not return %NULL). If you don't provide an @error, then the function
+ * never returns %NULL.
+ **/
+GPtrArray *
+_nm_utils_team_link_watchers_from_variant (GVariant *value,
+                                           gboolean strict_parsing,
+                                           GError **error)
+{
+	gs_unref_ptrarray GPtrArray *link_watchers = NULL;
+	GVariantIter iter;
+	GVariant *watcher_var;
+
+	g_return_val_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}")), NULL);
+
+	link_watchers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref);
+
+	g_variant_iter_init (&iter, value);
+	while (g_variant_iter_next (&iter, "@a{sv}", &watcher_var)) {
+		_nm_unused gs_unref_variant GVariant *watcher_var_free = watcher_var;
+		NMTeamLinkWatcher *watcher;
+
+		watcher = _link_watcher_from_variant (watcher_var, strict_parsing, error);
+		if (error && *error)
+			return NULL;
+		if (watcher)
+			g_ptr_array_add (link_watchers, watcher);
+	}
+
+	return g_steal_pointer (&link_watchers);
+}
+
+/*****************************************************************************/
+
+const char *
+nm_team_setting_config_get (const NMTeamSetting *self)
+{
+	char *js_str;
+
+	nm_assert (self);
+
+	if (G_LIKELY (!self->d._js_str_need_synthetize))
+		return self->d._js_str;
+
+	nm_assert (!self->d._js_str);
+	nm_assert (self->d.strict_validated);
+
+	if (_team_setting_check_default (self) == 0) {
+		/* the default is set. We signal this as a NULL JSON string.
+		 * Nothing to do. */
+		js_str = NULL;
+	} else {
+		gboolean list_is_empty = TRUE;
+		GString *gstr;
+
+		gstr = g_string_new (NULL);
+
+		g_string_append (gstr, "{ ");
+
+		if (self->d.is_port) {
+			static const NMTeamAttribute attr_lst_port[] = {
+				NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID,
+				NM_TEAM_ATTRIBUTE_PORT_PRIO,
+				NM_TEAM_ATTRIBUTE_PORT_STICKY,
+				NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO,
+				NM_TEAM_ATTRIBUTE_PORT_LACP_KEY,
+			};
+
+			if (_team_setting_fields_to_json_maybe (self, gstr, !list_is_empty, attr_lst_port, G_N_ELEMENTS (attr_lst_port)))
+				list_is_empty = FALSE;
+		} else {
+			static const NMTeamAttribute attr_lst_runner_pt1[] = {
+				NM_TEAM_ATTRIBUTE_MASTER_RUNNER,
+				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY,
+				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH,
+			};
+			static const NMTeamAttribute attr_lst_runner_pt2[] = {
+				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER,
+				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL,
+			};
+			static const NMTeamAttribute attr_lst_runner_pt3[] = {
+				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE,
+				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE,
+				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO,
+				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS,
+				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY,
+			};
+			static const NMTeamAttribute attr_lst_notify_peers[] = {
+				NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT,
+				NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL,
+			};
+			static const NMTeamAttribute attr_lst_mcast_rejoin[] = {
+				NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT,
+				NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL,
+			};
+
+			if (   _team_setting_has_fields_any_v (self, attr_lst_runner_pt1, G_N_ELEMENTS (attr_lst_runner_pt1))
+			    || _team_setting_has_fields_any_v (self, attr_lst_runner_pt2, G_N_ELEMENTS (attr_lst_runner_pt2))
+			    || _team_setting_has_fields_any_v (self, attr_lst_runner_pt3, G_N_ELEMENTS (attr_lst_runner_pt3))) {
+				gboolean list_is_empty2 = TRUE;
+
+				if (!list_is_empty)
+					nm_json_aux_gstr_append_delimiter (gstr);
+				nm_json_aux_gstr_append_obj_name (gstr, "runner", '{');
+
+				if (_team_setting_fields_to_json_maybe (self, gstr, !list_is_empty2, attr_lst_runner_pt1, G_N_ELEMENTS (attr_lst_runner_pt1)))
+					list_is_empty2 = FALSE;
+
+				if (_team_setting_has_fields_any_v (self, attr_lst_runner_pt2, G_N_ELEMENTS (attr_lst_runner_pt2))) {
+					if (!list_is_empty2)
+						nm_json_aux_gstr_append_delimiter (gstr);
+					nm_json_aux_gstr_append_obj_name (gstr, "tx_balancer", '{');
+					if (!_team_setting_fields_to_json_maybe (self, gstr, FALSE, attr_lst_runner_pt2, G_N_ELEMENTS (attr_lst_runner_pt2)))
+						nm_assert_not_reached ();
+					g_string_append (gstr, " }");
+					list_is_empty2 = FALSE;
+				}
+
+				if (_team_setting_fields_to_json_maybe (self, gstr, !list_is_empty2, attr_lst_runner_pt3, G_N_ELEMENTS (attr_lst_runner_pt3)))
+					list_is_empty2 = FALSE;
+
+				nm_assert (!list_is_empty2);
+				g_string_append (gstr, " }");
+				list_is_empty = FALSE;
+			}
+
+			if (_team_setting_has_fields_any_v (self, attr_lst_notify_peers, G_N_ELEMENTS (attr_lst_notify_peers))) {
+				if (!list_is_empty)
+					nm_json_aux_gstr_append_delimiter (gstr);
+				nm_json_aux_gstr_append_obj_name (gstr, "notify_peers", '{');
+				if (!_team_setting_fields_to_json_maybe (self, gstr, FALSE, attr_lst_notify_peers, G_N_ELEMENTS (attr_lst_notify_peers)))
+					nm_assert_not_reached ();
+				g_string_append (gstr, " }");
+				list_is_empty = FALSE;
+			}
+
+			if (_team_setting_has_fields_any_v (self, attr_lst_mcast_rejoin, G_N_ELEMENTS (attr_lst_mcast_rejoin))) {
+				if (!list_is_empty)
+					nm_json_aux_gstr_append_delimiter (gstr);
+				nm_json_aux_gstr_append_obj_name (gstr, "mcast_rejoin", '{');
+				if (!_team_setting_fields_to_json_maybe (self, gstr, FALSE, attr_lst_mcast_rejoin, G_N_ELEMENTS (attr_lst_mcast_rejoin)))
+					nm_assert_not_reached ();
+				g_string_append (gstr, " }");
+				list_is_empty = FALSE;
+			}
+		}
+
+		if (_team_setting_field_to_json (self,
+		                                 gstr,
+		                                 !list_is_empty,
+		                                 _team_attr_data_get (self->d.is_port, NM_TEAM_ATTRIBUTE_LINK_WATCHERS)))
+			list_is_empty = FALSE;
+
+		if (!list_is_empty)
+			g_string_append (gstr, " }");
+
+		js_str = g_string_free (gstr, list_is_empty);;
+	}
+
+	/* mutate the constant object. In C++ speak, these fields are "mutable".
+	 * That is because we construct the JSON string lazily/on-demand. */
+	*((char **) &self->_data_priv._js_str) = js_str;
+	*((bool *) &self->_data_priv._js_str_need_synthetize) = FALSE;
+
+	return self->d._js_str;
+}
+
+/*****************************************************************************/
+
+#if WITH_JSON_VALIDATION
+static gboolean
+_attr_data_match_keys (const TeamAttrData *attr_data,
+                       const char *const*keys,
+                       guint8 n_keys)
+{
+	guint8 i;
+
+	_team_attr_data_ASSERT (attr_data);
+	nm_assert (keys);
+	nm_assert (n_keys > 0);
+	nm_assert (({
+		gboolean all_non_null = TRUE;
+
+		for (i = 0; i < n_keys; i++)
+			all_non_null = all_non_null && keys[i] && keys[i][0] != '\0';
+		all_non_null;
+	}));
+
+	if (attr_data->js_keys_len < n_keys)
+		return FALSE;
+	for (i = 0; i < n_keys; i++) {
+		if (!nm_streq (keys[i], attr_data->js_keys[i]))
+			return FALSE;
+	}
+	return TRUE;
+}
+
+static const TeamAttrData *
+_attr_data_find_by_json_key (gboolean is_port,
+                             const char *const*keys,
+                             guint8 n_keys)
+{
+	const TeamAttrData *attr_data;
+
+	for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
+		if (    _team_attr_data_is_relevant (attr_data, is_port)
+		    && _attr_data_match_keys (attr_data, keys, n_keys))
+			return attr_data;
+	}
+
+	return NULL;
+}
+
+static void
+_js_parse_locate_keys (NMTeamSetting *self,
+                       json_t *root_js_obj,
+                       json_t *found_keys[static _NM_TEAM_ATTRIBUTE_NUM],
+                       gboolean *out_unrecognized_content)
+{
+	const char *keys[3];
+	const char *cur_key1;
+	const char *cur_key2;
+	const char *cur_key3;
+	json_t *cur_val1;
+	json_t *cur_val2;
+	json_t *cur_val3;
+
+#define _handle(_self, _cur_key, _cur_val, _keys, _level, _found_keys, _out_unrecognized_content) \
+	({ \
+		const TeamAttrData *_attr_data; \
+		gboolean _handled = FALSE; \
+		\
+		(_keys)[(_level) - 1] = (_cur_key); \
+		_attr_data = _attr_data_find_by_json_key ((_self)->d.is_port, (_keys), (_level)); \
+		if (   _attr_data \
+			&& _attr_data->js_keys_len == (_level)) { \
+			if ((_found_keys)[_attr_data->team_attr]) \
+				*(_out_unrecognized_content) = TRUE; \
+			(_found_keys)[_attr_data->team_attr] = (_cur_val); \
+			_handled = TRUE; \
+		} else if (   !_attr_data \
+		           || !json_is_object ((_cur_val))) { \
+			*(_out_unrecognized_content) = TRUE; \
+			_handled = TRUE; \
+		} \
+		_handled; \
+	})
+
+	json_object_foreach (root_js_obj, cur_key1, cur_val1) {
+		if (!_handle (self, cur_key1, cur_val1, keys, 1, found_keys, out_unrecognized_content)) {
+			json_object_foreach (cur_val1, cur_key2, cur_val2) {
+				if (!_handle (self, cur_key2, cur_val2, keys, 2, found_keys, out_unrecognized_content)) {
+					json_object_foreach (cur_val2, cur_key3, cur_val3) {
+						if (!_handle (self, cur_key3, cur_val3, keys, 3, found_keys, out_unrecognized_content))
+							*out_unrecognized_content = TRUE;
+					}
+				}
+			}
+		}
+	}
+
+#undef _handle
+}
+
+static void
+_js_parse_unpack (gboolean is_port,
+                  json_t *found_keys[static _NM_TEAM_ATTRIBUTE_NUM],
+                  bool out_has_lst[static _NM_TEAM_ATTRIBUTE_NUM],
+                  NMValueTypUnion out_val_lst[static _NM_TEAM_ATTRIBUTE_NUM],
+                  gboolean *out_unrecognized_content,
+                  GPtrArray **out_ptr_array_link_watchers_free,
+                  GPtrArray **out_ptr_array_master_runner_tx_hash_free)
+{
+	const TeamAttrData *attr_data;
+
+	for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
+		NMValueTypUnion *p_out_val;
+		gboolean valid = FALSE;
+		json_t *arg_js_obj;
+
+		if (!_team_attr_data_is_relevant (attr_data, is_port))
+			continue;
+
+		nm_assert (!out_has_lst[attr_data->team_attr]);
+
+		arg_js_obj = found_keys[attr_data->team_attr];
+		if (!arg_js_obj)
+			continue;
+
+		p_out_val = &out_val_lst[attr_data->team_attr];
+
+		if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC)
+			valid = nm_value_type_from_json (attr_data->value_type, arg_js_obj, p_out_val);
+		else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) {
+			GPtrArray *link_watchers = NULL;
+			NMTeamLinkWatcher *link_watcher;
+
+			nm_assert (out_ptr_array_link_watchers_free && !*out_ptr_array_link_watchers_free);
+			if (json_is_array (arg_js_obj)) {
+				gsize i, len;
+
+				len = json_array_size (arg_js_obj);
+				link_watchers = g_ptr_array_new_full (len, (GDestroyNotify) nm_team_link_watcher_unref);
+				for (i = 0; i < len; i++) {
+					link_watcher = _link_watcher_from_json (json_array_get (arg_js_obj, i),
+					                                        out_unrecognized_content);
+					if (link_watcher)
+						g_ptr_array_add (link_watchers, link_watcher);
+				}
+			} else {
+				link_watcher = _link_watcher_from_json (arg_js_obj,
+				                                        out_unrecognized_content);
+				if (link_watcher) {
+					link_watchers = g_ptr_array_new_full (1, (GDestroyNotify) nm_team_link_watcher_unref);
+					g_ptr_array_add (link_watchers, link_watcher);
+				}
+			}
+			if (link_watchers) {
+				valid = TRUE;
+				p_out_val->v_ptrarray = link_watchers;
+				*out_ptr_array_link_watchers_free = link_watchers;
+			}
+		} else if (   !is_port
+		           && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
+			GPtrArray *strv = NULL;
+
+			nm_assert (out_ptr_array_master_runner_tx_hash_free && !*out_ptr_array_master_runner_tx_hash_free);
+			if (json_is_array (arg_js_obj)) {
+				gsize i, len;
+
+				len = json_array_size (arg_js_obj);
+				if (len > 0) {
+					strv = g_ptr_array_sized_new (len);
+					for (i = 0; i < len; i++) {
+						const char *v_string;
+
+						if (   nm_jansson_json_as_string (json_array_get (arg_js_obj, i),
+						                                  &v_string) <= 0
+						    || !v_string
+						    || v_string[0] == '\0') {
+							/* we remember that there was some invalid content, but parts of the
+							 * list could still be parsed. */
+							*out_unrecognized_content = TRUE;
+							continue;
+						}
+						g_ptr_array_add (strv, (char *) v_string);
+					}
+				}
+				valid = TRUE;
+				*out_ptr_array_master_runner_tx_hash_free = strv;
+			}
+			p_out_val->v_ptrarray = strv;
+		} else
+			nm_assert_not_reached ();
+
+		out_has_lst[attr_data->team_attr] = valid;
+		if (!valid)
+			*out_unrecognized_content = TRUE;
+	}
+}
+#endif
+
+guint32
+nm_team_setting_config_set (NMTeamSetting *self, const char *js_str)
+{
+	guint32 changed_flags = 0;
+	gboolean do_set_default = TRUE;
+	gboolean new_js_str_invalid = FALSE;
+
+	_team_setting_ASSERT (self);
+
+	if (   !js_str
+	    || js_str[0] == '\0') {
+		changed_flags = _team_setting_set_default (self);
+		if (   changed_flags != 0
+		    || !nm_streq0 (js_str, self->d._js_str))
+			changed_flags |= nm_team_attribute_to_flags (NM_TEAM_ATTRIBUTE_CONFIG);
+		nm_clear_g_free ((char **) &self->_data_priv._js_str);
+		self->_data_priv._js_str = g_strdup (js_str);
+		self->_data_priv._js_str_need_synthetize = FALSE;
+		self->_data_priv.strict_validated = TRUE;
+		self->_data_priv.js_str_invalid = FALSE;
+		return changed_flags;
+	}
+
+	if (   self->d._js_str
+	    && nm_streq (js_str, self->d._js_str)) {
+	    if (!self->d.strict_validated) {
+			/* setting the same JSON string twice in a row has no effect. */
+			return 0;
+		}
+	} else
+		changed_flags |= nm_team_attribute_to_flags (NM_TEAM_ATTRIBUTE_CONFIG);
+
+#if WITH_JSON_VALIDATION
+	{
+		nm_auto_decref_json json_t *root_js_obj = NULL;
+
+		if (nm_jansson_load ())
+			root_js_obj = json_loads (js_str, 0, NULL);
+
+		if (   !root_js_obj
+		    || !json_is_object (root_js_obj))
+			new_js_str_invalid = TRUE;
+		else {
+			gboolean unrecognized_content = FALSE;
+			bool has_lst[_NM_TEAM_ATTRIBUTE_NUM] = { FALSE, };
+			NMValueTypUnion val_lst[_NM_TEAM_ATTRIBUTE_NUM];
+			json_t *found_keys[_NM_TEAM_ATTRIBUTE_NUM] = { NULL, };
+			gs_unref_ptrarray GPtrArray *ptr_array_master_runner_tx_hash_free = NULL;
+			gs_unref_ptrarray GPtrArray *ptr_array_link_watchers_free = NULL;
+
+			_js_parse_locate_keys (self,
+			                       root_js_obj,
+			                       found_keys,
+			                       &unrecognized_content);
+
+			_js_parse_unpack (self->d.is_port,
+			                  found_keys,
+			                  has_lst,
+			                  val_lst,
+			                  &unrecognized_content,
+			                  &ptr_array_link_watchers_free,
+			                  &ptr_array_master_runner_tx_hash_free);
+
+			do_set_default = FALSE;
+
+			changed_flags |= _team_setting_set (self,
+			                                    TRUE,
+			                                    has_lst,
+			                                    val_lst);
+		}
+	}
+
+#endif
+
+	if (do_set_default)
+		changed_flags |= _team_setting_set_default (self);
+
+	self->_data_priv.strict_validated = FALSE;
+	self->_data_priv._js_str_need_synthetize = FALSE;
+	self->_data_priv.js_str_invalid = new_js_str_invalid;
+	g_free ((char *) self->_data_priv._js_str);
+	self->_data_priv._js_str = g_strdup (js_str);
+
+	return changed_flags;
+}
+
+/*****************************************************************************/
+
+static void
+_team_setting_prefix_error_plain (gboolean is_port,
+                                  const char *property_name,
+                                  GError **error)
+{
+	g_prefix_error (error,
+	                "%s.%s: ",
+	                  is_port
+	                ? NM_SETTING_TEAM_PORT_SETTING_NAME
+	                : NM_SETTING_TEAM_SETTING_NAME,
+	                property_name);
+}
+
+static void
+_team_setting_prefix_error (const NMTeamSetting *self,
+                            const char *prop_name_master,
+                            const char *prop_name_port,
+                            GError **error)
+{
+	_team_setting_ASSERT (self);
+	nm_assert (  self->d.is_port
+	           ? (!!prop_name_port)
+	           : (!!prop_name_master));
+	_team_setting_prefix_error_plain (self->d.is_port,
+	                                    self->d.is_port
+	                                  ? prop_name_port
+	                                  : prop_name_master,
+	                                  error);
+}
+
+static gboolean
+_team_setting_verify_properties (const NMTeamSetting *self,
+                                 GError **error)
+{
+	const TeamAttrData *attr_data;
+	guint i;
+
+	for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
+
+		if (!_team_attr_data_is_relevant (attr_data, self->d.is_port))
+			continue;
+		if (!_team_setting_has_field (self, attr_data))
+			continue;
+
+		if (attr_data->has_range) {
+			gconstpointer p_field;
+
+			p_field = _team_setting_get_field (self, attr_data);
+			if (attr_data->value_type == NM_VALUE_TYPE_INT32) {
+				gint32 v = *((const gint32 *) p_field);
+
+				if (   v < attr_data->range.r_int32.min
+				    || v > attr_data->range.r_int32.max) {
+					g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
+					             _("value out or range"));
+					_team_setting_prefix_error_plain (self->d.is_port, attr_data->property_name, error);
+					return FALSE;
+				}
+			} else if (attr_data->value_type == NM_VALUE_TYPE_STRING) {
+				const char *v = *((const char *const*) p_field);
+
+				if (nm_utils_strv_find_first ((char **) attr_data->range.r_string.valid_names,
+				                              -1,
+				                              v) < 0) {
+					g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
+					             _("invalid value"));
+					_team_setting_prefix_error_plain (self->d.is_port, attr_data->property_name, error);
+					return FALSE;
+				}
+			} else
+				nm_assert_not_reached ();
+		}
+
+		if (   !self->d.is_port
+		    && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
+			if (self->d.master.runner_tx_hash) {
+				for (i = 0; i < self->d.master.runner_tx_hash->len; i++) {
+					const char *val = self->d.master.runner_tx_hash->pdata[i];
+
+					if (  !val
+					    || (nm_utils_strv_find_first ((char **) _valid_names_runner_tx_hash,
+					                                  -1,
+					                                  val) < 0)) {
+						g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
+						             _("invalid runner-tx-hash"));
+						_team_setting_prefix_error_plain (self->d.is_port, NM_SETTING_TEAM_RUNNER_TX_HASH, error);
+						return FALSE;
+					}
+				}
+			}
+		}
+	}
+
+	if (!self->d.is_port) {
+
+		for (i = 0; i < G_N_ELEMENTS (_runner_compat_lst); i++) {
+			const RunnerCompatElem *e = &_runner_compat_lst[i];
+
+			nm_assert (NM_PTRARRAY_LEN (e->valid_runners) > 0);
+
+			attr_data = _team_attr_data_get (FALSE, e->team_attr);
+
+			if (!_team_setting_has_field (self, attr_data))
+				continue;
+			if (   self->d.master.runner
+			    && (nm_utils_strv_find_first ((char **) e->valid_runners,
+			                                  -1,
+			                                  self->d.master.runner) >= 0))
+				continue;
+			if (e->valid_runners[1] == NULL) {
+				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
+				             _("%s is only allowed for runner %s"),
+				             attr_data->property_name,
+				             e->valid_runners[0]);
+			} else {
+				gs_free char *s = NULL;
+
+				s = g_strjoinv (",", (char **) e->valid_runners);
+				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
+				             _("%s is only allowed for runners %s"),
+				             attr_data->property_name,
+				             s);
+			}
+			_team_setting_prefix_error_plain (self->d.is_port, NM_SETTING_TEAM_RUNNER, error);
+			return FALSE;
+		}
+	} else {
+		gboolean has_lacp_attrs;
+		gboolean has_activebackup_attrs;
+
+		has_lacp_attrs = _team_setting_has_fields_any (self, NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO,
+		                                                     NM_TEAM_ATTRIBUTE_PORT_LACP_KEY);
+		has_activebackup_attrs = _team_setting_has_fields_any (self, NM_TEAM_ATTRIBUTE_PORT_PRIO,
+		                                                             NM_TEAM_ATTRIBUTE_PORT_STICKY);
+		if (has_lacp_attrs && has_activebackup_attrs) {
+			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
+			             _("cannot set parameters for lacp and activebackup runners together"));
+			_team_setting_prefix_error (self, NM_SETTING_TEAM_LINK_WATCHERS, NM_SETTING_TEAM_PORT_LINK_WATCHERS, error);
+			return FALSE;
+		}
+	}
+
+	for (i = 0; i < self->d.link_watchers->len; i++) {
+		if (!self->d.link_watchers->pdata[i]) {
+			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
+			             _("missing link watcher"));
+			_team_setting_prefix_error (self, NM_SETTING_TEAM_LINK_WATCHERS, NM_SETTING_TEAM_PORT_LINK_WATCHERS, error);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+_team_setting_verify_config (const NMTeamSetting *self,
+                             GError **error)
+{
+	const char *js_str;
+
+	/* we always materialize the JSON string. That is because we want to validate the
+	 * string length of the resulting JSON. */
+	js_str = nm_team_setting_config_get (self);
+
+	if (js_str) {
+		if (strlen (js_str) > 1*1024*1024) {
+			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("team config exceeds size limit"));
+			_team_setting_prefix_error (self, NM_SETTING_TEAM_CONFIG, NM_SETTING_TEAM_PORT_CONFIG, error);
+			return FALSE;
+		}
+		if (!g_utf8_validate (js_str, -1, NULL)) {
+			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("team config is not valid UTF-8"));
+			_team_setting_prefix_error (self, NM_SETTING_TEAM_CONFIG, NM_SETTING_TEAM_PORT_CONFIG, error);
+			return FALSE;
+		}
+		if (self->d.js_str_invalid) {
+			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("invalid json"));
+			_team_setting_prefix_error (self, NM_SETTING_TEAM_CONFIG, NM_SETTING_TEAM_PORT_CONFIG, error);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+gboolean
+nm_team_setting_verify (const NMTeamSetting *self,
+                        GError **error)
+{
+	if (self->d.strict_validated) {
+		if (!_team_setting_verify_properties (self, error))
+			return FALSE;
+	}
+	return _team_setting_verify_config (self, error);
+}
+
+/*****************************************************************************/
+
+int
+nm_team_setting_cmp (const NMTeamSetting *self_a,
+                     const NMTeamSetting *self_b,
+                     gboolean ignore_js_str)
+{
+	const TeamAttrData *attr_data;
+
+	NM_CMP_SELF (self_a, self_b);
+
+	NM_CMP_FIELD_UNSAFE (self_a, self_b, d.is_port);
+
+	for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
+		if (!_team_attr_data_is_relevant (attr_data, self_a->d.is_port))
+			continue;
+
+		NM_CMP_RETURN (_team_attr_data_cmp (attr_data,
+		                                    self_a->d.is_port,
+		                                    _team_setting_get_field (self_a, attr_data),
+		                                    _team_setting_get_field (self_b, attr_data)));
+	}
+
+	if (!ignore_js_str) {
+		NM_CMP_DIRECT_STRCMP0 (nm_team_setting_config_get (self_a),
+		                       nm_team_setting_config_get (self_b));
+	}
+
+	return 0;
+}
+
+guint32
+nm_team_setting_reset (NMTeamSetting *self,
+                       const NMTeamSetting *src)
+{
+	const TeamAttrData *attr_data;
+	guint32 changed_flags;
+
+	_team_setting_ASSERT (self);
+	_team_setting_ASSERT (src);
+	nm_assert (self->d.is_port == src->d.is_port);
+
+	if (self == src)
+		return 0;
+
+	changed_flags = 0;
+
+	for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
+		if (!_team_attr_data_is_relevant (attr_data, self->d.is_port))
+			continue;
+		if (_team_attr_data_equal (attr_data,
+		                           self->d.is_port,
+		                           _team_setting_get_field (self, attr_data),
+		                           _team_setting_get_field (src, attr_data)))
+			continue;
+		_team_attr_data_copy (attr_data,
+		                      self->d.is_port,
+		                      _team_setting_get_field (self, attr_data),
+		                      _team_setting_get_field (src, attr_data));
+		changed_flags |= nm_team_attribute_to_flags (attr_data->team_attr);
+	}
+
+	self->_data_priv.has_fields_mask = src->d.has_fields_mask;
+
+	if (!nm_streq0 (self->d._js_str, src->d._js_str)) {
+		g_free ((char *) self->_data_priv._js_str);
+		self->_data_priv._js_str = g_strdup (src->d._js_str);
+		changed_flags |= nm_team_attribute_to_flags (NM_TEAM_ATTRIBUTE_CONFIG);
+	} else if (changed_flags != 0)
+		changed_flags |= nm_team_attribute_to_flags (NM_TEAM_ATTRIBUTE_CONFIG);
+
+	self->_data_priv._js_str_need_synthetize = src->d._js_str_need_synthetize;
+	self->_data_priv.strict_validated = src->d.strict_validated;
+	self->_data_priv.js_str_invalid = src->d.js_str_invalid;
+
+	return changed_flags;
+}
+
+static void
+_variants_list_team_unref_auto (GVariant *(*p_variants)[])
+{
+	int i;
+
+	for (i = 0; i < _NM_TEAM_ATTRIBUTE_NUM; i++)
+		nm_g_variant_unref ((*p_variants)[i]);
+}
+
+gboolean
+nm_team_setting_reset_from_dbus (NMTeamSetting *self,
+                                 GVariant *setting_dict,
+                                 GHashTable *keys,
+                                 guint32 *out_changed,
+                                 guint /* NMSettingParseFlags */ parse_flags,
+                                 GError **error)
+{
+	nm_auto (_variants_list_team_unref_auto) GVariant *variants[_NM_TEAM_ATTRIBUTE_NUM] = { NULL, };
+	gs_unref_ptrarray GPtrArray *v_link_watchers = NULL;
+	const TeamAttrData *attr_data;
+	GVariantIter iter;
+	const char *v_key;
+	GVariant *v_val;
+
+	*out_changed = 0;
+
+	g_variant_iter_init (&iter, setting_dict);
+	while (g_variant_iter_next (&iter, "{&sv}", &v_key, &v_val)) {
+		_nm_unused gs_unref_variant GVariant *v_val_free = v_val;
+		const GVariantType *variant_type = NULL;
+
+		attr_data = _team_attr_data_find_for_property_name (self->d.is_port, v_key);
+		if (!attr_data) {
+			/* _nm_setting_new_from_dbus() already checks for unknown keys. Don't
+			 * do that here. */
+			continue;
+		}
+
+		if (keys)
+			g_hash_table_remove (keys, v_key);
+
+		if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC)
+			variant_type = nm_value_type_get_variant_type (attr_data->value_type);
+		else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_CONFIG)
+			variant_type = G_VARIANT_TYPE_STRING;
+		else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS)
+			variant_type = G_VARIANT_TYPE ("aa{sv}");
+		else if (   !self->d.is_port
+		         && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH)
+			variant_type = G_VARIANT_TYPE_STRING_ARRAY;
+		else
+			nm_assert_not_reached ();
+
+		if (!g_variant_is_of_type (v_val, variant_type)) {
+			if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				             _("invalid D-Bus type \"%s\""),
+				             g_variant_get_type_string (v_val));
+				_team_setting_prefix_error_plain (self->d.is_port,
+				                                  attr_data->property_name,
+				                                  error);
+				return FALSE;
+			}
+			continue;
+		}
+
+		/* _nm_setting_new_from_dbus() already checks for duplicate keys. Don't
+		 * do that here. */
+		nm_g_variant_unref (variants[attr_data->team_attr]);
+		variants[attr_data->team_attr] = g_steal_pointer (&v_val_free);
+	}
+
+	if (variants[NM_TEAM_ATTRIBUTE_LINK_WATCHERS]) {
+
+		if (   variants[NM_TEAM_ATTRIBUTE_CONFIG]
+		    && WITH_JSON_VALIDATION
+		    && !NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+			/* we don't require the content of the "link-watchers" and we also
+			 * don't perform strict validation. No need to parse it. */
+		} else {
+			gs_free_error GError *local = NULL;
+
+			/* We might need the parsed v_link_watchers array below (because there is no JSON
+			 * "config" present or because we don't build WITH_JSON_VALIDATION).
+			 *
+			 * Or we might run with NM_SETTING_PARSE_FLAGS_STRICT. In that mode, we may not necessarily
+			 * require that the entire setting as a whole validates (if a JSON config is present and
+			 * we are not "strict_validated") , but we require that we can at least parse the link watchers
+			 * on their own. */
+			v_link_watchers = _nm_utils_team_link_watchers_from_variant (variants[NM_TEAM_ATTRIBUTE_LINK_WATCHERS],
+			                                                             NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT),
+			                                                             &local);
+			if (   local
+			    && NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
+				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				             _("invalid link-watchers: %s"),
+				             local->message);
+				_team_setting_prefix_error (self,
+				                            NM_SETTING_TEAM_LINK_WATCHERS,
+				                            NM_SETTING_TEAM_PORT_LINK_WATCHERS,
+				                            error);
+				return FALSE;
+			}
+		}
+	}
+
+	*out_changed |= nm_team_setting_config_set (self,
+	                                              variants[NM_TEAM_ATTRIBUTE_CONFIG]
+	                                            ? g_variant_get_string (variants[NM_TEAM_ATTRIBUTE_CONFIG], NULL)
+	                                            : NULL);
+
+	if (   WITH_JSON_VALIDATION
+	    && variants[NM_TEAM_ATTRIBUTE_CONFIG]) {
+		/* for team settings, the JSON must be able to express all possible options. That means,
+		 * if the GVariant contains both the JSON "config" and other options, then the other options
+		 * are silently ignored. */
+	} else {
+		guint32 extra_changed = 0u;
+
+		for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
+			NMValueTypUnion val;
+			guint32 changed_flags = 0u;
+
+			if (!_team_attr_data_is_relevant (attr_data, self->d.is_port))
+				continue;
+			if (!variants[attr_data->team_attr])
+				continue;
+
+			if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) {
+				nm_value_type_get_from_variant (attr_data->value_type, &val, variants[attr_data->team_attr], FALSE);
+				changed_flags = _team_setting_value_set (self,
+				                                         attr_data,
+				                                         &val,
+				                                         SET_FIELD_MODE_SET,
+				                                         RESET_JSON_NO);
+			} else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) {
+				changed_flags = _team_setting_value_link_watchers_set_list (self,
+				                                                            v_link_watchers ? (const NMTeamLinkWatcher *const *) v_link_watchers->pdata : NULL,
+				                                                            v_link_watchers ? v_link_watchers->len : 0u,
+				                                                            SET_FIELD_MODE_SET,
+				                                                            RESET_JSON_NO);
+			} else if (   !self->d.is_port
+			           && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
+				gs_free const char **strv = NULL;
+				gsize len;
+
+				strv = g_variant_get_strv (variants[attr_data->team_attr], &len);
+				changed_flags = _team_setting_value_master_runner_tx_hash_set_list (self,
+				                                                                    strv,
+				                                                                    NM_MIN (len, (gsize) G_MAXUINT),
+				                                                                    SET_FIELD_MODE_SET,
+				                                                                    RESET_JSON_NO);
+			} else
+				nm_assert_not_reached ();
+
+			extra_changed |= changed_flags;
+		}
+
+		if (!variants[NM_TEAM_ATTRIBUTE_CONFIG]) {
+			/* clear the JSON string so it can be regenerated. But only if we didn't set
+			 * it above. */
+			self->_data_priv.strict_validated = TRUE;
+			self->_data_priv._js_str_need_synthetize = TRUE;
+		}
+
+		*out_changed |= extra_changed;
+	}
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_team_setting_maybe_changed (NMSetting *source,
+                               const GParamSpec *const*obj_properties,
+                               guint32 changed_flags)
+{
+	NMTeamAttribute team_attr;
+	int count_flags;
+	guint32 ch;
+
+	if (changed_flags == 0u)
+		return FALSE;
+
+	count_flags = 0;
+	for (ch = changed_flags; ch != 0u; ch >>= 1) {
+		if (NM_FLAGS_HAS (ch, 0x1u))
+			count_flags++;
+	}
+
+	if (count_flags > 1)
+		g_object_freeze_notify (G_OBJECT (source));
+
+	ch = changed_flags;
+	for (team_attr = 0; team_attr < _NM_TEAM_ATTRIBUTE_NUM; team_attr++) {
+		if (!NM_FLAGS_ANY (ch, nm_team_attribute_to_flags (team_attr)))
+			continue;
+		g_object_notify_by_pspec (G_OBJECT (source),
+		                          (GParamSpec *) obj_properties[team_attr]);
+		ch &= ~nm_team_attribute_to_flags (team_attr);
+		if (ch == 0)
+			break;
+	}
+
+	if (count_flags > 1)
+		g_object_thaw_notify (G_OBJECT (source));
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+NMTeamSetting *
+_nm_setting_get_team_setting (struct _NMSetting *setting)
+{
+	if (NM_IS_SETTING_TEAM (setting))
+		return _nm_setting_team_get_team_setting (NM_SETTING_TEAM (setting));
+	return _nm_setting_team_port_get_team_setting (NM_SETTING_TEAM_PORT (setting));
+}
+
+GVariant *
+_nm_team_settings_property_to_dbus (const NMSettInfoSetting *sett_info,
+                                    guint property_idx,
+                                    NMConnection *connection,
+                                    NMSetting *setting,
+                                    NMConnectionSerializationFlags flags,
+                                    const NMConnectionSerializationOptions *options)
+{
+	NMTeamSetting *self = _nm_setting_get_team_setting (setting);
+	const TeamAttrData *attr_data = _team_attr_data_get (self->d.is_port, sett_info->property_infos[property_idx].param_spec->param_id);
+
+	if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_CONFIG) {
+		const char *config;
+
+		if (   self->d.strict_validated
+		    && !_nm_utils_is_manager_process) {
+			/* if we are in strict validating mode on the client side, the JSON is generated
+			 * artificially. In this case, don't send the config via D-Bus to the server.
+			 *
+			 * This also will cause NetworkManager to strictly validate the settings.
+			 * If a JSON "config" is present, strict validation won't be performed. */
+			return NULL;
+		}
+
+		config = nm_team_setting_config_get (self);
+		return config ? g_variant_new_string (config) : NULL;
+	}
+
+	if (!_team_setting_has_field (self, attr_data))
+		return NULL;
+
+	if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) {
+		return nm_value_type_to_variant (attr_data->value_type,
+	                                     _team_setting_get_field (self, attr_data));
+	}
+	if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS)
+		return _nm_utils_team_link_watchers_to_variant (self->d.link_watchers);
+	if (   !self->d.is_port
+	    && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
+		return g_variant_new_strv (self->d.master.runner_tx_hash ? (const char *const*) self->d.master.runner_tx_hash->pdata : NULL,
+		                           self->d.master.runner_tx_hash ? self->d.master.runner_tx_hash->len : 0u);
+	}
+
+	nm_assert_not_reached ();
+	return NULL;
+}
+
+void
+_nm_team_settings_property_from_dbus_link_watchers (GVariant *dbus_value,
+                                                    GValue *prop_value)
+{
+	g_value_take_boxed (prop_value,
+	                    _nm_utils_team_link_watchers_from_variant (dbus_value, FALSE, NULL));
+}
+
+/*****************************************************************************/
+
+NMTeamSetting *
+nm_team_setting_new (gboolean is_port,
+                     const char *js_str)
+{
+	NMTeamSetting *self;
+	gsize l;
+
+	G_STATIC_ASSERT_EXPR (sizeof (*self) == sizeof (self->_data_priv));
+	G_STATIC_ASSERT_EXPR (sizeof (*self) == NM_CONST_MAX (nm_offsetofend (NMTeamSetting, d.master), nm_offsetofend (NMTeamSetting, d.port)));
+
+	l =   is_port
+	    ? nm_offsetofend (NMTeamSetting, d.port)
+	    : nm_offsetofend (NMTeamSetting, d.master);
+
+	self = g_malloc0 (l);
+
+	self->_data_priv.is_port                 = is_port;
+	self->_data_priv.strict_validated        = TRUE;
+	self->_data_priv._js_str_need_synthetize = FALSE;
+	self->_data_priv.link_watchers           = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref);
+
+	_team_setting_ASSERT (self);
+
+	nm_team_setting_config_set (self, js_str);
+
+	_team_setting_ASSERT (self);
+
+	return self;
+}
+
+void
+nm_team_setting_free (NMTeamSetting *self)
+{
+	if (!self)
+		return;
+
+	_team_setting_ASSERT (self);
+
+	if (!self->d.is_port) {
+		nm_clear_pointer (((GPtrArray **) &self->_data_priv.master.runner_tx_hash), g_ptr_array_unref);
+		g_free ((char *) self->_data_priv.master.runner);
+		g_free ((char *) self->_data_priv.master.runner_hwaddr_policy);
+		g_free ((char *) self->_data_priv.master.runner_tx_balancer);
+		g_free ((char *) self->_data_priv.master.runner_agg_select_policy);
+	}
+	g_ptr_array_unref ((GPtrArray *) self->_data_priv.link_watchers);
+	g_free ((char *) self->_data_priv._js_str);
+	g_free (self);
+}
diff --git a/libnm-core/nm-team-utils.h b/libnm-core/nm-team-utils.h
new file mode 100644
index 00000000..7da42a3f
--- /dev/null
+++ b/libnm-core/nm-team-utils.h
@@ -0,0 +1,307 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_TEAM_UITLS_H__
+#define __NM_TEAM_UITLS_H__
+
+#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE)
+#error Cannot use this header.
+#endif
+
+#include "nm-glib-aux/nm-value-type.h"
+
+struct _NMSetting;
+
+struct NMTeamLinkWatcher;
+
+typedef enum {
+
+	_NM_TEAM_ATTRIBUTE_0            = 0,
+	NM_TEAM_ATTRIBUTE_CONFIG        = 1,
+	NM_TEAM_ATTRIBUTE_LINK_WATCHERS = 2,
+
+	_NM_TEAM_ATTRIBUTE_START        = 3,
+
+	NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT = _NM_TEAM_ATTRIBUTE_START,
+	NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL,
+	NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT,
+	NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL,
+	NM_TEAM_ATTRIBUTE_MASTER_RUNNER,
+	NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY,
+	NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH,
+	NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER,
+	NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL,
+	NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE,
+	NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE,
+	NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO,
+	NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS,
+	NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY,
+	_NM_TEAM_ATTRIBUTE_MASTER_NUM,
+
+	NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID = _NM_TEAM_ATTRIBUTE_START,
+	NM_TEAM_ATTRIBUTE_PORT_PRIO,
+	NM_TEAM_ATTRIBUTE_PORT_STICKY,
+	NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO,
+	NM_TEAM_ATTRIBUTE_PORT_LACP_KEY,
+	_NM_TEAM_ATTRIBUTE_PORT_NUM,
+
+	_NM_TEAM_ATTRIBUTE_NUM = NM_CONST_MAX (_NM_TEAM_ATTRIBUTE_MASTER_NUM, _NM_TEAM_ATTRIBUTE_PORT_NUM),
+
+} NMTeamAttribute;
+
+static inline guint32
+nm_team_attribute_to_flags (NMTeamAttribute team_attr)
+{
+	nm_assert (_NM_INT_NOT_NEGATIVE (team_attr));
+	nm_assert (team_attr < _NM_TEAM_ATTRIBUTE_NUM);
+	G_STATIC_ASSERT_EXPR (_NM_TEAM_ATTRIBUTE_NUM < 32);
+
+	return ((guint32) 1) << team_attr;
+}
+
+struct _NMTeamSettingData {
+
+	const char *_js_str;
+
+	const GPtrArray *link_watchers;
+
+	/* this means that @_js_str is unset and needs to be created by
+	 * converting the properties to JSON. This flag indicates that
+	 * we need to re-generate the JSON string on-demand (lazily). */
+	bool _js_str_need_synthetize;
+
+	bool strict_validated:1;
+
+	/* indicates tha the JSON is invalid. Usually, we do a very relaxed validation of
+	 * the JSON config, in case !@strict_validated and accept all unknown fields. This
+	 * flag indicates that the JSON value is not even parsable as JSON. nm_connection_verify()
+	 * would reject such a setting. */
+	bool js_str_invalid:1;
+
+	bool is_port:1;
+
+	guint32 has_fields_mask;
+
+	union {
+		struct {
+			const GPtrArray *runner_tx_hash;
+			const char *runner;
+			const char *runner_hwaddr_policy;
+			const char *runner_tx_balancer;
+			const char *runner_agg_select_policy;
+			gint32 notify_peers_count;
+			gint32 notify_peers_interval;
+			gint32 mcast_rejoin_count;
+			gint32 mcast_rejoin_interval;
+			gint32 runner_sys_prio;
+			gint32 runner_min_ports;
+			gint32 runner_tx_balancer_interval;
+			bool runner_active;
+			bool runner_fast_rate;
+		} master;
+		struct {
+			gint32 queue_id;
+			gint32 prio;
+			gint32 lacp_prio;
+			gint32 lacp_key;
+			bool sticky;
+		} port;
+	};
+};
+
+/*****************************************************************************/
+
+typedef struct {
+	union {
+		const struct _NMTeamSettingData d;
+
+		struct _NMTeamSettingData _data_priv;
+	};
+} NMTeamSetting;
+
+NMTeamSetting *nm_team_setting_new (gboolean is_port,
+                                    const char *js_str);
+
+void nm_team_setting_free (NMTeamSetting *self);
+
+NM_AUTO_DEFINE_FCN0 (NMTeamSetting *, _nm_auto_free_team_setting, nm_team_setting_free)
+#define nm_auto_free_team_setting nm_auto (_nm_auto_free_team_setting)
+
+/*****************************************************************************/
+
+const char *nm_team_setting_config_get (const NMTeamSetting *self);
+
+guint32 nm_team_setting_config_set (NMTeamSetting *self, const char *js_str);
+
+/*****************************************************************************/
+
+gconstpointer _nm_team_setting_value_get (const NMTeamSetting *self,
+                                          NMTeamAttribute team_attr,
+                                          NMValueType value_type);
+
+static inline gboolean
+nm_team_setting_value_get_bool (const NMTeamSetting *self,
+                                NMTeamAttribute team_attr)
+{
+	const bool *p;
+
+	p = _nm_team_setting_value_get (self, team_attr, NM_VALUE_TYPE_BOOL);
+	return p ? *p : 0;
+}
+
+static inline gint32
+nm_team_setting_value_get_int32 (const NMTeamSetting *self,
+                                 NMTeamAttribute team_attr)
+{
+	const gint32 *p;
+
+	p = _nm_team_setting_value_get (self, team_attr, NM_VALUE_TYPE_INT32);
+	return p ? *p : 0;
+}
+
+static inline const char *
+nm_team_setting_value_get_string (const NMTeamSetting *self,
+                                  NMTeamAttribute team_attr)
+{
+	const char *const*p;
+
+	p = _nm_team_setting_value_get (self, team_attr, NM_VALUE_TYPE_STRING);
+	return p ? *p : NULL;
+}
+
+/*****************************************************************************/
+
+guint32 nm_team_setting_value_reset (NMTeamSetting *self,
+                                     NMTeamAttribute team_attr,
+                                     gboolean to_default /* or else unset */);
+
+guint32 _nm_team_setting_value_set (NMTeamSetting *self,
+                                    NMTeamAttribute team_attr,
+                                    NMValueType value_type,
+                                    gconstpointer val);
+
+static inline guint32
+nm_team_setting_value_set_bool (NMTeamSetting *self,
+                                NMTeamAttribute team_attr,
+                                gboolean val)
+{
+	const bool bool_val = val;
+
+	return _nm_team_setting_value_set (self, team_attr, NM_VALUE_TYPE_BOOL, &bool_val);
+}
+
+static inline guint32
+nm_team_setting_value_set_int32 (NMTeamSetting *self,
+                                 NMTeamAttribute team_attr,
+                                 gint32 val)
+{
+	return _nm_team_setting_value_set (self, team_attr, NM_VALUE_TYPE_INT32, &val);
+}
+
+static inline guint32
+nm_team_setting_value_set_string (NMTeamSetting *self,
+                                  NMTeamAttribute team_attr,
+                                  const char *arg)
+{
+	return _nm_team_setting_value_set (self, team_attr, NM_VALUE_TYPE_STRING, &arg);
+}
+
+/*****************************************************************************/
+
+guint32 nm_team_setting_value_link_watchers_add (NMTeamSetting *self,
+                                                 const struct NMTeamLinkWatcher *link_watcher);
+
+guint32 nm_team_setting_value_link_watchers_remove (NMTeamSetting *self,
+                                                    guint idx);
+
+guint32 nm_team_setting_value_link_watchers_remove_by_value (NMTeamSetting *self,
+                                                             const struct NMTeamLinkWatcher *link_watcher);
+
+guint32 nm_team_setting_value_link_watchers_set_list (NMTeamSetting *self,
+                                                      const struct NMTeamLinkWatcher *const*arr,
+                                                      guint len);
+
+/*****************************************************************************/
+
+guint32 nm_team_setting_value_master_runner_tx_hash_add (NMTeamSetting *self,
+                                                         const char *txhash);
+
+guint32 nm_team_setting_value_master_runner_tx_hash_remove (NMTeamSetting *self,
+                                                            guint idx);
+
+guint32 nm_team_setting_value_master_runner_tx_hash_set_list (NMTeamSetting *self,
+                                                              const char *const*arr,
+                                                              guint len);
+
+/*****************************************************************************/
+
+gboolean nm_team_setting_verify (const NMTeamSetting *self,
+                                 GError **error);
+
+/*****************************************************************************/
+
+int nm_team_setting_cmp (const NMTeamSetting *self_a,
+                         const NMTeamSetting *self_b,
+                         gboolean ignore_js_str);
+
+guint32 nm_team_setting_reset (NMTeamSetting *self,
+                               const NMTeamSetting *src);
+
+gboolean nm_team_setting_reset_from_dbus (NMTeamSetting *self,
+                                          GVariant *setting_dict,
+                                          GHashTable *keys,
+                                          guint32 *out_changed,
+                                          guint /* NMSettingParseFlags */ parse_flags,
+                                          GError **error);
+
+/*****************************************************************************/
+
+GPtrArray *_nm_utils_team_link_watchers_from_variant (GVariant *value,
+                                                      gboolean strict_parsing,
+                                                      GError **error);
+GVariant  *_nm_utils_team_link_watchers_to_variant (const GPtrArray *link_watchers);
+
+/*****************************************************************************/
+
+gboolean nm_team_setting_maybe_changed (struct _NMSetting *source,
+                                        const GParamSpec *const*obj_properties,
+                                        guint32 changed);
+
+struct _NMSettingTeam;
+struct _NMSettingTeamPort;
+NMTeamSetting *_nm_setting_team_get_team_setting (struct _NMSettingTeam *setting);
+NMTeamSetting *_nm_setting_team_port_get_team_setting (struct _NMSettingTeamPort *setting);
+NMTeamSetting *_nm_setting_get_team_setting (struct _NMSetting *setting);
+
+/*****************************************************************************/
+
+#include "nm-connection.h"
+#include "nm-core-internal.h"
+
+GVariant *_nm_team_settings_property_to_dbus (const NMSettInfoSetting *sett_info,
+                                              guint property_idx,
+                                              NMConnection *connection,
+                                              NMSetting *setting,
+                                              NMConnectionSerializationFlags flags,
+                                              const NMConnectionSerializationOptions *options);
+
+void _nm_team_settings_property_from_dbus_link_watchers (GVariant *dbus_value,
+                                                         GValue *prop_value);
+
+#endif /* __NM_TEAM_UITLS_H__ */
diff --git a/libnm-core/nm-utils-private.h b/libnm-core/nm-utils-private.h
index a1a1369a..22943edd 100644
--- a/libnm-core/nm-utils-private.h
+++ b/libnm-core/nm-utils-private.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -38,6 +37,13 @@ struct _NMVariantAttributeSpec {
 	char str_type;
 };
 
+#define NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(_name, _type, ...) \
+	(&((const NMVariantAttributeSpec) { \
+		.name          = _name, \
+		.type          = _type, \
+		__VA_ARGS__ \
+	}))
+
 gboolean    _nm_utils_string_slist_validate (GSList *list,
                                              const char **valid_values);
 
@@ -55,8 +61,12 @@ gboolean _nm_utils_wps_method_validate (NMSettingWirelessSecurityWpsMethod wps_m
 
 /* D-Bus transform funcs */
 
-GVariant   *_nm_utils_hwaddr_cloned_get (NMSetting     *setting,
-                                         const char    *property);
+GVariant *_nm_utils_hwaddr_cloned_get (const NMSettInfoSetting *sett_info,
+                                       guint property_idx,
+                                       NMConnection *connection,
+                                       NMSetting *setting,
+                                       NMConnectionSerializationFlags flags,
+                                       const NMConnectionSerializationOptions *options);
 gboolean    _nm_utils_hwaddr_cloned_set (NMSetting     *setting,
                                          GVariant      *connection_dict,
                                          const char    *property,
@@ -72,7 +82,8 @@ GVariant *  _nm_utils_hwaddr_cloned_data_synth (const NMSettInfoSetting *sett_in
                                                 guint property_idx,
                                                 NMConnection *connection,
                                                 NMSetting *setting,
-                                                NMConnectionSerializationFlags flags);
+                                                NMConnectionSerializationFlags flags,
+                                                const NMConnectionSerializationOptions *options);
 gboolean    _nm_utils_hwaddr_cloned_data_set (NMSetting *setting,
                                               GVariant *connection_dict,
                                               const char *property,
@@ -93,9 +104,6 @@ void        _nm_utils_bytes_from_dbus   (GVariant *dbus_value,
 
 char *      _nm_utils_hwaddr_canonical_or_invalid (const char *mac, gssize length);
 
-GPtrArray * _nm_utils_team_link_watchers_from_variant (GVariant *value);
-GVariant *  _nm_utils_team_link_watchers_to_variant (GPtrArray *link_watchers);
-
 void        _nm_utils_format_variant_attributes_full (GString *str,
                                                       const NMUtilsNamedValue *values,
                                                       guint num_values,
@@ -103,7 +111,13 @@ void        _nm_utils_format_variant_attributes_full (GString *str,
                                                       char key_value_separator);
 gboolean    _nm_sriov_vf_parse_vlans (NMSriovVF *vf, const char *str, GError **error);
 
-GVariant *  _nm_utils_bridge_vlans_to_dbus (NMSetting *setting, const char *property);
+GVariant *  _nm_utils_bridge_vlans_to_dbus (const NMSettInfoSetting *sett_info,
+                                            guint property_idx,
+                                            NMConnection *connection,
+                                            NMSetting *setting,
+                                            NMConnectionSerializationFlags flags,
+                                            const NMConnectionSerializationOptions *options);
+
 gboolean    _nm_utils_bridge_vlans_from_dbus (NMSetting *setting,
                                               GVariant *connection_dict,
                                               const char *property,
@@ -116,116 +130,4 @@ gboolean    _nm_utils_bridge_vlan_verify_list (GPtrArray *vlans,
                                                const char *setting,
                                                const char *property);
 
-/* JSON to GValue conversion macros */
-
-static inline void
-_nm_auto_unset_and_free_gvalue (GValue **ptr)
-{
-	if (*ptr) {
-		g_value_unset (*ptr);
-		g_free (*ptr);
-	}
-}
-#define nm_auto_unset_and_free_gvalue nm_auto(_nm_auto_unset_and_free_gvalue)
-
-typedef struct {
-	const char *key1;
-	const char *key2;
-	const char *key3;
-	union {
-		int default_int;
-		gboolean default_bool;
-		const char *default_str;
-	};
-} _NMUtilsTeamPropertyKeys;
-
-static inline int
-_nm_utils_json_extract_int (char *conf,
-                            _NMUtilsTeamPropertyKeys key,
-                            gboolean is_port)
-{
-	nm_auto_unset_and_free_gvalue GValue *t_value = NULL;
-
-	t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port);
-	if (   !t_value
-	    || !G_VALUE_HOLDS_INT (t_value))
-		return key.default_int;
-	return g_value_get_int (t_value);
-}
-
-static inline gboolean
-_nm_utils_json_extract_boolean (char *conf,
-                                _NMUtilsTeamPropertyKeys key,
-                                gboolean is_port)
-{
-	nm_auto_unset_and_free_gvalue GValue *t_value = NULL;
-
-	t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port);
-	if (   !t_value
-	    || !G_VALUE_HOLDS_BOOLEAN (t_value))
-		return key.default_bool;
-	return g_value_get_boolean (t_value);
-}
-
-static inline char *
-_nm_utils_json_extract_string (char *conf,
-                               _NMUtilsTeamPropertyKeys key,
-                               gboolean is_port)
-{
-	nm_auto_unset_and_free_gvalue GValue *t_value = NULL;
-
-	t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port);
-	if (   !t_value
-	    || !G_VALUE_HOLDS_STRING (t_value))
-		return g_strdup (key.default_str);
-	return g_value_dup_string (t_value);
-}
-
-static inline char **
-_nm_utils_json_extract_strv (char *conf,
-                             _NMUtilsTeamPropertyKeys key,
-                             gboolean is_port)
-{
-	nm_auto_unset_and_free_gvalue GValue *t_value = NULL;
-
-	t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port);
-	if (   !t_value
-	    || !G_TYPE_CHECK_VALUE_TYPE (t_value, G_TYPE_STRV))
-		return NULL;
-	return    g_strdupv (g_value_get_boxed (t_value))
-	       ?: g_new0 (char *, 1);
-}
-
-static inline GPtrArray *
-_nm_utils_json_extract_ptr_array (char *conf,
-                                  _NMUtilsTeamPropertyKeys key,
-                                  gboolean is_port)
-{
-	nm_auto_unset_and_free_gvalue GValue *t_value = NULL;
-	GPtrArray *data, *ret;
-	guint i;
-
-	ret = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref);
-
-	t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port);
-	if (   !t_value
-	    || !G_TYPE_CHECK_VALUE_TYPE (t_value, G_TYPE_PTR_ARRAY))
-		return ret;
-
-	data = g_value_get_boxed (t_value);
-	if (!data)
-		return ret;
-	for (i = 0; i < data->len; i++)
-		g_ptr_array_add (ret, nm_team_link_watcher_dup (data->pdata[i]));
-	return ret;
-}
-
-static inline void
-_nm_utils_json_append_gvalue (char **conf,
-                              _NMUtilsTeamPropertyKeys key,
-                              const GValue *val)
-{
-	_nm_utils_team_config_set (conf, key.key1, key.key2, key.key3, val);
-}
-
 #endif
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c
index 04d5b1b5..55b004c8 100644
--- a/libnm-core/nm-utils.c
+++ b/libnm-core/nm-utils.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -38,6 +36,7 @@
 #endif
 
 #include "nm-glib-aux/nm-enum-utils.h"
+#include "nm-glib-aux/nm-time-utils.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"
@@ -851,8 +850,7 @@ _nm_utils_strdict_to_dbus (const GValue *prop_value)
 		} while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value));
 		nm_assert (i == len);
 
-		g_qsort_with_data (idx, len, sizeof (idx[0]),
-		                   nm_utils_named_entry_cmp_with_data, NULL);
+		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);
@@ -958,43 +956,51 @@ _nm_utils_bytes_from_dbus (GVariant *dbus_value,
 	g_value_take_boxed (prop_value, bytes);
 }
 
+/*****************************************************************************/
+
 GSList *
 _nm_utils_strv_to_slist (char **strv, gboolean deep_copy)
 {
-	int i;
 	GSList *list = NULL;
+	gsize i;
 
-	if (strv) {
-		if (deep_copy) {
-			for (i = 0; strv[i]; i++)
-				list = g_slist_prepend (list, g_strdup (strv[i]));
-		} else {
-			for (i = 0; strv[i]; i++)
-				list = g_slist_prepend (list, strv[i]);
-		}
-	}
+	if (!strv)
+		return NULL;
 
+	if (deep_copy) {
+		for (i = 0; strv[i]; i++)
+			list = g_slist_prepend (list, g_strdup (strv[i]));
+	} else {
+		for (i = 0; strv[i]; i++)
+			list = g_slist_prepend (list, strv[i]);
+	}
 	return g_slist_reverse (list);
 }
 
 char **
-_nm_utils_slist_to_strv (GSList *slist, gboolean deep_copy)
+_nm_utils_slist_to_strv (const GSList *slist, gboolean deep_copy)
 {
-	GSList *iter;
+	const GSList *iter;
 	char **strv;
-	int len, i;
+	guint len, i;
 
-	len = g_slist_length (slist);
-	if (!len)
+	if (!slist)
 		return NULL;
+
+	len = g_slist_length ((GSList *) slist);
+
 	strv = g_new (char *, len + 1);
 
 	if (deep_copy) {
-		for (i = 0, iter = slist; iter; iter = iter->next, i++)
+		for (i = 0, iter = slist; iter; iter = iter->next, i++) {
+			nm_assert (iter->data);
 			strv[i] = g_strdup (iter->data);
+		}
 	} else {
-		for (i = 0, iter = slist; iter; iter = iter->next, i++)
+		for (i = 0, iter = slist; iter; iter = iter->next, i++) {
+			nm_assert (iter->data);
 			strv[i] = iter->data;
+		}
 	}
 	strv[i] = NULL;
 
@@ -1005,9 +1011,11 @@ GPtrArray *
 _nm_utils_strv_to_ptrarray (char **strv)
 {
 	GPtrArray *ptrarray;
-	int i;
+	gsize i, l;
+
+	l = NM_PTRARRAY_LEN (strv);
 
-	ptrarray = g_ptr_array_new_with_free_func (g_free);
+	ptrarray = g_ptr_array_new_full (l, g_free);
 
 	if (strv) {
 		for (i = 0; strv[i]; i++)
@@ -1018,10 +1026,10 @@ _nm_utils_strv_to_ptrarray (char **strv)
 }
 
 char **
-_nm_utils_ptrarray_to_strv (GPtrArray *ptrarray)
+_nm_utils_ptrarray_to_strv (const GPtrArray *ptrarray)
 {
 	char **strv;
-	int i;
+	guint i;
 
 	if (!ptrarray)
 		return g_new0 (char *, 1);
@@ -1035,6 +1043,8 @@ _nm_utils_ptrarray_to_strv (GPtrArray *ptrarray)
 	return strv;
 }
 
+/*****************************************************************************/
+
 static gboolean
 device_supports_ap_ciphers (guint32 dev_caps,
                             guint32 ap_flags,
@@ -2280,41 +2290,80 @@ _nm_utils_string_append_tc_parent (GString *string, const char *prefix, guint32
 guint32
 _nm_utils_parse_tc_handle (const char *str, GError **error)
 {
-	gint64 maj, min;
-	char *sep;
+	gint64 maj;
+	gint64 min = 0;
+	const char *sep;
 
-	maj = g_ascii_strtoll (str, &sep, 0x10);
-	if (*sep == ':')
-		min = g_ascii_strtoll (&sep[1], &sep, 0x10);
-	else
-		min = 0;
+	nm_assert (str);
+
+	maj = g_ascii_strtoll (str, (char **) &sep, 0x10);
+	if (sep == str)
+		goto fail;
+
+	sep = nm_str_skip_leading_spaces (sep);
+
+	if (sep[0] == ':') {
+		const char *str2 = &sep[1];
+
+		min = g_ascii_strtoll (str2, (char **) &sep, 0x10);
+		sep = nm_str_skip_leading_spaces (sep);
+		if (sep[0] != '\0')
+			goto fail;
+	} else if (sep[0] != '\0')
+		goto fail;
 
-	if (*sep != '\0' || maj <= 0 || maj > 0xffff || min < 0 || min > 0xffff) {
-		g_set_error (error, 1, 0, _("'%s' is not a valid handle."), str);
-		return TC_H_UNSPEC;
+	if (   maj <= 0
+	    || maj > 0xffff
+	    || min < 0
+	    || min > 0xffff
+	    || !NM_STRCHAR_ALL (str, ch, (   g_ascii_isxdigit (ch)
+	                                  || ch == ':'
+	                                  || g_ascii_isspace (ch)))) {
+		goto fail;
 	}
 
-	return TC_H_MAKE (maj << 16, min);
+	return TC_H_MAKE (((guint32) maj) << 16, (guint32) min);
+fail:
+	nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, _("'%s' is not a valid handle."), str);
+	return TC_H_UNSPEC;
 }
 
-#define TC_ATTR_SPEC_PTR(name, type, no_value, consumes_rest, str_type) \
-	&(NMVariantAttributeSpec) { name, type, FALSE, FALSE, no_value, consumes_rest, str_type }
+static const NMVariantAttributeSpec *const tc_object_attribute_spec[] = {
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("root",   G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE,                                         ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("parent", G_VARIANT_TYPE_STRING,                                           .str_type = 'a', ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("handle", G_VARIANT_TYPE_STRING,                                           .str_type = 'a', ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("kind",   G_VARIANT_TYPE_STRING,  .no_value = TRUE,                        .str_type = 'a', ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("",       G_VARIANT_TYPE_STRING,  .no_value = TRUE, .consumes_rest = TRUE, .str_type = 'a', ),
+	NULL,
+};
+
+static const NMVariantAttributeSpec *const tc_qdisc_fq_codel_spec[] = {
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("limit",        G_VARIANT_TYPE_UINT32,                    ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("flows",        G_VARIANT_TYPE_UINT32,                    ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("target",       G_VARIANT_TYPE_UINT32,                    ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("interval",     G_VARIANT_TYPE_UINT32,                    ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("quantum",      G_VARIANT_TYPE_UINT32,                    ),
 
-static const NMVariantAttributeSpec * const tc_object_attribute_spec[] = {
-	TC_ATTR_SPEC_PTR ("root",    G_VARIANT_TYPE_BOOLEAN, TRUE,  FALSE, 0   ),
-	TC_ATTR_SPEC_PTR ("parent",  G_VARIANT_TYPE_STRING,  FALSE, FALSE, 'a' ),
-	TC_ATTR_SPEC_PTR ("handle",  G_VARIANT_TYPE_STRING,  FALSE, FALSE, 'a' ),
-	TC_ATTR_SPEC_PTR ("kind",    G_VARIANT_TYPE_STRING,  TRUE,  FALSE, 'a' ),
-	TC_ATTR_SPEC_PTR ("",        G_VARIANT_TYPE_STRING,  TRUE,  TRUE,  'a' ),
+	/* 0x83126E97u is not a valid value (it means "disabled"). We should reject that
+	 * value. Or alternatively, reject all values >= MAX_INT(32). */
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("ce_threshold", G_VARIANT_TYPE_UINT32,                    ),
+
+	/* kernel clamps the value at 2^31. Possibly such values should be rejected from configuration
+	 * as they cannot be configured. Leaving the attribute unspecified causes kernel to choose
+	 * a default (currently 32MB). */
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("memory_limit", G_VARIANT_TYPE_UINT32,                    ),
+
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("ecn",          G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ),
 	NULL,
 };
 
 typedef struct {
 	const char *kind;
-	const NMVariantAttributeSpec * const *attrs;
+	const NMVariantAttributeSpec *const *attrs;
 } NMQdiscAttributeSpec;
 
 static const NMQdiscAttributeSpec *const tc_qdisc_attribute_spec[] = {
+	&(const NMQdiscAttributeSpec) { "fq_codel", tc_qdisc_fq_codel_spec },
 	NULL,
 };
 
@@ -2524,14 +2573,23 @@ nm_utils_tc_qdisc_from_str (const char *str, GError **error)
 
 /*****************************************************************************/
 
-static const NMVariantAttributeSpec * const tc_action_simple_attribute_spec[] = {
-	TC_ATTR_SPEC_PTR ("sdata",   G_VARIANT_TYPE_BYTESTRING,  FALSE, FALSE, 0   ),
+static const NMVariantAttributeSpec *const tc_action_simple_attribute_spec[] = {
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("sdata", G_VARIANT_TYPE_BYTESTRING, ),
+	NULL,
+};
+
+static const NMVariantAttributeSpec *const tc_action_mirred_attribute_spec[] = {
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("egress",   G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("ingress",  G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("mirror",   G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("redirect", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE,                  ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("dev",      G_VARIANT_TYPE_STRING,  .no_value = TRUE, .str_type = 'a', ),
 	NULL,
 };
 
-static const NMVariantAttributeSpec * const tc_action_attribute_spec[] = {
-	TC_ATTR_SPEC_PTR ("kind",    G_VARIANT_TYPE_STRING,      TRUE,  FALSE, 'a' ),
-	TC_ATTR_SPEC_PTR ("",        G_VARIANT_TYPE_STRING,      TRUE,  TRUE,  'a' ),
+static const NMVariantAttributeSpec *const tc_action_attribute_spec[] = {
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("kind",    G_VARIANT_TYPE_STRING, .no_value = TRUE,                        .str_type = 'a', ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("",        G_VARIANT_TYPE_STRING, .no_value = TRUE, .consumes_rest = TRUE, .str_type = 'a', ),
 	NULL,
 };
 
@@ -2600,7 +2658,7 @@ nm_utils_tc_action_from_str (const char *str, GError **error)
 	gs_unref_hashtable GHashTable *ht = NULL;
 	gs_unref_hashtable GHashTable *options = NULL;
 	GVariant *variant;
-	const NMVariantAttributeSpec * const *attrs;
+	const NMVariantAttributeSpec *const *attrs;
 
 	nm_assert (str);
 	nm_assert (!error || !*error);
@@ -2623,6 +2681,8 @@ nm_utils_tc_action_from_str (const char *str, GError **error)
 	kind = g_variant_get_string (variant, NULL);
 	if (strcmp (kind, "simple") == 0)
 		attrs = tc_action_simple_attribute_spec;
+	else if (strcmp (kind, "mirred") == 0)
+		attrs = tc_action_mirred_attribute_spec;
 	else
 		attrs = NULL;
 
@@ -2726,9 +2786,9 @@ nm_utils_tc_tfilter_to_str (NMTCTfilter *tfilter, GError **error)
 	return g_string_free (string, FALSE);
 }
 
-static const NMVariantAttributeSpec * const tc_tfilter_attribute_spec[] = {
-	TC_ATTR_SPEC_PTR ("action",  G_VARIANT_TYPE_BOOLEAN,     TRUE,  FALSE, 0   ),
-	TC_ATTR_SPEC_PTR ("",        G_VARIANT_TYPE_STRING,      TRUE,  TRUE,  'a' ),
+static const NMVariantAttributeSpec *const tc_tfilter_attribute_spec[] = {
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("action", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE,                                         ),
+	NM_VARIANT_ATTRIBUTE_SPEC_DEFINE ("",       G_VARIANT_TYPE_STRING,  .no_value = TRUE, .consumes_rest = TRUE, .str_type = 'a', ),
 	NULL,
 };
 
@@ -3665,6 +3725,25 @@ nm_utils_wifi_freq_to_channel (guint32 freq)
 }
 
 /**
+ * nm_utils_wifi_freq_to_band:
+ * @freq: frequency
+ *
+ * Utility function to translate a Wi-Fi frequency to its corresponding band.
+ *
+ * Returns: the band containing the frequency or NULL if freq is invalid
+ **/
+const char *
+nm_utils_wifi_freq_to_band (guint32 freq)
+{
+	if (freq >= 4915 && freq <= 5825)
+		return "a";
+	else if (freq >= 2412 && freq <= 2484)
+		return "bg";
+
+	return NULL;
+}
+
+/**
  * nm_utils_wifi_channel_to_freq:
  * @channel: channel
  * @band: frequency band for wireless ("a" or "bg")
@@ -4257,12 +4336,16 @@ _nm_utils_hwaddr_to_dbus_impl (const char *str)
 }
 
 GVariant *
-_nm_utils_hwaddr_cloned_get (NMSetting     *setting,
-                             const char    *property)
+_nm_utils_hwaddr_cloned_get (const NMSettInfoSetting *sett_info,
+                             guint property_idx,
+                             NMConnection *connection,
+                             NMSetting *setting,
+                             NMConnectionSerializationFlags flags,
+                             const NMConnectionSerializationOptions *options)
 {
 	gs_free char *addr = NULL;
 
-	nm_assert (nm_streq0 (property, "cloned-mac-address"));
+	nm_assert (nm_streq (sett_info->property_infos[property_idx].name, "cloned-mac-address"));
 
 	g_object_get (setting, "cloned-mac-address", &addr, NULL);
 	return _nm_utils_hwaddr_to_dbus_impl (addr);
@@ -4316,7 +4399,8 @@ _nm_utils_hwaddr_cloned_data_synth (const NMSettInfoSetting *sett_info,
                                     guint property_idx,
                                     NMConnection *connection,
                                     NMSetting *setting,
-                                    NMConnectionSerializationFlags flags)
+                                    NMConnectionSerializationFlags flags,
+                                    const NMConnectionSerializationOptions *options)
 {
 	gs_free char *addr = NULL;
 
@@ -4595,7 +4679,7 @@ nm_utils_is_valid_iface_name_utf8safe (const char *utf8safe_name)
 
 /**
  * nm_utils_is_valid_iface_name:
- * @name: Name of interface
+ * @name: (allow-none): Name of interface
  * @error: location to store the error occurring, or %NULL to ignore
  *
  * Validate the network interface name.
@@ -4604,13 +4688,20 @@ nm_utils_is_valid_iface_name_utf8safe (const char *utf8safe_name)
  * function in net/core/dev.c.
  *
  * Returns: %TRUE if interface name is valid, otherwise %FALSE is returned.
+ *
+ * Before 1.20, this function did not accept %NULL as @name argument. If you
+ *   want to run against older versions of libnm, don't pass %NULL.
  */
 gboolean
 nm_utils_is_valid_iface_name (const char *name, GError **error)
 {
 	int i;
 
-	g_return_val_if_fail (name, FALSE);
+	if (!name) {
+		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		                     _("interface name is missing"));
+		return FALSE;
+	}
 
 	if (name[0] == '\0') {
 		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
@@ -4647,13 +4738,16 @@ nm_utils_is_valid_iface_name (const char *name, GError **error)
 
 /**
  * nm_utils_iface_valid_name:
- * @name: Name of interface
+ * @name: (allow-none): Name of interface
  *
  * Validate the network interface name.
  *
  * 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.
+ *
+ * Before 1.20, this function did not accept %NULL as @name argument. If you
+ *   want to run against older versions of libnm, don't pass %NULL.
  */
 gboolean
 nm_utils_iface_valid_name (const char *name)
@@ -4663,11 +4757,14 @@ nm_utils_iface_valid_name (const char *name)
 
 /**
  * nm_utils_is_uuid:
- * @str: a string that might be a UUID
+ * @str: (allow-none): a string that might be a UUID
  *
  * Checks if @str is a UUID
  *
  * Returns: %TRUE if @str is a UUID, %FALSE if not
+ *
+ * In older versions, nm_utils_is_uuid() did not accept %NULL as @str
+ * argument. Don't pass %NULL if you run against older versions of libnm.
  */
 gboolean
 nm_utils_is_uuid (const char *str)
@@ -4675,7 +4772,8 @@ nm_utils_is_uuid (const char *str)
 	const char *p = str;
 	int num_dashes = 0;
 
-	g_return_val_if_fail (str, FALSE);
+	if (!p)
+		return FALSE;
 
 	while (*p) {
 		if (*p == '-')
@@ -5326,429 +5424,6 @@ _nm_utils_is_json_object_no_validation (const char *str, GError **error)
 	return FALSE;
 }
 
-#if WITH_JSON_VALIDATION
-
-static void
-_json_add_object (json_t *json,
-                  const char *key1,
-                  const char *key2,
-                  const char *key3,
-                  json_t *value)
-{
-	json_t *json_element, *json_link;
-
-	json_element = json_object_get (json, key1);
-	if (!json_element) {
-		json_element = value;
-		if (key2) {
-			if (key3) {
-				json_element = json_object ();
-				json_object_set_new (json_element, key3, value);
-			}
-			json_link = json_object ();
-			json_object_set_new (json_link, key2, json_element);
-			json_element = json_link;
-		}
-		json_object_set_new (json, key1, json_element);
-		return;
-	}
-
-	if (!key2)
-		goto key_already_there;
-
-	json_link = json_element;
-	json_element = json_object_get (json_element, key2);
-	if (!json_element) {
-		json_element = value;
-		if (key3) {
-			json_element = json_object ();
-			json_object_set_new (json_element, key3, value);
-		}
-		json_object_set_new (json_link, key2, json_element);
-		return;
-	}
-
-	if (!key3)
-		goto key_already_there;
-
-	json_link = json_element;
-	json_element = json_object_get (json_element, key3);
-	if (!json_element) {
-		json_object_set_new (json_link, key3, value);
-		return;
-	}
-
-key_already_there:
-	json_decref (value);
-}
-
-/*
- * Removes the specified key1[.key2.key3] from json.
- * Returns TRUE if json has been modified, FALSE otherwise. */
-static gboolean
-_json_del_object (json_t *json,
-                  const char *key1,
-                  const char *key2,
-                  const char *key3)
-{
-	json_t *json_element = json;
-	json_t *json_link = NULL;
-	const char *iter_key = key1;
-
-	if (key2) {
-		json_link = json;
-		json_element = json_object_get (json, key1);
-		if (!json_element)
-			return FALSE;
-		iter_key = key2;
-	}
-	if (key3) {
-		json_link = json_element;
-		json_element = json_object_get (json_element, key2);
-		if (!json_element)
-			return FALSE;
-		iter_key = key3;
-	}
-
-	if (json_object_del (json_element, iter_key) != 0)
-		return FALSE;
-
-	/* 1st level key only */
-	if (!json_link)
-		return TRUE;
-
-	if (json_object_size (json_element) == 0)
-		json_object_del (json_link, (key3 ? key2 : key1));
-
-	if (key3 && json_object_size (json_link) == 0)
-		json_object_del (json, key1);
-
-	return TRUE;
-}
-
-/* Adds in place to json the defaults for missing properties;
- * the "add_implicit" allows to add to the json also the default
- * values used but not shown with teamdctl */
-static void
-_json_team_add_defaults (json_t *json,
-                         gboolean port_config,
-                         gboolean add_implicit)
-{
-	json_t *json_element;
-	const char *runner = NULL;
-
-	if (port_config) {
-		_json_add_object (json, "link_watch", "name", NULL,
-		                  json_string (NM_TEAM_LINK_WATCHER_ETHTOOL));
-		return;
-	}
-
-	/* Retrieve runner or add default one */
-	json_element = json_object_get (json, "runner");
-	if (json_element) {
-		runner = json_string_value (json_object_get (json_element, "name"));
-	} else {
-		json_element = json_object ();
-		json_object_set_new (json, "runner", json_element);
-	}
-	if (!runner) {
-		runner = NM_SETTING_TEAM_RUNNER_DEFAULT;
-		json_object_set_new (json_element, "name", json_string (runner));
-	}
-
-	if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP)) {
-		_json_add_object (json, "notify_peers", "count", NULL,
-		                  json_integer (NM_SETTING_TEAM_NOTIFY_PEERS_COUNT_ACTIVEBACKUP_DEFAULT));
-		_json_add_object (json, "mcast_rejoin", "count", NULL,
-		                  json_integer (NM_SETTING_TEAM_NOTIFY_MCAST_COUNT_ACTIVEBACKUP_DEFAULT));
-	} else if (   nm_streq (runner, NM_SETTING_TEAM_RUNNER_LOADBALANCE)
-	           || nm_streq (runner, NM_SETTING_TEAM_RUNNER_LACP)) {
-		json_element = json_array ();
-		json_array_append_new (json_element, json_string ("eth"));
-		json_array_append_new (json_element, json_string ("ipv4"));
-		json_array_append_new (json_element, json_string ("ipv6"));
-		_json_add_object (json, "runner", "tx_hash", NULL, json_element);
-	}
-
-	if (!add_implicit)
-		return;
-
-	if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP))
-		_json_add_object (json, "runner", "hwaddr_policy", NULL, json_string ("same_all"));
-	else if (NM_IN_STRSET (runner,
-	                       NM_SETTING_TEAM_RUNNER_LOADBALANCE,
-	                       NM_SETTING_TEAM_RUNNER_LACP)) {
-		_json_add_object (json, "runner", "tx_balancer", "balancing_interval",
-		                  json_integer (NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL_DEFAULT));
-		if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_LACP)) {
-			_json_add_object (json, "runner", "active", NULL, json_boolean (TRUE));
-			_json_add_object (json, "runner", "sys_prio", NULL,
-			                  json_integer (NM_SETTING_TEAM_RUNNER_SYS_PRIO_DEFAULT));
-			_json_add_object (json, "runner", "min_ports", NULL, json_integer (0));
-			_json_add_object (json, "runner", "agg_select_policy", NULL,
-			                  json_string (NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY_DEFAULT));
-		}
-	}
-}
-
-static json_t *
-_json_find_object (json_t *json,
-                   const char *key1,
-                   const char *key2,
-                   const char *key3)
-{
-	json_t *json_element;
-
-	if (!key1)
-		return NULL;
-	json_element = json_object_get (json, key1);
-	if (!key2 || !json_element)
-		return json_element;
-
-	json_element = json_object_get (json_element, key2);
-	if (!key3 || !json_element)
-		return json_element;
-
-	json_element = json_object_get (json_element, key3);
-	return json_element;
-}
-
-static void
-_json_delete_object_on_int_match (json_t *json,
-                                  const char *key1,
-                                  const char *key2,
-                                  const char *key3,
-                                  int val)
-{
-	json_t *json_element;
-
-	json_element = _json_find_object (json, key1, key2, key3);
-	if (!json_element || !json_is_integer (json_element))
-		return;
-	if (json_integer_value (json_element) == val)
-		_json_del_object (json, key1, key2, key3);
-}
-
-static void
-_json_delete_object_on_bool_match (json_t *json,
-                                   const char *key1,
-                                   const char *key2,
-                                   const char *key3,
-                                   gboolean val)
-{
-	json_t *json_element;
-
-	json_element = _json_find_object (json, key1, key2, key3);
-	if (!json_element || !json_is_boolean (json_element))
-		return;
-	if (json_boolean_value (json_element) == val)
-		_json_del_object (json, key1, key2, key3);
-}
-
-static void
-_json_delete_object_on_string_match (json_t *json,
-                                     const char *key1,
-                                     const char *key2,
-                                     const char *key3,
-                                     const char *val)
-{
-	json_t *json_element;
-
-	json_element = _json_find_object (json, key1, key2, key3);
-	if (!json_element || !json_is_string (json_element))
-		return;
-	if (nm_streq0 (json_string_value (json_element), val))
-		_json_del_object (json, key1, key2, key3);
-}
-
-static void
-_json_team_normalize_defaults (json_t *json, gboolean reset)
-{
-	json_t *json_element;
-	const char *runner = NM_SETTING_TEAM_RUNNER_DEFAULT;
-	gs_free char *runner_free = NULL;
-	int notify_peers_count = 0, notify_peers_interval = 0;
-	int mcast_rejoin_count = 0, mcast_rejoin_interval = 0;
-	int runner_tx_balancer_interval = -1;
-	gboolean runner_active = FALSE, runner_fast_rate = FALSE;
-	int runner_sys_prio = -1, runner_min_ports = -1;
-
-	json_element = _json_find_object (json, "runner", "name", NULL);
-	if (json_element) {
-		runner_free = g_strdup (json_string_value (json_element));
-		runner = runner_free;
-		_json_delete_object_on_string_match (json, "runner", "name", NULL,
-		                                     NM_SETTING_TEAM_RUNNER_DEFAULT);
-	}
-
-	/* the runner changed: clear all the properties. Then team.config will be saved
-	 * and reloaded triggering the reset of the values through _nm_utils_team_config_get
-	 */
-	if (reset) {
-		_json_del_object (json, "notify_peers", "count", NULL);
-		_json_del_object (json, "notify_peers", "interval", NULL);
-		_json_del_object (json, "mcast_rejoin", "count", NULL);
-		_json_del_object (json, "mcast_rejoin", "interval", NULL);
-		_json_del_object (json, "runner", "hwaddr_policy", NULL);
-		_json_del_object (json, "runner", "tx_hash", NULL);
-		_json_del_object (json, "runner", "tx_balancer", "name");
-		_json_del_object (json, "runner", "tx_balancer", "balancing_interval");
-		_json_del_object (json, "runner", "active", NULL);
-		_json_del_object (json, "runner", "fast_rate", NULL);
-		_json_del_object (json, "runner", "sys_prio", NULL);
-		_json_del_object (json, "runner", "min_ports", NULL);
-		_json_del_object (json, "runner", "agg_select_policy", NULL);
-		return;
-	}
-
-	if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP)) {
-		notify_peers_count = 1;
-		mcast_rejoin_count = 1;
-		_json_delete_object_on_string_match (json, "runner", "hwaddr_policy", NULL,
-		                                     NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_DEFAULT);
-	} else if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_LACP)) {
-		runner_tx_balancer_interval = NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL_DEFAULT;
-		runner_active = TRUE;
-		runner_sys_prio = NM_SETTING_TEAM_RUNNER_SYS_PRIO_DEFAULT;
-		runner_min_ports = 0;
-		_json_delete_object_on_string_match (json, "runner", "agg_select_policy", NULL,
-		                                     NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY_DEFAULT);
-	} else if (nm_streq (runner, NM_SETTING_TEAM_RUNNER_LOADBALANCE))
-		runner_tx_balancer_interval = 50;
-
-	_json_delete_object_on_int_match (json, "notify_peers", "count", NULL, notify_peers_count);
-	_json_delete_object_on_int_match (json, "notify_peers", "interval", NULL, notify_peers_interval);
-	_json_delete_object_on_int_match (json, "mcast_rejoin", "count", NULL, mcast_rejoin_count);
-	_json_delete_object_on_int_match (json, "macst_rejoin", "interval", NULL, mcast_rejoin_interval);
-	_json_delete_object_on_int_match (json, "runner", "tx_balancer", "balancing_interval",
-	                                  runner_tx_balancer_interval);
-	_json_delete_object_on_int_match (json, "runner", "sys_prio", NULL, runner_sys_prio);
-	_json_delete_object_on_int_match (json, "runner", "min_ports", NULL, runner_min_ports);
-	_json_delete_object_on_bool_match (json, "runner", "active", NULL, runner_active);
-	_json_delete_object_on_bool_match (json, "runner", "active", NULL, runner_active);
-	_json_delete_object_on_bool_match (json, "runner", "fast_rate", NULL, runner_fast_rate);
-}
-
-static NMTeamLinkWatcher *
-_nm_utils_team_link_watcher_from_json (json_t *json_element)
-{
-	const char *j_key;
-	json_t *j_val;
-	gs_free char *name = NULL, *target_host = NULL, *source_host = NULL;
-	int val1 = 0, val2 = 0, val3 = 3, val4 = -1;
-	NMTeamLinkWatcherArpPingFlags flags = 0;
-
-	g_return_val_if_fail (json_element, NULL);
-
-	json_object_foreach (json_element, j_key, j_val) {
-		if (nm_streq (j_key, "name")) {
-			g_free (name);
-			name = strdup (json_string_value (j_val));
-		} else if (nm_streq (j_key, "target_host")) {
-			g_free (target_host);
-			target_host = strdup (json_string_value (j_val));
-		} else if (nm_streq (j_key, "source_host")) {
-			g_free (source_host);
-			source_host = strdup (json_string_value (j_val));
-		} else if (NM_IN_STRSET (j_key, "delay_up", "init_wait"))
-			val1 = json_integer_value (j_val);
-		else if (NM_IN_STRSET (j_key, "delay_down", "interval"))
-			val2 = json_integer_value (j_val);
-		else if (nm_streq (j_key, "missed_max"))
-			val3 = json_integer_value (j_val);
-		else if (nm_streq (j_key, "vlanid"))
-			val4 = json_integer_value (j_val);
-		else if (nm_streq (j_key, "validate_active")) {
-			if (json_is_true (j_val))
-				flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE;
-		} else if (nm_streq (j_key, "validate_inactive")) {
-			if (json_is_true (j_val))
-				flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE;
-		} else if (nm_streq (j_key, "send_always")) {
-			if (json_is_true (j_val))
-				flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS;
-		}
-	}
-
-	if (nm_streq0 (name, NM_TEAM_LINK_WATCHER_ETHTOOL))
-		return nm_team_link_watcher_new_ethtool (val1, val2, NULL);
-	else if (nm_streq0 (name, NM_TEAM_LINK_WATCHER_NSNA_PING))
-		return nm_team_link_watcher_new_nsna_ping (val1, val2, val3, target_host, NULL);
-	else if (nm_streq0 (name, NM_TEAM_LINK_WATCHER_ARP_PING)) {
-		return nm_team_link_watcher_new_arp_ping2 (val1, val2, val3, val4, target_host,
-		                                           source_host, flags, NULL);
-	} else
-		return NULL;
-}
-
-static json_t *
-_nm_utils_team_link_watcher_to_json (NMTeamLinkWatcher *watcher)
-{
-	const char *name;
-	int int_val;
-	const char *str_val;
-	NMTeamLinkWatcherArpPingFlags flags = 0;
-	json_t *json_element;
-
-	g_return_val_if_fail (watcher, NULL);
-
-	json_element = json_object ();
-	name = nm_team_link_watcher_get_name (watcher);
-	if (!name)
-		goto fail;
-
-	json_object_set_new (json_element, "name", json_string (name));
-
-	if (nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL)) {
-		int_val = nm_team_link_watcher_get_delay_up (watcher);
-		if (int_val)
-			json_object_set_new (json_element, "delay_up", json_integer (int_val));
-		int_val = nm_team_link_watcher_get_delay_down (watcher);
-		if (int_val)
-			json_object_set_new (json_element, "delay_down", json_integer (int_val));
-		return json_element;
-	}
-
-	int_val = nm_team_link_watcher_get_init_wait (watcher);
-	if (int_val)
-		json_object_set_new (json_element, "init_wait", json_integer (int_val));
-	int_val = nm_team_link_watcher_get_interval (watcher);
-	if (int_val)
-		json_object_set_new (json_element, "interval", json_integer (int_val));
-	int_val = nm_team_link_watcher_get_missed_max (watcher);
-	if (int_val != 3)
-		json_object_set_new (json_element, "missed_max", json_integer (int_val));
-	str_val = nm_team_link_watcher_get_target_host (watcher);
-	if (!str_val)
-		goto fail;
-	json_object_set_new (json_element, "target_host", json_string (str_val));
-
-	if (nm_streq (name, NM_TEAM_LINK_WATCHER_NSNA_PING))
-		return json_element;
-
-	int_val = nm_team_link_watcher_get_vlanid (watcher);
-	if (int_val != -1)
-		json_object_set_new (json_element, "vlanid", json_integer (int_val));
-	str_val = nm_team_link_watcher_get_source_host (watcher);
-	if (!str_val)
-		goto fail;
-	json_object_set_new (json_element, "source_host", json_string (str_val));
-
-	flags = nm_team_link_watcher_get_flags (watcher);
-	if (flags & NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE)
-		json_object_set_new (json_element, "validate_active", json_string ("true"));
-	if (flags & NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE)
-		json_object_set_new (json_element, "validate_inactive", json_string ("true"));
-	if (flags & NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS)
-		json_object_set_new (json_element, "send_always", json_string ("true"));
-
-	return json_element;
-
-fail:
-	json_decref (json_element);
-	return NULL;
-}
-
 /**
  * nm_utils_is_json_object:
  * @str: the JSON string to test
@@ -5764,6 +5439,7 @@ fail:
 gboolean
 nm_utils_is_json_object (const char *str, GError **error)
 {
+#if WITH_JSON_VALIDATION
 	json_t *json;
 	json_error_t jerror;
 
@@ -5803,305 +5479,7 @@ nm_utils_is_json_object (const char *str, GError **error)
 
 	json_decref (json);
 	return TRUE;
-}
-
-gboolean
-_nm_utils_team_config_equal (const char *conf1,
-                             const char *conf2,
-                             gboolean port_config)
-{
-	json_t *json1 = NULL, *json2 = NULL, *json;
-	gs_free char *dump1 = NULL, *dump2 = NULL;
-	json_t *value;
-	json_error_t jerror;
-	const char *key;
-	gboolean ret;
-	void *tmp;
-	int i;
-
-	if (nm_streq0 (conf1, conf2))
-		return TRUE;
-	else if (!nm_jansson_load ())
-		return FALSE;
-
-	/* A NULL configuration is equivalent to default value '{}' */
-	json1 = json_loads (conf1 ?: "{}", JSON_REJECT_DUPLICATES, &jerror);
-	if (json1)
-		json2 = json_loads (conf2 ?: "{}", JSON_REJECT_DUPLICATES, &jerror);
-
-	if (!json1 || !json2) {
-		ret = FALSE;
-		goto out;
-	}
-
-	/* Some properties are added by teamd when missing from the initial
-	 * configuration.  Add them with the default value if necessary, depending
-	 * on the configuration type.
-	 */
-	for (i = 0, json = json1; i < 2; i++, json = json2)
-		_json_team_add_defaults (json, port_config, FALSE);
-
-	/* Only consider a given subset of nodes, others can change depending on
-	 * current state */
-	for (i = 0, json = json1; i < 2; i++, json = json2) {
-		json_object_foreach_safe (json, tmp, key, value) {
-			if (!NM_IN_STRSET (key, "runner", "link_watch"))
-				json_object_del (json, key);
-		}
-	}
-
-	dump1 = json_dumps (json1, JSON_INDENT(0) | JSON_ENSURE_ASCII | JSON_SORT_KEYS);
-	dump2 = json_dumps (json2, JSON_INDENT(0) | JSON_ENSURE_ASCII | JSON_SORT_KEYS);
-
-	ret = nm_streq0 (dump1, dump2);
-out:
-
-	if (json1)
-		json_decref (json1);
-	if (json2)
-		json_decref (json2);
-
-	return ret;
-}
-
-GValue *
-_nm_utils_team_config_get (const char *conf,
-                           const char *key,
-                           const char *key2,
-                           const char *key3,
-                           gboolean port_config)
-{
-	json_t *json;
-	json_t *json_element;
-	GValue *value = NULL;
-	json_error_t jerror;
-
-	if (!key)
-		return NULL;
-
-	if (!nm_jansson_load ())
-		return NULL;
-
-	json = json_loads (conf ?: "{}", JSON_REJECT_DUPLICATES, &jerror);
-
-	/* Invalid json in conf */
-	if (!json)
-		return NULL;
-
-	/* Some properties are added by teamd when missing from the initial
-	 * configuration.  Add them with the default value if necessary, depending
-	 * on the configuration type.
-	 * Skip this for port config, as some properties change on the basis of the
-	 * runner specified in the master connection... but we don't want to check
-	 * against properties in another connection. Moreover, for team-port we have
-	 * the link-watchers property only here: and for this compound property it is
-	 * fine to show the default value only if explicitly set.
-	 */
-	if (!port_config)
-		_json_team_add_defaults (json, port_config, TRUE);
-
-	/* Now search the property to retrieve */
-	json_element = json_object_get (json, key);
-	if (json_element && key2)
-		json_element = json_object_get (json_element, key2);
-	if (json_element && key3)
-		json_element = json_object_get (json_element, key3);
-
-	if (json_element) {
-		value = g_new0 (GValue, 1);
-		if (json_is_string (json_element)) {
-			g_value_init (value, G_TYPE_STRING);
-			g_value_set_string (value, json_string_value (json_element));
-		} else if (json_is_integer (json_element)) {
-			g_value_init (value, G_TYPE_INT);
-			g_value_set_int (value, json_integer_value (json_element));
-		} else if (json_is_boolean (json_element)) {
-			g_value_init (value, G_TYPE_BOOLEAN);
-			g_value_set_boolean (value, json_boolean_value (json_element));
-		} else if (nm_streq (key, "link_watch")) {
-			NMTeamLinkWatcher *watcher;
-			GPtrArray *data = g_ptr_array_new_with_free_func
-			                  ((GDestroyNotify) nm_team_link_watcher_unref);
-
-			if (json_is_array (json_element)) {
-				json_t *j_watcher;
-				int index;
-
-				json_array_foreach (json_element, index, j_watcher) {
-					watcher = _nm_utils_team_link_watcher_from_json (j_watcher);
-					if (watcher)
-						g_ptr_array_add (data, watcher);
-				}
-			} else {
-				watcher = _nm_utils_team_link_watcher_from_json (json_element);
-				if (watcher)
-					g_ptr_array_add (data, watcher);
-			}
-			if (data->len) {
-				g_value_init (value, G_TYPE_PTR_ARRAY);
-				g_value_take_boxed (value, data);
-			} else
-				g_ptr_array_free (data, TRUE);
-
-		} else if (json_is_array (json_element)) {
-			GPtrArray *data = g_ptr_array_new_with_free_func (g_free);
-			json_t *str_element;
-			int index;
-
-			json_array_foreach (json_element, index, str_element) {
-				if (json_is_string (str_element))
-					g_ptr_array_add (data, g_strdup (json_string_value (str_element)));
-			}
-			g_ptr_array_add (data, NULL);
-			g_value_init (value, G_TYPE_STRV);
-			g_value_take_boxed (value, g_ptr_array_free (data, FALSE));
-		} else {
-			g_assert_not_reached ();
-			g_free (value);
-			value = NULL;
-		}
-	}
-
-	if (json)
-		json_decref (json);
-
-	return value;
-}
-
-/* if conf is updated in place returns TRUE */
-gboolean
-_nm_utils_team_config_set (char **conf,
-                           const char *key,
-                           const char *key2,
-                           const char *key3,
-                           const GValue *value)
-{
-	nm_auto_decref_json json_t *json = NULL;
-	nm_auto_decref_json json_t *json_value = NULL;
-	json_t *json_element;
-	json_t *json_link;
-	json_error_t jerror;
-	const char *iter_key = key;
-	gs_free char *conf_new = NULL;
-
-	g_return_val_if_fail (key, FALSE);
-
-	if (!nm_jansson_load ())
-		return FALSE;
-
-	json = json_loads (*conf?: "{}", JSON_REJECT_DUPLICATES, &jerror);
-	if (!json)
-		return FALSE;
-
-	if (!value) {
-		if (!_json_del_object (json, key, key2, key3))
-			return FALSE;
-		goto done;
-	}
-
-	if (G_VALUE_HOLDS_STRING (value))
-		json_value = json_string (g_value_get_string (value));
-	else if (G_VALUE_HOLDS_INT (value))
-		json_value = json_integer (g_value_get_int (value));
-	else if (G_VALUE_HOLDS_BOOLEAN (value))
-		json_value = json_boolean (g_value_get_boolean (value));
-	else if (G_VALUE_HOLDS_BOXED (value)) {
-		if (nm_streq (key, "link_watch")) {
-			gboolean has_array = FALSE;
-			GPtrArray *array;
-			guint i;
-
-			array = g_value_get_boxed (value);
-			if (!array || !array->len)
-				return FALSE;
-
-			for (i = 0; i < array->len; i++) {
-				json_t *el;
-
-				el = _nm_utils_team_link_watcher_to_json (array->pdata[i]);
-				if (!el)
-					continue;
-				/* if there is only one watcher, it is added as-is. If there
-				 * are multiple watchers, they are added in an array. */
-				if (!json_value) {
-					json_value = el;
-					continue;
-				}
-				if (!has_array) {
-					json_t *el_arr;
-
-					has_array = TRUE;
-					el_arr = json_array();
-					json_array_append_new (el_arr, json_value);
-					json_value = el_arr;
-				}
-				json_array_append_new (json_value, el);
-			}
-		} else if (   nm_streq (key, "runner")
-		           && nm_streq0 (key2, "tx_hash")) {
-			const char *const*strv;
-			gsize i;
-
-			strv = g_value_get_boxed (value);
-			if (!strv)
-				return FALSE;
-
-			json_value = json_array ();
-			for (i = 0; strv[i]; i++)
-				json_array_append_new (json_value, json_string (strv[i]));
-		} else {
-			nm_assert_not_reached ();
-			return FALSE;
-		}
-
-	} else {  /* G_VALUE_HOLDS_? */
-		nm_assert_not_reached ();
-		return FALSE;
-	}
-
-	/* Simplest case: first level key only */
-	json_element = json;
-	json_link = NULL;
-
-	if (key2) {
-		json_link = json;
-		json_element = json_object_get (json, iter_key);
-		if (!json_element) {
-			json_element = json_object ();
-			json_object_set_new (json_link, iter_key, json_element);
-		}
-		iter_key = key2;
-	}
-	if (key3) {
-		json_link = json_element;
-		json_element = json_object_get (json_link, iter_key);
-		if (!json_element) {
-			json_element = json_object ();
-			json_object_set_new (json_link, iter_key, json_element);
-		}
-		iter_key = key3;
-	}
-
-	json_object_set_new (json_element, iter_key, g_steal_pointer (&json_value));
-
-done:
-	_json_team_normalize_defaults (json, (   nm_streq0 (key, "runner")
-	                                      && nm_streq0 (key2, "name")));
-	conf_new = json_dumps (json, JSON_PRESERVE_ORDER);
-	if (nm_streq0 (conf_new, "{}"))
-		nm_clear_g_free (&conf_new);
-	if (nm_streq0 (conf_new, *conf))
-		return FALSE;
-	g_free (*conf);
-	*conf = g_steal_pointer (&conf_new);
-	return TRUE;
-}
-
 #else /* !WITH_JSON_VALIDATION */
-
-gboolean
-nm_utils_is_json_object (const char *str, GError **error)
-{
 	g_return_val_if_fail (!error || !*error, FALSE);
 
 	if (!str || !str[0]) {
@@ -6113,239 +5491,7 @@ nm_utils_is_json_object (const char *str, GError **error)
 	}
 
 	return _nm_utils_is_json_object_no_validation (str, error);
-}
-
-gboolean
-_nm_utils_team_config_equal (const char *conf1,
-                             const char *conf2,
-                             gboolean port_config)
-{
-	return nm_streq0 (conf1, conf2);
-}
-
-GValue *
-_nm_utils_team_config_get (const char *conf,
-                           const char *key,
-                           const char *key2,
-                           const char *key3,
-                           gboolean port_config)
-{
-	return NULL;
-}
-
-gboolean
-_nm_utils_team_config_set (char **conf,
-                           const char *key,
-                           const char *key2,
-                           const char *key3,
-                           const GValue *value)
-{
-	return FALSE;
-}
 #endif
-
-/**
- * _nm_utils_team_link_watchers_to_variant:
- * @link_watchers: (element-type NMTeamLinkWatcher): array of #NMTeamLinkWatcher
- *
- * Utility function to convert a #GPtrArray of #NMTeamLinkWatcher objects
- * representing link watcher configuration for team devices into a #GVariant
- * of type 'aa{sv}' representing an array of link watchers.
- *
- * Returns: (transfer none): a new floating #GVariant representing link watchers.
- **/
-GVariant *
-_nm_utils_team_link_watchers_to_variant (GPtrArray *link_watchers)
-{
-	GVariantBuilder builder;
-	int i;
-
-	g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
-
-	if (!link_watchers)
-		goto end;
-
-	for (i = 0; i < link_watchers->len; i++) {
-		NMTeamLinkWatcher *watcher = link_watchers->pdata[i];
-		GVariantBuilder watcher_builder;
-		const char *name;
-		int int_val;
-		NMTeamLinkWatcherArpPingFlags flags;
-
-		g_variant_builder_init (&watcher_builder, G_VARIANT_TYPE ("a{sv}"));
-
-		name = nm_team_link_watcher_get_name (watcher);
-		g_variant_builder_add (&watcher_builder, "{sv}",
-		                       "name",
-		                       g_variant_new_string (name));
-
-		if (nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL)) {
-			int_val = nm_team_link_watcher_get_delay_up (watcher);
-			if (int_val) {
-				g_variant_builder_add (&watcher_builder, "{sv}",
-				                       "delay-up",
-				                       g_variant_new_int32 (int_val));
-			}
-			int_val = nm_team_link_watcher_get_delay_down (watcher);
-			if (int_val) {
-				g_variant_builder_add (&watcher_builder, "{sv}",
-				                       "delay-down",
-				                       g_variant_new_int32 (int_val));
-			}
-			g_variant_builder_add (&builder, "a{sv}", &watcher_builder);
-			continue;
-		}
-
-		/* Common properties for arp_ping and nsna_ping link watchers */
-		int_val = nm_team_link_watcher_get_init_wait (watcher);
-		if (int_val) {
-			g_variant_builder_add (&watcher_builder, "{sv}",
-			                       "init-wait",
-			                       g_variant_new_int32 (int_val));
-		}
-		int_val = nm_team_link_watcher_get_interval (watcher);
-		if (int_val) {
-			g_variant_builder_add (&watcher_builder, "{sv}",
-			                       "interval",
-			                       g_variant_new_int32 (int_val));
-		}
-		int_val = nm_team_link_watcher_get_missed_max (watcher);
-		if (int_val != 3) {
-			g_variant_builder_add (&watcher_builder, "{sv}",
-			                       "missed-max",
-			                       g_variant_new_int32 (int_val));
-		}
-		g_variant_builder_add (&watcher_builder, "{sv}",
-		                       "target-host",
-		                       g_variant_new_string (nm_team_link_watcher_get_target_host (watcher)));
-
-		if (nm_streq (name, NM_TEAM_LINK_WATCHER_NSNA_PING)) {
-			g_variant_builder_add (&builder, "a{sv}", &watcher_builder);
-			continue;
-		}
-
-		/* arp_ping watcher only */
-		int_val = nm_team_link_watcher_get_vlanid (watcher);
-		if (int_val != -1) {
-			g_variant_builder_add (&watcher_builder, "{sv}",
-			                       "vlanid",
-			                       g_variant_new_int32 (int_val));
-		}
-		g_variant_builder_add (&watcher_builder, "{sv}",
-		                       "source-host",
-		                       g_variant_new_string (nm_team_link_watcher_get_source_host (watcher)));
-		flags = nm_team_link_watcher_get_flags (watcher);
-		if (flags & NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE) {
-			g_variant_builder_add (&watcher_builder, "{sv}",
-			                       "validate-active",
-			                       g_variant_new_boolean (TRUE));
-		}
-		if (flags & NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE) {
-			g_variant_builder_add (&watcher_builder, "{sv}",
-			                       "validate-inactive",
-			                       g_variant_new_boolean (TRUE));
-		}
-		if (flags & NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS) {
-			g_variant_builder_add (&watcher_builder, "{sv}",
-			                       "send-always",
-			                       g_variant_new_boolean (TRUE));
-		}
-		g_variant_builder_add (&builder, "a{sv}", &watcher_builder);
-	}
-end:
-	return g_variant_builder_end (&builder);
-}
-
-/**
- * _nm_utils_team_link_watchers_from_variant:
- * @value: a #GVariant of type 'aa{sv}'
- *
- * Utility function to convert a #GVariant representing a list of team link
- * watchers int a #GPtrArray of #NMTeamLinkWatcher objects.
- *
- * Returns: (transfer full) (element-type NMTeamLinkWatcher): a newly allocated
- *   #GPtrArray of #NMTeamLinkWatcher objects.
- **/
-GPtrArray *
-_nm_utils_team_link_watchers_from_variant (GVariant *value)
-{
-	GPtrArray *link_watchers;
-	GVariantIter iter;
-	GVariant *watcher_var;
-
-	g_return_val_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}")), NULL);
-
-	link_watchers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref);
-	g_variant_iter_init (&iter, value);
-
-	while (g_variant_iter_next (&iter, "@a{sv}", &watcher_var)) {
-		NMTeamLinkWatcher *watcher;
-		const char *name;
-		int val1, val2, val3 = 0, val4 = -1;
-		const char *target_host = NULL, *source_host = NULL;
-		gboolean bval;
-		NMTeamLinkWatcherArpPingFlags flags = NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE;
-		GError *error = NULL;
-
-		if (!g_variant_lookup (watcher_var, "name", "&s", &name))
-			goto next;
-		if (!NM_IN_STRSET (name,
-		                   NM_TEAM_LINK_WATCHER_ETHTOOL,
-		                   NM_TEAM_LINK_WATCHER_ARP_PING,
-		                   NM_TEAM_LINK_WATCHER_NSNA_PING)) {
-			goto next;
-		}
-
-		if (nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL)) {
-			if (!g_variant_lookup (watcher_var, "delay-up", "i", &val1))
-				val1 = 0;
-			if (!g_variant_lookup (watcher_var, "delay-down", "i", &val2))
-				val2 = 0;
-			watcher = nm_team_link_watcher_new_ethtool (val1, val2, &error);
-		} else {
-			if (!g_variant_lookup (watcher_var, "target-host", "&s", &target_host))
-				goto next;
-			if (!g_variant_lookup (watcher_var, "init_wait", "i", &val1))
-				val1 = 0;
-			if (!g_variant_lookup (watcher_var, "interval", "i", &val2))
-				val2 = 0;
-			if (!g_variant_lookup (watcher_var, "missed-max", "i", &val3))
-				val3 = 3;
-			if (nm_streq (name, NM_TEAM_LINK_WATCHER_ARP_PING)) {
-				if (!g_variant_lookup (watcher_var, "vlanid", "i", &val4))
-					val4 = -1;
-				if (!g_variant_lookup (watcher_var, "source-host", "&s", &source_host))
-					goto next;
-				if (!g_variant_lookup (watcher_var, "validate-active", "b", &bval))
-					bval = FALSE;
-				if (bval)
-					flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE;
-				if (!g_variant_lookup (watcher_var, "validate-inactive", "b", &bval))
-					bval = FALSE;
-				if (bval)
-					flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE;
-				if (!g_variant_lookup (watcher_var, "send-always", "b", &bval))
-					bval = FALSE;
-				if (bval)
-					flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS;
-				watcher = nm_team_link_watcher_new_arp_ping2 (val1, val2, val3, val4,
-				                                              target_host, source_host,
-				                                              flags, &error);
-			} else
-				watcher = nm_team_link_watcher_new_nsna_ping (val1, val2, val3,
-				                                              target_host, &error);
-		}
-		if (!watcher) {
-			g_clear_error (&error);
-			goto next;
-		}
-
-		g_ptr_array_add (link_watchers, watcher);
-next:
-		g_variant_unref (watcher_var);
-	}
-
-	return link_watchers;
 }
 
 static char *
@@ -6415,7 +5561,7 @@ nm_utils_parse_variant_attributes (const char *string,
 	gs_unref_hashtable GHashTable *ht = NULL;
 	const char *ptr = string, *start = NULL, *sep;
 	GVariant *variant;
-	const NMVariantAttributeSpec * const *s;
+	const NMVariantAttributeSpec *const *s;
 
 	g_return_val_if_fail (string, NULL);
 	g_return_val_if_fail (attr_separator, NULL);
@@ -6659,24 +5805,23 @@ nm_utils_format_variant_attributes (GHashTable *attributes,
 gint64
 nm_utils_get_timestamp_msec (void)
 {
-	struct timespec ts;
+	gint64 ts;
 
-	if (clock_gettime (CLOCK_BOOTTIME, &ts) != -1)
-		goto success;
+	ts = nm_utils_clock_gettime_ms (CLOCK_BOOTTIME);
+	if (ts >= 0)
+		return ts;
 
-	if (errno == EINVAL) {
+	if (ts == -EINVAL) {
 		/* The fallback to CLOCK_MONOTONIC is taken only if we're running on a
 		 * 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. */
-		if (clock_gettime (CLOCK_MONOTONIC, &ts) != -1)
-			goto success;
+		ts = nm_utils_clock_gettime_ms (CLOCK_MONOTONIC);
+		if (ts >= 0)
+			return ts;
 	}
 
 	g_return_val_if_reached (-1);
-
-success:
-	return (((gint64) ts.tv_sec) * 1000) + (ts.tv_nsec / 1000000);
 }
 
 /*****************************************************************************/
@@ -6767,13 +5912,21 @@ nm_utils_base64secret_normalize (const char *base64_key,
 }
 
 GVariant *
-_nm_utils_bridge_vlans_to_dbus (NMSetting *setting, const char *property)
+_nm_utils_bridge_vlans_to_dbus (const NMSettInfoSetting *sett_info,
+                                guint property_idx,
+                                NMConnection *connection,
+                                NMSetting *setting,
+                                NMConnectionSerializationFlags flags,
+                                const NMConnectionSerializationOptions *options)
 {
 	gs_unref_ptrarray GPtrArray *vlans = NULL;
 	GVariantBuilder builder;
 	guint i;
+	const char *property_name = sett_info->property_infos[property_idx].name;
+
+	nm_assert (property_name);
 
-	g_object_get (setting, property, &vlans, NULL);
+	g_object_get (setting, property_name, &vlans, NULL);
 	g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
 
 	if (vlans) {
@@ -6928,3 +6081,30 @@ _nm_utils_bridge_vlan_verify_list (GPtrArray *vlans,
 
 	return TRUE;
 }
+
+gboolean
+nm_utils_connection_is_adhoc_wpa (NMConnection *connection)
+{
+	NMSettingWireless *s_wifi;
+	NMSettingWirelessSecurity *s_wsec;
+	const char *key_mgmt;
+	const char *mode;
+
+	s_wifi = nm_connection_get_setting_wireless (connection);
+	if (!s_wifi)
+		return FALSE;
+
+	mode = nm_setting_wireless_get_mode (s_wifi);
+	if (!nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC))
+		return FALSE;
+
+	s_wsec = nm_connection_get_setting_wireless_security (connection);
+	if (!s_wsec)
+		return FALSE;
+
+	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec);
+	if (!nm_streq0 (key_mgmt, "wpa-none"))
+		return FALSE;
+
+	return TRUE;
+}
diff --git a/libnm-core/nm-utils.h b/libnm-core/nm-utils.h
index 2b5baba4..3e54a33c 100644
--- a/libnm-core/nm-utils.h
+++ b/libnm-core/nm-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-version.h b/libnm-core/nm-version.h
index 0ac2955b..ee6a1e7d 100644
--- a/libnm-core/nm-version.h
+++ b/libnm-core/nm-version.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -202,4 +201,18 @@
 # define NM_AVAILABLE_IN_1_18
 #endif
 
+#if NM_VERSION_MIN_REQUIRED >= NM_VERSION_1_20
+# define NM_DEPRECATED_IN_1_20           G_DEPRECATED
+# define NM_DEPRECATED_IN_1_20_FOR(f)    G_DEPRECATED_FOR(f)
+#else
+# define NM_DEPRECATED_IN_1_20
+# define NM_DEPRECATED_IN_1_20_FOR(f)
+#endif
+
+#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_20
+# define NM_AVAILABLE_IN_1_20            G_UNAVAILABLE(1,20)
+#else
+# define NM_AVAILABLE_IN_1_20
+#endif
+
 #endif  /* NM_VERSION_H */
diff --git a/libnm-core/nm-vpn-dbus-interface.h b/libnm-core/nm-vpn-dbus-interface.h
index 4ef80f1d..3c8b19f1 100644
--- a/libnm-core/nm-vpn-dbus-interface.h
+++ b/libnm-core/nm-vpn-dbus-interface.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/libnm-core/nm-vpn-editor-plugin.c b/libnm-core/nm-vpn-editor-plugin.c
index 30213ea0..d465695e 100644
--- a/libnm-core/nm-vpn-editor-plugin.c
+++ b/libnm-core/nm-vpn-editor-plugin.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-vpn-editor-plugin.h b/libnm-core/nm-vpn-editor-plugin.h
index 0bdd9298..2be6c379 100644
--- a/libnm-core/nm-vpn-editor-plugin.h
+++ b/libnm-core/nm-vpn-editor-plugin.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-vpn-plugin-info.c b/libnm-core/nm-vpn-plugin-info.c
index bfcd70a5..462e41a6 100644
--- a/libnm-core/nm-vpn-plugin-info.c
+++ b/libnm-core/nm-vpn-plugin-info.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/nm-vpn-plugin-info.h b/libnm-core/nm-vpn-plugin-info.h
index 61539cbc..08414113 100644
--- a/libnm-core/nm-vpn-plugin-info.h
+++ b/libnm-core/nm-vpn-plugin-info.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/tests/certs/test-tpm2wrapped-key.pem b/libnm-core/tests/certs/test-tpm2wrapped-key.pem
new file mode 100644
index 00000000..f3fd271c
--- /dev/null
+++ b/libnm-core/tests/certs/test-tpm2wrapped-key.pem
@@ -0,0 +1,14 @@
+-----BEGIN TSS2 PRIVATE KEY-----
+MIICEwYGZ4EFCgEDoAMBAQECBQCBAAABBIIBGAEWAAEACwACBEAAAAAQABAIAAAA
+AAABAOJdEXw1LO6JWskHBSYQc1NfJAe9DOAyLA4XbXI5asQ8aNbmL51DP9mQQpqq
+a1CSRZAIuuorMxyRBBAFpF4OZqjNd/Nskp3iMmifr5yqAYZ3M31MqlBFiiyctqKp
+VwIChwsIbKelrsXbty1icP2CH+k4w/nPymPjnfYtgpMe8QW8n6U156ujIdJcISds
+QFcl3nrDnD1IumX0/LfanQrRDVSI+m6szvTrdsPMtGeaNMnlz0gx74auo7/CgEjX
+69xjPvQpNLHO/nV4EHSdfXH3LamcpWO8aEAVne3MFFA9V0bpv7uKoGhRjssD9kSr
+PQzNXfjHpkcOLeH4pzxDMFXDIGUEgeAA3gAgrCL3RRcBryFXToo9ZN3/f4EeeEjK
+58ejYomsxqvckhgAEMxbT26fo2h27b4KPlnUpoiL2JPLB0Xz6PJAF8n0YdJUO381
+xhzPTIQop81BxljTLV2C9WGns5bWDPW9ItEbv4UalEwFfxsW4Ma5smLWn3A1UwVN
+Z1cW/oUx+3nOCF1TZgbMPszToqCPlpIHd9vO709qpSyULIUkZLHS6PUM7ESY5U81
+f4BITxJR+aYaqErni/FDLsDVP0MQ3CuFHeUDHI0uEzzbfurYFjpp9+caaoWuZzpg
+VYV5pjPdgg==
+-----END TSS2 PRIVATE KEY-----
diff --git a/libnm-core/tests/test-compare.c b/libnm-core/tests/test-compare.c
index 7f2ba980..ec4ca437 100644
--- a/libnm-core/tests/test-compare.c
+++ b/libnm-core/tests/test-compare.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/tests/test-crypto.c b/libnm-core/tests/test-crypto.c
index 4ac9679b..9eab29d4 100644
--- a/libnm-core/tests/test-crypto.c
+++ b/libnm-core/tests/test-crypto.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * Dan Williams <dcbw@redhat.com>
  *
diff --git a/libnm-core/tests/test-general-enums.h b/libnm-core/tests/test-general-enums.h
index 8aa3f99f..72981171 100644
--- a/libnm-core/tests/test-general-enums.h
+++ b/libnm-core/tests/test-general-enums.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index a6abf391..601e3edc 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  *
  * This program is free software; you can redistribute it and/or modify
@@ -32,6 +31,7 @@
 #include "nm-utils-private.h"
 #include "nm-core-internal.h"
 #include "nm-core-tests-enum-types.h"
+#include "nm-team-utils.h"
 
 #include "nm-setting-8021x.h"
 #include "nm-setting-adsl.h"
@@ -600,7 +600,7 @@ test_nm_utils_strsplit_set (void)
 	words_exp = g_ptr_array_new_with_free_func (g_free);
 	for (test_run = 0; test_run < 100; test_run++) {
 		gboolean f_allow_escaping = nmtst_get_rand_bool ();
-		guint words_len = nmtst_get_rand_int () % 100;
+		guint words_len = nmtst_get_rand_uint32 () % 100;
 		gs_free char *str = NULL;
 		guint i;
 
@@ -610,14 +610,14 @@ test_nm_utils_strsplit_set (void)
 			char *word;
 			guint j;
 
-			word_len = nmtst_get_rand_int ();
+			word_len = nmtst_get_rand_uint32 ();
 			if ((word_len % 100) < 30)
 				word_len = 0;
 			else
 				word_len = (word_len >> 10) % 100;
 			word = g_new (char, word_len + 3);
 			for (j = 0; j < word_len; ) {
-				guint32 p = nmtst_get_rand_int ();
+				guint32 p = nmtst_get_rand_uint32 ();
 				static const char delimiters_arr[] = { DELIMITERS_C };
 				static const char regular_chars[] = "abcdefghijklmnopqrstuvwxyz";
 
@@ -705,7 +705,7 @@ _do_test_c_list_sort (CListSort *elements, guint n_list, gboolean headless)
 	c_list_init (&head);
 	for (i = 0; i < n_list; i++) {
 		el = &elements[i];
-		el->val = nmtst_get_rand_int () % (2*n_list);
+		el->val = nmtst_get_rand_uint32 () % (2*n_list);
 		c_list_link_tail (&head, &el->lst);
 	}
 
@@ -766,7 +766,7 @@ test_c_list_sort (void)
 	elements = g_new0 (CListSort, N_ELEMENTS);
 	for (n_list = 1; n_list < N_ELEMENTS; n_list++) {
 		if (n_list > 150) {
-			n_list += nmtst_get_rand_int () % n_list;
+			n_list += nmtst_get_rand_uint32 () % n_list;
 			if (n_list >= N_ELEMENTS)
 				break;
 		}
@@ -774,7 +774,7 @@ test_c_list_sort (void)
 			const guint N_REPEAT = n_list > 50 ? 1 : 5;
 
 			for (repeat = 0; repeat < N_REPEAT; repeat++)
-				_do_test_c_list_sort (elements, n_list, nmtst_get_rand_int () % 2);
+				_do_test_c_list_sort (elements, n_list, nmtst_get_rand_uint32 () % 2);
 		}
 	}
 }
@@ -1855,7 +1855,7 @@ test_setting_to_dbus_all (void)
 
 	s_wsec = make_test_wsec_setting ("setting-to-dbus-all");
 
-	dict = _nm_setting_to_dbus (NM_SETTING (s_wsec), NULL, NM_CONNECTION_SERIALIZE_ALL);
+	dict = _nm_setting_to_dbus (NM_SETTING (s_wsec), NULL, NM_CONNECTION_SERIALIZE_ALL, NULL);
 
 	/* Make sure all keys are there */
 	g_assert (_variant_contains (dict, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT));
@@ -1875,7 +1875,7 @@ test_setting_to_dbus_no_secrets (void)
 
 	s_wsec = make_test_wsec_setting ("setting-to-dbus-no-secrets");
 
-	dict = _nm_setting_to_dbus (NM_SETTING (s_wsec), NULL, NM_CONNECTION_SERIALIZE_NO_SECRETS);
+	dict = _nm_setting_to_dbus (NM_SETTING (s_wsec), NULL, NM_CONNECTION_SERIALIZE_NO_SECRETS, NULL);
 
 	/* Make sure non-secret keys are there */
 	g_assert (_variant_contains (dict, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT));
@@ -1897,7 +1897,7 @@ test_setting_to_dbus_only_secrets (void)
 
 	s_wsec = make_test_wsec_setting ("setting-to-dbus-only-secrets");
 
-	dict = _nm_setting_to_dbus (NM_SETTING (s_wsec), NULL, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
+	dict = _nm_setting_to_dbus (NM_SETTING (s_wsec), NULL, NM_CONNECTION_SERIALIZE_ONLY_SECRETS, NULL);
 
 	/* Make sure non-secret keys are not there */
 	g_assert (!_variant_contains (dict, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT));
@@ -1928,7 +1928,7 @@ test_setting_to_dbus_transform (void)
 
 	g_assert_cmpstr (nm_setting_wired_get_mac_address (NM_SETTING_WIRED (s_wired)), ==, test_mac_address);
 
-	dict = _nm_setting_to_dbus (s_wired, NULL, NM_CONNECTION_SERIALIZE_ALL);
+	dict = _nm_setting_to_dbus (s_wired, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL);
 	g_assert (dict != NULL);
 
 	val = g_variant_lookup_value (dict, NM_SETTING_WIRED_MAC_ADDRESS, G_VARIANT_TYPE_BYTESTRING);
@@ -1957,7 +1957,7 @@ test_setting_to_dbus_enum (void)
 	              NM_SETTING_IP6_CONFIG_IP6_PRIVACY, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
 	              NULL);
 
-	dict = _nm_setting_to_dbus (s_ip6, NULL, NM_CONNECTION_SERIALIZE_ALL);
+	dict = _nm_setting_to_dbus (s_ip6, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL);
 	g_assert (dict != NULL);
 
 	val = g_variant_lookup_value (dict, NM_SETTING_IP6_CONFIG_IP6_PRIVACY, G_VARIANT_TYPE_INT32);
@@ -1976,7 +1976,7 @@ test_setting_to_dbus_enum (void)
 	                                                           NM_SETTING_SECRET_FLAG_NOT_SAVED),
 	              NULL);
 
-	dict = _nm_setting_to_dbus (s_wsec, NULL, NM_CONNECTION_SERIALIZE_ALL);
+	dict = _nm_setting_to_dbus (s_wsec, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL);
 	g_assert (dict != NULL);
 
 	val = g_variant_lookup_value (dict, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, G_VARIANT_TYPE_UINT32);
@@ -1999,7 +1999,7 @@ test_setting_to_dbus_enum (void)
 	              NM_SETTING_SERIAL_PARITY, NM_SETTING_SERIAL_PARITY_ODD,
 	              NULL);
 
-	dict = _nm_setting_to_dbus (s_serial, NULL, NM_CONNECTION_SERIALIZE_ALL);
+	dict = _nm_setting_to_dbus (s_serial, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL);
 	g_assert (dict != NULL);
 
 	val = g_variant_lookup_value (dict, NM_SETTING_SERIAL_PARITY, G_VARIANT_TYPE_BYTE);
@@ -2114,7 +2114,7 @@ test_setting_new_from_dbus (void)
 	GVariant *dict;
 
 	s_wsec = make_test_wsec_setting ("setting-new-from-dbus");
-	dict = _nm_setting_to_dbus (NM_SETTING (s_wsec), NULL, NM_CONNECTION_SERIALIZE_ALL);
+	dict = _nm_setting_to_dbus (NM_SETTING (s_wsec), NULL, NM_CONNECTION_SERIALIZE_ALL, NULL);
 	g_object_unref (s_wsec);
 
 	s_wsec = (NMSettingWirelessSecurity *) _nm_setting_new_from_dbus (NM_TYPE_SETTING_WIRELESS_SECURITY, dict, NULL, NM_SETTING_PARSE_FLAGS_NONE, NULL);
@@ -3024,6 +3024,7 @@ test_connection_diff_a_only (void)
 			{ NM_SETTING_CONNECTION_AUTH_RETRIES,         NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_CONNECTION_MDNS,                 NM_SETTING_DIFF_RESULT_IN_A },
 			{ NM_SETTING_CONNECTION_LLMNR,                NM_SETTING_DIFF_RESULT_IN_A },
+			{ NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT,  NM_SETTING_DIFF_RESULT_IN_A },
 			{ NULL, NM_SETTING_DIFF_RESULT_UNKNOWN }
 		} },
 		{ NM_SETTING_WIRED_SETTING_NAME, {
@@ -3500,7 +3501,7 @@ test_setting_compare_addresses (void)
 
 	nm_ip_address_unref (a);
 
-	if (nmtst_get_rand_int () % 2)
+	if (nmtst_get_rand_uint32 () % 2)
 		NMTST_SWAP (s1, s2);
 
 	success = nm_setting_compare (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT);
@@ -3532,7 +3533,7 @@ test_setting_compare_routes (void)
 
 	nm_ip_route_unref (r);
 
-	if (nmtst_get_rand_int () % 2)
+	if (nmtst_get_rand_uint32 () % 2)
 		NMTST_SWAP (s1, s2);
 
 	success = nm_setting_compare (s1, s2, NM_SETTING_COMPARE_FLAG_EXACT);
@@ -6080,7 +6081,7 @@ test_hexstr2bin (void)
 static void
 _do_strquote (const char *str, gsize buf_len, const char *expected)
 {
-	char canary = (char) nmtst_get_rand_int ();
+	char canary = (char) nmtst_get_rand_uint32 ();
 	gs_free char *buf_full = g_malloc (buf_len + 2);
 	char *buf = &buf_full[1];
 	const char *b;
@@ -6807,14 +6808,42 @@ _team_config_equal_check (const char *conf1,
                           gboolean port_config,
                           gboolean expected)
 {
-	g_assert_cmpint (_nm_utils_team_config_equal (conf1, conf2, port_config), ==, expected);
+	nm_auto_free_team_setting NMTeamSetting *team_a = NULL;
+	nm_auto_free_team_setting NMTeamSetting *team_b = NULL;
+	gboolean is_same;
+
+	if (nmtst_get_rand_bool ())
+		NMTST_SWAP (conf1, conf2);
+
+	if (!nm_streq0 (conf1, conf2)) {
+		_team_config_equal_check (conf1, conf1, port_config, TRUE);
+		_team_config_equal_check (conf2, conf2, port_config, TRUE);
+	}
+
+	team_a = nm_team_setting_new (port_config, conf1);
+	team_b = nm_team_setting_new (port_config, conf2);
+
+	is_same = (nm_team_setting_cmp (team_a, team_b, TRUE) == 0);
+	g_assert_cmpint (is_same, ==, expected);
+
+	if (nm_streq0 (conf1, conf2)) {
+		g_assert_cmpint (nm_team_setting_cmp (team_a, team_b, FALSE), ==, 0);
+		g_assert (expected);
+	} else
+		g_assert_cmpint (nm_team_setting_cmp (team_a, team_b, FALSE), !=, 0);
 }
 
 static void
 test_nm_utils_team_config_equal (void)
 {
-#if WITH_JSON_VALIDATION
-	_team_config_equal_check ("", "", TRUE, TRUE);
+	_team_config_equal_check ("",
+	                          "",
+	                          TRUE,
+	                          TRUE);
+	_team_config_equal_check ("",
+	                          " ",
+	                          TRUE,
+	                          TRUE);
 	_team_config_equal_check ("{}",
 	                          "{ }",
 	                          TRUE,
@@ -6822,21 +6851,25 @@ test_nm_utils_team_config_equal (void)
 	_team_config_equal_check ("{}",
 	                          "{",
 	                          TRUE,
-	                          FALSE);
+	                          TRUE);
+	_team_config_equal_check ("{ \"a\": 1 }",
+	                          "{ \"a\": 1 }",
+	                          TRUE,
+	                          TRUE);
+	_team_config_equal_check ("{ \"a\": 1 }",
+	                          "{ \"a\":   1 }",
+	                          TRUE,
+	                          TRUE);
 
 	/* team config */
 	_team_config_equal_check ("{ }",
-	                          "{ \"runner\" :  { \"name\" : \"roundrobin\"} }",
-	                          FALSE,
-	                          TRUE);
-	_team_config_equal_check ("{ }",
 	                          "{ \"runner\" :  { \"name\" : \"random\"} }",
 	                          FALSE,
-	                          FALSE);
+	                          !WITH_JSON_VALIDATION);
 	_team_config_equal_check ("{ \"runner\" :  { \"name\" : \"roundrobin\"} }",
 	                          "{ \"runner\" :  { \"name\" : \"random\"} }",
 	                          FALSE,
-	                          FALSE);
+	                          !WITH_JSON_VALIDATION);
 	_team_config_equal_check ("{ \"runner\" :  { \"name\" : \"random\"} }",
 	                          "{ \"runner\" :  { \"name\" : \"random\"} }",
 	                          FALSE,
@@ -6852,29 +6885,29 @@ test_nm_utils_team_config_equal (void)
 	_team_config_equal_check ("{ \"runner\" :  { \"name\" : \"lacp\"} }",
 	                          "{ \"runner\" :  { \"name\" : \"lacp\", \"tx_hash\" : [ \"eth\", \"ipv4\", \"ipv6\" ] } }",
 	                          FALSE,
-	                          TRUE);
+	                          !WITH_JSON_VALIDATION);
 	_team_config_equal_check ("{ \"runner\" :  { \"name\" : \"roundrobin\"} }",
 	                          "{ \"runner\" :  { \"name\" : \"roundrobin\", \"tx_hash\" : [ \"eth\", \"ipv4\", \"ipv6\" ] } }",
 	                          FALSE,
-	                          FALSE);
+	                          !WITH_JSON_VALIDATION);
 	_team_config_equal_check ("{ \"runner\" :  { \"name\" : \"lacp\"} }",
 	                          "{ \"runner\" :  { \"name\" : \"lacp\", \"tx_hash\" : [ \"eth\" ] } }",
 	                          FALSE,
-	                          FALSE);
+	                          !WITH_JSON_VALIDATION);
 
 	/* team port config */
 	_team_config_equal_check ("{ }",
 	                          "{ \"link_watch\" :  { \"name\" : \"ethtool\"} }",
 	                          TRUE,
-	                          TRUE);
+	                          !WITH_JSON_VALIDATION);
 	_team_config_equal_check ("{ }",
 	                          "{ \"link_watch\" :  { \"name\" : \"arp_ping\"} }",
 	                          TRUE,
-	                          FALSE);
+	                          TRUE);
 	_team_config_equal_check ("{ \"link_watch\" :  { \"name\" : \"ethtool\"} }",
 	                          "{ \"link_watch\" :  { \"name\" : \"arp_ping\"} }",
 	                          TRUE,
-	                          FALSE);
+	                          !WITH_JSON_VALIDATION);
 	_team_config_equal_check ("{ \"link_watch\" :  { \"name\" : \"arp_ping\"} }",
 	                          "{ \"link_watch\" :  { \"name\" : \"arp_ping\"} }",
 	                          TRUE,
@@ -6883,13 +6916,6 @@ test_nm_utils_team_config_equal (void)
 	                          "{ \"link_watch\" :  { \"name\" : \"arp_ping\"}, \"ports\" : { \"eth1\" : {} } }",
 	                          TRUE,
 	                          TRUE);
-#else
-	/* Without JSON library, strings are compared for equality */
-	_team_config_equal_check ("", "", TRUE, TRUE);
-	_team_config_equal_check ("", " ", TRUE, FALSE);
-	_team_config_equal_check ("{ \"a\": 1 }", "{ \"a\": 1 }", TRUE, TRUE);
-	_team_config_equal_check ("{ \"a\": 1 }", "{ \"a\":   1 }", TRUE, FALSE);
-#endif
 }
 
 /*****************************************************************************/
@@ -7188,7 +7214,7 @@ test_nm_utils_ptrarray_find_binary_search_with_duplicates (void)
 			/* fill with random numbers... surely there are some duplicates
 			 * there... or maybe even there are none... */
 			for (i = 0; i < i_len; i++)
-				arr[i] = GINT_TO_POINTER (nmtst_get_rand_int () % (i_len + BIN_SEARCH_W_DUPS_JITTER));
+				arr[i] = GINT_TO_POINTER (nmtst_get_rand_uint32 () % (i_len + BIN_SEARCH_W_DUPS_JITTER));
 			g_qsort_with_data (arr,
 			                   i_len,
 			                   sizeof (gpointer),
diff --git a/libnm-core/tests/test-keyfile.c b/libnm-core/tests/test-keyfile.c
index 157e8f18..195f3797 100644
--- a/libnm-core/tests/test-keyfile.c
+++ b/libnm-core/tests/test-keyfile.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  *
  * This program is free software; you can redistribute it and/or modify
@@ -36,6 +35,7 @@
 #define TEST_CERT_DIR              NM_BUILD_SRCDIR"/libnm-core/tests/certs"
 #define TEST_WIRED_TLS_CA_CERT     TEST_CERT_DIR"/test-ca-cert.pem"
 #define TEST_WIRED_TLS_PRIVKEY     TEST_CERT_DIR"/test-key-and-cert.pem"
+#define TEST_WIRED_TLS_TPM2KEY     TEST_CERT_DIR"/test-tpm2wrapped-key.pem"
 
 /*****************************************************************************/
 
@@ -378,15 +378,15 @@ _test_8021x_cert_check_blob_full (NMConnection *con, const void *data, gsize len
 #define _test_8021x_cert_check_blob(con, data) _test_8021x_cert_check_blob_full(con, data, NM_STRLEN (data))
 
 static void
-test_8021x_cert (void)
+_test_8021x_cert_from_files (const char *cert, const char *key)
 {
 	NMSetting8021x *s_8021x;
 	gs_unref_object NMConnection *con = nmtst_create_minimal_connection ("test-cert", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
 	GError *error = NULL;
 	gboolean success;
 	NMSetting8021xCKScheme scheme = NM_SETTING_802_1X_CK_SCHEME_PATH;
-	gs_free char *full_TEST_WIRED_TLS_CA_CERT = nmtst_file_resolve_relative_path (TEST_WIRED_TLS_CA_CERT, NULL);
-	gs_free char *full_TEST_WIRED_TLS_PRIVKEY = nmtst_file_resolve_relative_path (TEST_WIRED_TLS_PRIVKEY, NULL);
+	gs_free char *full_TEST_WIRED_TLS_CA_CERT = nmtst_file_resolve_relative_path (cert, NULL);
+	gs_free char *full_TEST_WIRED_TLS_PRIVKEY = nmtst_file_resolve_relative_path (key, NULL);
 
 	/* test writing/reading of certificates of NMSetting8021x */
 
@@ -445,6 +445,18 @@ test_8021x_cert (void)
 
 }
 
+static void
+test_8021x_cert (void)
+{
+	_test_8021x_cert_from_files (TEST_WIRED_TLS_CA_CERT, TEST_WIRED_TLS_PRIVKEY);
+}
+
+static void
+test_8021x_cert_tpm2key (void)
+{
+	_test_8021x_cert_from_files (TEST_WIRED_TLS_CA_CERT, TEST_WIRED_TLS_TPM2KEY);
+}
+
 /*****************************************************************************/
 
 static void
@@ -626,11 +638,15 @@ test_team_conf_read_valid (void)
 static void
 test_team_conf_read_invalid (void)
 {
-#if WITH_JSON_VALIDATION
 	GKeyFile *keyfile = NULL;
 	gs_unref_object NMConnection *con = NULL;
 	NMSettingTeam *s_team;
 
+	if (!WITH_JSON_VALIDATION) {
+		g_test_skip ("team test requires JSON validation");
+		return;
+	}
+
 	con = nmtst_create_connection_from_keyfile (
 	      "[connection]\n"
 	      "type=team\n"
@@ -645,7 +661,6 @@ test_team_conf_read_invalid (void)
 	g_assert (nm_setting_team_get_config (s_team) == NULL);
 
 	CLEAR (&con, &keyfile);
-#endif
 }
 
 /*****************************************************************************/
@@ -848,6 +863,7 @@ int main (int argc, char **argv)
 
 	g_test_add_func ("/core/keyfile/encode_key", test_encode_key);
 	g_test_add_func ("/core/keyfile/test_8021x_cert", test_8021x_cert);
+	g_test_add_func ("/core/keyfile/test_8021x_cert_tpm2key", test_8021x_cert_tpm2key);
 	g_test_add_func ("/core/keyfile/test_8021x_cert_read", test_8021x_cert_read);
 	g_test_add_func ("/core/keyfile/test_team_conf_read/valid", test_team_conf_read_valid);
 	g_test_add_func ("/core/keyfile/test_team_conf_read/invalid", test_team_conf_read_invalid);
diff --git a/libnm-core/tests/test-secrets.c b/libnm-core/tests/test-secrets.c
index 5debe5cf..d657a43c 100644
--- a/libnm-core/tests/test-secrets.c
+++ b/libnm-core/tests/test-secrets.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  *
  * This program is free software; you can redistribute it and/or modify
@@ -649,7 +648,7 @@ test_update_secrets_null_setting_name_with_setting_hash (void)
 
 	secrets = build_wep_secrets (wepkey);
 
-	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (setting_name != NULL || full_connection));
+	NMTST_EXPECT_LIBNM_CRITICAL (NMTST_G_RETURN_MSG (setting_name || full_connection));
 	success = nm_connection_update_secrets (connection, NULL, secrets, &error);
 	g_test_assert_expected_messages ();
 	g_assert_no_error (error);
diff --git a/libnm-core/tests/test-setting.c b/libnm-core/tests/test-setting.c
index 03100a03..aa9de5a0 100644
--- a/libnm-core/tests/test-setting.c
+++ b/libnm-core/tests/test-setting.c
@@ -45,6 +45,15 @@
 
 /*****************************************************************************/
 
+/* assert that the define is just a plain integer (boolean). */
+
+G_STATIC_ASSERT (   (WITH_JSON_VALIDATION) == 1
+                 || (WITH_JSON_VALIDATION) == 0);
+
+_nm_unused static const int _with_json_validation = WITH_JSON_VALIDATION;
+
+/*****************************************************************************/
+
 /* converts @dict to a connection. In this case, @dict must be good, without warnings, so that
  * NM_SETTING_PARSE_FLAGS_STRICT and NM_SETTING_PARSE_FLAGS_BEST_EFFORT yield the exact same results. */
 static NMConnection *
@@ -92,8 +101,8 @@ _connection_new_from_dbus_strict (GVariant *dict,
 	 * after accounting for normalization. */
 	for (i = 0; i < 10; i++) {
 		NMConnection *cons[] = { con_x_0, con_x_s, con_x_e, con_n_0, con_n_s, con_n_e };
-		guint idx_a = (nmtst_get_rand_int () % G_N_ELEMENTS (cons));
-		guint idx_b = (nmtst_get_rand_int () % G_N_ELEMENTS (cons));
+		guint idx_a = (nmtst_get_rand_uint32 () % G_N_ELEMENTS (cons));
+		guint idx_b = (nmtst_get_rand_uint32 () % G_N_ELEMENTS (cons));
 		gboolean normalize_a, normalize_b;
 
 		if (idx_a <= 2 && idx_b <= 2) {
@@ -128,14 +137,14 @@ _create_random_ipaddr (int addr_family, gboolean as_service)
 	g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
 	if (as_service)
-		num = (nmtst_get_rand_int () % 1000) + 30000;
+		num = (nmtst_get_rand_uint32 () % 1000) + 30000;
 	else
 		num = addr_family == AF_INET ? 32 : 128;
 
 	if (addr_family == AF_INET)
-		return g_strdup_printf ("192.168.%u.%u%c%d", nmtst_get_rand_int () % 256, nmtst_get_rand_int () % 256, delimiter, num);
+		return g_strdup_printf ("192.168.%u.%u%c%d", nmtst_get_rand_uint32 () % 256, nmtst_get_rand_uint32 () % 256, delimiter, num);
 	else
-		return g_strdup_printf ("a:b:c::%02x:%02x%c%d", nmtst_get_rand_int () % 256, nmtst_get_rand_int () % 256, delimiter, num);
+		return g_strdup_printf ("a:b:c::%02x:%02x%c%d", nmtst_get_rand_uint32 () % 256, nmtst_get_rand_uint32 () % 256, delimiter, num);
 }
 
 /*****************************************************************************/
@@ -966,7 +975,6 @@ test_dcb_bandwidth_sums (void)
 
 /*****************************************************************************/
 
-#if WITH_JSON_VALIDATION
 static void
 _test_team_config_sync (const char *team_config,
                         int notify_peer_count,
@@ -989,26 +997,31 @@ _test_team_config_sync (const char *team_config,
 	guint i, j;
 	gboolean found;
 
+	if (!WITH_JSON_VALIDATION) {
+		g_test_skip ("team test requires JSON validation");
+		return;
+	}
+
 	s_team = (NMSettingTeam *) nm_setting_team_new ();
 	g_assert (s_team);
 
 	g_object_set (s_team, NM_SETTING_TEAM_CONFIG, team_config, NULL);
-	g_assert (nm_setting_team_get_notify_peers_count (s_team) == notify_peer_count);
-	g_assert (nm_setting_team_get_notify_peers_interval (s_team) == notify_peers_interval);
-	g_assert (nm_setting_team_get_mcast_rejoin_count (s_team) == mcast_rejoin_count);
-	g_assert (nm_setting_team_get_mcast_rejoin_interval (s_team) == mcast_rejoin_interval);
-	g_assert (nm_setting_team_get_runner_tx_balancer_interval (s_team) == runner_tx_balancer_interval);
-	g_assert (nm_setting_team_get_runner_active (s_team) == runner_active);
-	g_assert (nm_setting_team_get_runner_fast_rate (s_team) == runner_fast_rate);
-	g_assert (nm_setting_team_get_runner_sys_prio (s_team) == runner_sys_prio);
-	g_assert (nm_setting_team_get_runner_min_ports (s_team) == runner_min_ports);
-	g_assert (nm_streq0 (nm_setting_team_get_runner (s_team), runner));
-	g_assert (nm_streq0 (nm_setting_team_get_runner_hwaddr_policy (s_team), runner_hwaddr_policy));
-	g_assert (nm_streq0 (nm_setting_team_get_runner_tx_balancer (s_team), runner_tx_balancer));
-	g_assert (nm_streq0 (nm_setting_team_get_runner_agg_select_policy (s_team), runner_agg_select_policy));
+	g_assert_cmpint (nm_setting_team_get_notify_peers_count (s_team), ==, notify_peer_count);
+	g_assert_cmpint (nm_setting_team_get_notify_peers_interval (s_team), ==, notify_peers_interval);
+	g_assert_cmpint (nm_setting_team_get_mcast_rejoin_count (s_team), ==, mcast_rejoin_count);
+	g_assert_cmpint (nm_setting_team_get_mcast_rejoin_interval (s_team), ==, mcast_rejoin_interval);
+	g_assert_cmpint (nm_setting_team_get_runner_tx_balancer_interval (s_team), ==, runner_tx_balancer_interval);
+	g_assert_cmpint (nm_setting_team_get_runner_active (s_team), ==, runner_active);
+	g_assert_cmpint (nm_setting_team_get_runner_fast_rate (s_team), ==, runner_fast_rate);
+	g_assert_cmpint (nm_setting_team_get_runner_sys_prio (s_team), ==, runner_sys_prio);
+	g_assert_cmpint (nm_setting_team_get_runner_min_ports (s_team), ==, runner_min_ports);
+	g_assert_cmpstr (nm_setting_team_get_runner (s_team), ==, runner);
+	g_assert_cmpstr (nm_setting_team_get_runner_hwaddr_policy (s_team), ==, runner_hwaddr_policy);
+	g_assert_cmpstr (nm_setting_team_get_runner_tx_balancer (s_team), ==, runner_tx_balancer);
+	g_assert_cmpstr (nm_setting_team_get_runner_agg_select_policy (s_team), ==, runner_agg_select_policy);
 
 	if (runner_tx_hash) {
-		g_assert (runner_tx_hash->len == nm_setting_team_get_num_runner_tx_hash (s_team));
+		g_assert_cmpint (runner_tx_hash->len, ==, nm_setting_team_get_num_runner_tx_hash (s_team));
 		for (i = 0; i < runner_tx_hash->len; i++) {
 			found = FALSE;
 			for (j = 0; j < nm_setting_team_get_num_runner_tx_hash (s_team); j++) {
@@ -1023,7 +1036,7 @@ _test_team_config_sync (const char *team_config,
 	}
 
 	if (link_watchers) {
-		g_assert (link_watchers->len == nm_setting_team_get_num_link_watchers (s_team));
+		g_assert_cmpint (link_watchers->len, ==, nm_setting_team_get_num_link_watchers (s_team));
 		for (i = 0; i < link_watchers->len; i++) {
 			found = FALSE;
 			for (j = 0; j < nm_setting_team_get_num_link_watchers (s_team); j++) {
@@ -1044,11 +1057,11 @@ static void
 test_runner_roundrobin_sync_from_config (void)
 {
 	_test_team_config_sync ("",
-	                        0, 0, 0, 0,
-	                        NM_SETTING_TEAM_RUNNER_ROUNDROBIN,
+	                        -1, -1, -1, -1,
+	                        NULL,
 	                        NULL,
 	                        NULL, NULL, -1,
-	                        FALSE, FALSE, -1, -1, NULL,
+	                        TRUE, FALSE, -1, -1, NULL,
 	                        NULL);
 }
 
@@ -1056,11 +1069,11 @@ static void
 test_runner_broadcast_sync_from_config (void)
 {
 	_test_team_config_sync ("{\"runner\": {\"name\": \"broadcast\"}}",
-	                        0, 0, 0, 0,
+	                        -1, -1, -1, -1,
 	                        NM_SETTING_TEAM_RUNNER_BROADCAST,
 	                        NULL,
 	                        NULL, NULL, -1,
-	                        FALSE, FALSE, -1, -1, NULL,
+	                        TRUE, FALSE, -1, -1, NULL,
 	                        NULL);
 }
 
@@ -1068,11 +1081,11 @@ static void
 test_runner_random_sync_from_config (void)
 {
 	_test_team_config_sync ("{\"runner\": {\"name\": \"random\"}}",
-	                        0, 0, 0, 0,
+	                        -1, -1, -1, -1,
 	                        NM_SETTING_TEAM_RUNNER_RANDOM,
 	                        NULL,
 	                        NULL, NULL, -1,
-	                        FALSE, FALSE, -1, -1, NULL,
+	                        TRUE, FALSE, -1, -1, NULL,
 	                        NULL);
 }
 
@@ -1080,12 +1093,11 @@ static void
 test_runner_activebackup_sync_from_config (void)
 {
 	_test_team_config_sync ("{\"runner\": {\"name\": \"activebackup\"}}",
-	                        NM_SETTING_TEAM_NOTIFY_PEERS_COUNT_ACTIVEBACKUP_DEFAULT, 0,
-	                        NM_SETTING_TEAM_NOTIFY_MCAST_COUNT_ACTIVEBACKUP_DEFAULT, 0,
+	                        -1, -1, -1, -1,
 	                        NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP,
-	                        NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_DEFAULT,
+	                        NULL,
 	                        NULL, NULL, -1,
-	                        FALSE, FALSE, -1, -1, NULL,
+	                        TRUE, FALSE, -1, -1, NULL,
 	                        NULL);
 }
 
@@ -1100,29 +1112,29 @@ test_runner_loadbalance_sync_from_config (void)
 	g_ptr_array_add (tx_hash, g_strdup ("ipv6"));
 
 	_test_team_config_sync ("{\"runner\": {\"name\": \"loadbalance\"}}",
-	                        0, 0, 0, 0,
+	                        -1, -1, -1, -1,
 	                        NM_SETTING_TEAM_RUNNER_LOADBALANCE,
 	                        NULL,
-	                        tx_hash, NULL, NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL_DEFAULT,
-	                        FALSE, FALSE, -1, -1, NULL,
+	                        NULL, NULL, -1,
+	                        TRUE, FALSE, -1, -1, NULL,
 	                        NULL);
 
 	_test_team_config_sync ("{\"runner\": {\"name\": \"loadbalance\", "
 	                        "\"tx_hash\": [\"eth\", \"ipv4\", \"ipv6\"]}}",
-	                        0, 0, 0, 0,
+	                        -1, -1, -1, -1,
 	                        NM_SETTING_TEAM_RUNNER_LOADBALANCE,
 	                        NULL,
-	                        tx_hash, NULL, NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL_DEFAULT,
-	                        FALSE, FALSE, -1, -1, NULL,
+	                        tx_hash, NULL, -1,
+	                        TRUE, FALSE, -1, -1, NULL,
 	                        NULL);
 
 	_test_team_config_sync ("{\"runner\": {\"name\": \"loadbalance\", \"tx_hash\": [\"eth\", \"ipv4\", \"ipv6\"], "
 	                        "\"tx_balancer\": {\"name\": \"basic\", \"balancing_interval\": 30}}}",
-	                        0, 0, 0, 0,
+	                        -1, -1, -1, -1,
 	                        NM_SETTING_TEAM_RUNNER_LOADBALANCE,
 	                        NULL,
 	                        tx_hash, "basic", 30,
-	                        FALSE, FALSE, -1, -1, NULL,
+	                        TRUE, FALSE, -1, -1, NULL,
 	                        NULL);
 }
 
@@ -1137,21 +1149,21 @@ test_runner_lacp_sync_from_config (void)
 	g_ptr_array_add (tx_hash, g_strdup ("ipv6"));
 
 	_test_team_config_sync ("{\"runner\": {\"name\": \"lacp\", \"tx_hash\": [\"eth\", \"ipv4\", \"ipv6\"]}}",
-	                        0, 0, 0, 0,
+	                        -1, -1, -1, -1,
 	                        NM_SETTING_TEAM_RUNNER_LACP,
 	                        NULL,
-	                        tx_hash, NULL, NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL_DEFAULT,
-	                        TRUE, FALSE, NM_SETTING_TEAM_RUNNER_SYS_PRIO_DEFAULT, 0,
-	                        NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY_DEFAULT,
+	                        tx_hash, NULL, -1,
+	                        TRUE, FALSE, -1, -1,
+	                        NULL,
 	                        NULL);
 
 	_test_team_config_sync ("{\"runner\": {\"name\": \"lacp\", \"tx_hash\": [\"eth\", \"ipv4\", \"ipv6\"], "
 	                        "\"active\": false, \"fast_rate\": true, \"sys_prio\": 10, \"min_ports\": 5, "
 	                        "\"agg_select_policy\": \"port_config\"}}",
-	                        0, 0, 0, 0,
+	                        -1, -1, -1, -1,
 	                        NM_SETTING_TEAM_RUNNER_LACP,
 	                        NULL,
-	                        tx_hash, NULL, NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL_DEFAULT,
+	                        tx_hash, NULL, -1,
 	                        FALSE, TRUE, 10, 5, "port_config",
 	                        NULL);
 }
@@ -1164,11 +1176,11 @@ test_watcher_ethtool_sync_from_config (void)
 	link_watchers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref);
 	g_ptr_array_add (link_watchers, nm_team_link_watcher_new_ethtool (0, 0, NULL));
 	_test_team_config_sync ("{\"link_watch\": {\"name\": \"ethtool\"}}",
-	                        0, 0, 0, 0,
-	                        "roundrobin",
+	                        -1, -1, -1, -1,
+	                        NULL,
 	                        NULL,
 	                        NULL, NULL, -1,
-	                        FALSE, FALSE, -1, -1, NULL,
+	                        TRUE, FALSE, -1, -1, NULL,
 	                        link_watchers);
 }
 
@@ -1180,11 +1192,11 @@ test_watcher_nsna_ping_sync_from_config (void)
 	link_watchers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref);
 	g_ptr_array_add (link_watchers, nm_team_link_watcher_new_nsna_ping (0, 0, 3, "target.host", NULL));
 	_test_team_config_sync ("{\"link_watch\": {\"name\": \"nsna_ping\", \"target_host\": \"target.host\"}}",
-	                        0, 0, 0, 0,
-	                        "roundrobin",
+	                        -1, -1, -1, -1,
+	                        NULL,
 	                        NULL,
 	                        NULL, NULL, -1,
-	                        FALSE, FALSE, -1, -1, NULL,
+	                        TRUE, FALSE, -1, -1, NULL,
 	                        link_watchers);
 }
 
@@ -1198,11 +1210,11 @@ test_watcher_arp_ping_sync_from_config (void)
 	                 nm_team_link_watcher_new_arp_ping (0, 0, 3, "target.host", "source.host", 0, NULL));
 	_test_team_config_sync ("{\"link_watch\": {\"name\": \"arp_ping\", \"target_host\": \"target.host\", "
 	                        "\"source_host\": \"source.host\"}}",
-	                        0, 0, 0, 0,
-	                        "roundrobin",
+	                        -1, -1, -1, -1,
+	                        NULL,
 	                        NULL,
 	                        NULL, NULL, -1,
-	                        FALSE, FALSE, -1, -1, NULL,
+	                        TRUE, FALSE, -1, -1, NULL,
 	                        link_watchers);
 }
 
@@ -1227,11 +1239,11 @@ test_multiple_watchers_sync_from_config (void)
 	                        "\"validate_active\": true, \"validate_inactive\": true, \"send_always\": true}, "
 	                        "{\"name\": \"nsna_ping\", \"init_wait\": 3, \"interval\": 6, \"missed_max\": 9, "
 	                        "\"target_host\": \"target.host\"}]}",
-	                        0, 0, 0, 0,
-	                        "roundrobin",
+	                        -1, -1, -1, -1,
+	                        NULL,
 	                        NULL,
 	                        NULL, NULL, -1,
-	                        FALSE, FALSE, -1, -1, NULL,
+	                        TRUE, FALSE, -1, -1, NULL,
 	                        link_watchers);
 }
 
@@ -1250,6 +1262,11 @@ _test_team_port_config_sync (const char *team_port_config,
 	guint i, j;
 	gboolean found;
 
+	if (!WITH_JSON_VALIDATION) {
+		g_test_skip ("team test requires JSON validation");
+		return;
+	}
+
 	s_team_port = (NMSettingTeamPort *) nm_setting_team_port_new ();
 	g_assert (s_team_port);
 
@@ -1282,52 +1299,52 @@ _test_team_port_config_sync (const char *team_port_config,
 static void
 test_team_port_default (void)
 {
-	_test_team_port_config_sync ("", -1, 0, FALSE, 255, 0, NULL);
+	_test_team_port_config_sync ("", -1, 0, FALSE, -1, -1, NULL);
 }
 
 static void
 test_team_port_queue_id (void)
 {
 	_test_team_port_config_sync ("{\"queue_id\": 3}",
-	                             3, 0, FALSE, 255, 0, NULL);
+	                             3, 0, FALSE, -1, -1, NULL);
 	_test_team_port_config_sync ("{\"queue_id\": 0}",
-	                             0, 0, FALSE, 255, 0, NULL);
+	                             0, 0, FALSE, -1, -1, NULL);
 }
 
 static void
 test_team_port_prio (void)
 {
 	_test_team_port_config_sync ("{\"prio\": 6}",
-	                             -1, 6, FALSE, 255, 0, NULL);
+	                             -1, 6, FALSE, -1, -1, NULL);
 	_test_team_port_config_sync ("{\"prio\": 0}",
-	                             -1, 0, FALSE, 255, 0, NULL);
+	                             -1, 0, FALSE, -1, -1, NULL);
 }
 
 static void
 test_team_port_sticky (void)
 {
 	_test_team_port_config_sync ("{\"sticky\": true}",
-	                             -1, 0, TRUE, 255, 0, NULL);
+	                             -1, 0, TRUE, -1, -1, NULL);
 	_test_team_port_config_sync ("{\"sticky\": false}",
-	                             -1, 0, FALSE, 255, 0, NULL);
+	                             -1, 0, FALSE, -1, -1, NULL);
 }
 
 static void
 test_team_port_lacp_prio (void)
 {
 	_test_team_port_config_sync ("{\"lacp_prio\": 9}",
-	                             -1, 0, FALSE, 9, 0, NULL);
+	                             -1, 0, FALSE, 9, -1, NULL);
 	_test_team_port_config_sync ("{\"lacp_prio\": 0}",
-	                             -1, 0, FALSE, 0, 0, NULL);
+	                             -1, 0, FALSE, 0, -1, NULL);
 }
 
 static void
 test_team_port_lacp_key (void)
 {
 	_test_team_port_config_sync ("{\"lacp_key\": 12}",
-	                             -1, 0, FALSE, 255, 12, NULL);
+	                             -1, 0, FALSE, -1, 12, NULL);
 	_test_team_port_config_sync ("{\"lacp_key\": 0}",
-	                             -1, 0, FALSE, 255, 0, NULL);
+	                             -1, 0, FALSE, -1, 0, NULL);
 }
 
 static void
@@ -1354,7 +1371,127 @@ test_team_port_full_config (void)
 	                             "\"send_always\": true}]}",
 	                             10, 20, true, 30, 40, NULL);
 }
-#endif
+
+/*****************************************************************************/
+
+static void
+_check_team_setting (NMSetting *setting)
+{
+	gs_unref_object NMSetting *setting2 = NULL;
+	gs_unref_object NMSetting *setting_clone = NULL;
+	gboolean is_port = NM_IS_SETTING_TEAM_PORT (setting);
+	gs_unref_variant GVariant *variant2 = NULL;
+	gs_unref_variant GVariant *variant3 = NULL;
+
+	g_assert (NM_IS_SETTING_TEAM (setting) || is_port);
+
+	setting2 = g_object_new (G_OBJECT_TYPE (setting),
+	                           is_port
+	                         ? NM_SETTING_TEAM_PORT_CONFIG
+	                         : NM_SETTING_TEAM_CONFIG,
+	                           is_port
+	                         ? nm_setting_team_port_get_config (NM_SETTING_TEAM_PORT (setting))
+	                         : nm_setting_team_get_config (NM_SETTING_TEAM (setting)),
+	                         NULL);
+
+	if (WITH_JSON_VALIDATION)
+		nmtst_assert_setting_is_equal (setting, setting2, NM_SETTING_COMPARE_FLAG_EXACT);
+
+	g_clear_object (&setting2);
+
+	nmtst_assert_setting_dbus_roundtrip (setting);
+
+	/* OK, now parse the setting only from the D-Bus variant, but removing the JSON config.
+	 * For that, we have to "drop" the JSON and we do that by resetting the property.
+	 * This causes JSON to be regenerated and it's in a normalized form that will compare
+	 * equal. */
+	setting_clone = nm_setting_duplicate (setting);
+	setting = setting_clone;
+	if (is_port) {
+		g_object_set (setting,
+		              NM_SETTING_TEAM_PORT_STICKY,
+		              nm_setting_team_port_get_sticky (NM_SETTING_TEAM_PORT (setting)),
+		              NULL);
+	} else {
+		g_object_set (setting,
+		              NM_SETTING_TEAM_RUNNER_SYS_PRIO,
+		              nm_setting_team_get_runner_sys_prio (NM_SETTING_TEAM (setting)),
+		              NULL);
+	}
+	variant2 = _nm_setting_to_dbus (setting, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL);
+	variant3 = nm_utils_gvariant_vardict_filter_drop_one (variant2, "config");
+	setting2 = nmtst_assert_setting_dbus_new (G_OBJECT_TYPE (setting), variant3);
+	nmtst_assert_setting_is_equal (setting, setting2, NM_SETTING_COMPARE_FLAG_EXACT);
+}
+
+static void
+test_team_setting (void)
+{
+	gs_unref_variant GVariant *variant = nmtst_variant_from_string (G_VARIANT_TYPE_VARDICT,
+	                                                                "{'config': <'{\"link_watch\": {\"name\": \"ethtool\"}}'>, 'interface-name': <'nm-team'>, 'link-watchers': <[{'name': <'ethtool'>}]>}");
+	gs_free_error GError *error = NULL;
+	gs_unref_object NMSetting *setting = NULL;
+	nm_auto_unref_team_link_watcher NMTeamLinkWatcher *watcher1 = nm_team_link_watcher_new_nsna_ping (1, 3, 4, "bbb", NULL);
+	nm_auto_unref_team_link_watcher NMTeamLinkWatcher *watcher2 = nm_team_link_watcher_new_arp_ping2 (1, 3, 4, -1, "ccc", "ddd", 0, NULL);
+
+	g_assert (watcher1);
+	g_assert (watcher2);
+
+	setting = _nm_setting_new_from_dbus (NM_TYPE_SETTING_TEAM,
+	                                     variant,
+	                                     NULL,
+	                                     NM_SETTING_PARSE_FLAGS_STRICT,
+	                                     &error);
+	nmtst_assert_success (setting, error);
+	_check_team_setting (setting);
+
+	g_assert_cmpstr (nm_setting_team_get_config (NM_SETTING_TEAM (setting)), ==, "{\"link_watch\": {\"name\": \"ethtool\"}}");
+	g_assert_cmpint (nm_setting_team_get_num_link_watchers (NM_SETTING_TEAM (setting)), ==, 1);
+
+	g_object_set (setting,
+	              NM_SETTING_TEAM_RUNNER_SYS_PRIO,
+	              (int) 10,
+	              NULL);
+
+	_check_team_setting (setting);
+	g_assert_cmpint (nm_setting_team_get_num_link_watchers (NM_SETTING_TEAM (setting)), ==, 1);
+	g_assert_cmpstr (nm_setting_team_get_config (NM_SETTING_TEAM (setting)), ==, "{ \"runner\": { \"sys_prio\": 10 }, \"link_watch\": { \"name\": \"ethtool\" } }");
+
+	nm_setting_team_remove_link_watcher (NM_SETTING_TEAM (setting), 0);
+
+	_check_team_setting (setting);
+	g_assert_cmpint (nm_setting_team_get_num_link_watchers (NM_SETTING_TEAM (setting)), ==, 0);
+	g_assert_cmpstr (nm_setting_team_get_config (NM_SETTING_TEAM (setting)), ==, "{ \"runner\": { \"sys_prio\": 10 } }");
+
+	nm_setting_team_add_link_watcher (NM_SETTING_TEAM (setting), watcher1);
+	_check_team_setting (setting);
+	g_assert_cmpstr (nm_setting_team_get_config (NM_SETTING_TEAM (setting)), ==, "{ \"runner\": { \"sys_prio\": 10 }, \"link_watch\": { \"name\": \"nsna_ping\", \"interval\": 3, \"init_wait\": 1, \"missed_max\": 4, \"target_host\": \"bbb\" } }");
+
+	nm_setting_team_add_link_watcher (NM_SETTING_TEAM (setting), watcher2);
+	_check_team_setting (setting);
+	g_assert_cmpstr (nm_setting_team_get_config (NM_SETTING_TEAM (setting)), ==, "{ \"runner\": { \"sys_prio\": 10 }, \"link_watch\": [ { \"name\": \"nsna_ping\", \"interval\": 3, \"init_wait\": 1, \"missed_max\": 4, \"target_host\": \"bbb\" }, { \"name\": \"arp_ping\", \"interval\": 3, \"init_wait\": 1, \"missed_max\": 4, \"source_host\": \"ddd\", \"target_host\": \"ccc\" } ] }");
+
+	nm_setting_team_remove_link_watcher (NM_SETTING_TEAM (setting), 0);
+	nm_setting_team_remove_link_watcher (NM_SETTING_TEAM (setting), 0);
+	g_object_set (setting,
+	              NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL,
+	              (int) 5,
+	              NULL);
+	g_assert_cmpstr (nm_setting_team_get_config (NM_SETTING_TEAM (setting)), ==, "{ \"runner\": { \"tx_balancer\": { \"balancing_interval\": 5 }, \"sys_prio\": 10 } }");
+
+	g_object_set (setting,
+	              NM_SETTING_TEAM_RUNNER,
+	              NULL,
+	              NULL);
+	_check_team_setting (setting);
+	g_assert_cmpstr (nm_setting_team_get_config (NM_SETTING_TEAM (setting)), ==, "{ \"runner\": { \"tx_balancer\": { \"balancing_interval\": 5 }, \"sys_prio\": 10 } }");
+
+	g_object_set (setting,
+	              NM_SETTING_TEAM_CONFIG,
+	              "{ \"runner\": { \"tx_hash\": [ \"eth\", \"l3\" ] } }",
+	              NULL);
+	_check_team_setting (setting);
+}
 
 /*****************************************************************************/
 
@@ -2108,6 +2245,105 @@ test_tc_config_dbus (void)
 
 /*****************************************************************************/
 
+static void
+_rndt_wired_add_s390_options (NMSettingWired *s_wired,
+                              char **out_keyfile_entries)
+{
+	gsize n_opts;
+	gsize i, j;
+	const char *const*option_names;
+	gs_free const char **opt_keys = NULL;
+	gs_strfreev char **opt_vals = NULL;
+	gs_free bool *opt_found = NULL;
+	GString *keyfile_entries;
+	nm_auto_free_gstring GString *str_tmp = NULL;
+
+	option_names = nm_setting_wired_get_valid_s390_options (nmtst_get_rand_bool () ? NULL : s_wired);
+
+	n_opts = NM_PTRARRAY_LEN (option_names);
+	opt_keys = g_new (const char *, (n_opts + 1));
+	nmtst_rand_perm (NULL, opt_keys, option_names, sizeof (const char *), n_opts);
+	n_opts = nmtst_get_rand_uint32 () % (n_opts + 1);
+	opt_keys[n_opts] = NULL;
+
+	opt_vals = g_new0 (char *, n_opts + 1);
+	opt_found = g_new0 (bool, n_opts + 1);
+	for (i = 0; i < n_opts; i++) {
+		guint p = nmtst_get_rand_uint32 () % 1000;
+
+		if (p < 200)
+			opt_vals[i] = nm_strdup_int (i);
+		else {
+			opt_vals[i] = g_strdup_printf ("%s%s%s%s-%zu",
+			                               ((p % 5)  % 2) ? "\n" : "",
+			                               ((p % 7)  % 2) ? "\t" : "",
+			                               ((p % 11) % 2) ? "x" : "",
+			                               ((p % 13) % 2) ? "=" : "",
+			                               i);
+		}
+	}
+
+	if (nmtst_get_rand_bool ()) {
+		gs_unref_hashtable GHashTable *hash = NULL;
+
+		hash = g_hash_table_new (nm_str_hash, g_str_equal);
+		for (i = 0; i < n_opts; i++)
+			g_hash_table_insert (hash, (char *) opt_keys[i], opt_vals[i]);
+		g_object_set (s_wired,
+		              NM_SETTING_WIRED_S390_OPTIONS,
+		              hash,
+		              NULL);
+	} else {
+		_nm_setting_wired_clear_s390_options (s_wired);
+		for (i = 0; i < n_opts; i++) {
+			if (!nm_setting_wired_add_s390_option (s_wired, opt_keys[i], opt_vals[i]))
+				g_assert_not_reached ();
+		}
+	}
+
+	g_assert_cmpint (nm_setting_wired_get_num_s390_options (s_wired), ==, n_opts);
+
+	keyfile_entries = g_string_new (NULL);
+	str_tmp = g_string_new (NULL);
+	if (n_opts > 0)
+		g_string_append_printf (keyfile_entries, "[ethernet-s390-options]\n");
+	for (i = 0; i < n_opts; i++) {
+		gssize idx;
+		const char *k, *v;
+
+		nm_setting_wired_get_s390_option (s_wired, i, &k, &v);
+		g_assert (k);
+		g_assert (v);
+
+		idx = nm_utils_strv_find_first ((char **) opt_keys, n_opts, k);
+		g_assert (idx >= 0);
+		g_assert (!opt_found[idx]);
+		opt_found[idx] = TRUE;
+		g_assert_cmpstr (opt_keys[idx], ==, k);
+		g_assert_cmpstr (opt_vals[idx], ==, v);
+
+		g_string_truncate (str_tmp, 0);
+		for (j = 0; v[j] != '\0'; j++) {
+			if (v[j] == '\n')
+				g_string_append (str_tmp, "\\n");
+			else if (v[j] == '\t')
+				g_string_append (str_tmp, "\\t");
+			else
+				g_string_append_c (str_tmp, v[j]);
+		}
+
+		g_string_append_printf (keyfile_entries,
+		                        "%s=%s\n",
+		                        k,
+		                        str_tmp->str);
+	}
+	for (i = 0; i < n_opts; i++)
+		g_assert (opt_found[i]);
+	if (n_opts > 0)
+		g_string_append_printf (keyfile_entries, "\n");
+	*out_keyfile_entries = g_string_free (keyfile_entries, FALSE);
+}
+
 static GPtrArray *
 _rndt_wg_peers_create (void)
 {
@@ -2116,7 +2352,7 @@ _rndt_wg_peers_create (void)
 
 	wg_peers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_wireguard_peer_unref);
 
-	n = nmtst_get_rand_int () % 10;
+	n = nmtst_get_rand_uint32 () % 10;
 	for (i = 0; i < n; i++) {
 		NMWireGuardPeer *peer;
 		guint8 public_key_buf[NM_WIREGUARD_PUBLIC_KEY_LEN];
@@ -2147,12 +2383,12 @@ _rndt_wg_peers_create (void)
 		                                                                    NM_SETTING_SECRET_FLAG_AGENT_OWNED));
 
 		nm_wireguard_peer_set_persistent_keepalive (peer,
-		                                            nmtst_rand_select ((guint32) 0, nmtst_get_rand_int ()));
+		                                            nmtst_rand_select ((guint32) 0, nmtst_get_rand_uint32 ()));
 
 		if (!nm_wireguard_peer_set_endpoint (peer, nmtst_rand_select (s_endpoint, NULL), TRUE))
 			g_assert_not_reached ();
 
-		n_aip = nmtst_rand_select (0, nmtst_get_rand_int () % 10);
+		n_aip = nmtst_rand_select (0, nmtst_get_rand_uint32 () % 10);
 		for (i_aip = 0; i_aip < n_aip; i_aip++) {
 			gs_free char *aip = NULL;
 
@@ -2349,7 +2585,7 @@ test_roundtrip_conversion (gconstpointer test_data)
 	const char *UUID= "63376701-b61e-4318-bf7e-664a1c1eeaab";
 	const char *INTERFACE_NAME = nm_sprintf_bufa (100, "ifname%d", MODE);
 	guint32 ETH_MTU = nmtst_rand_select ((guint32) 0u,
-	                                     nmtst_get_rand_int ());
+	                                     nmtst_get_rand_uint32 ());
 	const char *WG_PRIVATE_KEY = nmtst_get_rand_bool ()
 	                             ? "yGXGK+5bVnxSJUejH4vbpXbq+ZtaG4NB8IHRK/aVtE0="
 	                             : NULL;
@@ -2357,9 +2593,9 @@ test_roundtrip_conversion (gconstpointer test_data)
 	                                                                     NM_SETTING_SECRET_FLAG_NOT_SAVED,
 	                                                                     NM_SETTING_SECRET_FLAG_AGENT_OWNED);
 	const guint WG_LISTEN_PORT = nmtst_rand_select (0u,
-	                                                nmtst_get_rand_int () % 0x10000);
+	                                                nmtst_get_rand_uint32 () % 0x10000);
 	const guint WG_FWMARK = nmtst_rand_select (0u,
-	                                           nmtst_get_rand_int ());
+	                                           nmtst_get_rand_uint32 ());
 	gs_unref_ptrarray GPtrArray *kf_data_arr = g_ptr_array_new_with_free_func (g_free);
 	gs_unref_ptrarray GPtrArray *wg_peers = NULL;
 	const NMConnectionSerializationFlags dbus_serialization_flags[] = {
@@ -2385,6 +2621,7 @@ test_roundtrip_conversion (gconstpointer test_data)
 	int is_ipv4;
 	guint i;
 	gboolean success;
+	gs_free char *s390_keyfile_entries = NULL;
 
 	switch (MODE) {
 	case 0:
@@ -2403,6 +2640,8 @@ test_roundtrip_conversion (gconstpointer test_data)
 		              ETH_MTU,
 		              NULL);
 
+		_rndt_wired_add_s390_options (s_eth, &s390_keyfile_entries);
+
 		g_ptr_array_add (kf_data_arr,
 		    g_strdup_printf ("[connection]\n"
 		                     "id=%s\n"
@@ -2415,6 +2654,7 @@ test_roundtrip_conversion (gconstpointer test_data)
 		                     "mac-address-blacklist=\n"
 		                     "%s" /* mtu */
 		                     "\n"
+		                     "%s" /* [ethernet-s390-options] */
 		                     "[ipv4]\n"
 		                     "dns-search=\n"
 		                     "method=auto\n"
@@ -2429,7 +2669,8 @@ test_roundtrip_conversion (gconstpointer test_data)
 		                     INTERFACE_NAME,
 		                       (ETH_MTU != 0)
 		                     ? nm_sprintf_bufa (100, "mtu=%u\n", ETH_MTU)
-		                     : ""));
+		                     : "",
+		                     s390_keyfile_entries));
 
 		g_ptr_array_add (kf_data_arr,
 		    g_strdup_printf ("[connection]\n"
@@ -2443,6 +2684,7 @@ test_roundtrip_conversion (gconstpointer test_data)
 		                     "mac-address-blacklist=\n"
 		                     "%s" /* mtu */
 		                     "\n"
+		                     "%s" /* [ethernet-s390-options] */
 		                     "[ipv4]\n"
 		                     "dns-search=\n"
 		                     "method=auto\n"
@@ -2457,7 +2699,8 @@ test_roundtrip_conversion (gconstpointer test_data)
 		                     INTERFACE_NAME,
 		                       (ETH_MTU != 0)
 		                     ? nm_sprintf_bufa (100, "mtu=%d\n", (int) ETH_MTU)
-		                     : ""));
+		                     : "",
+		                     s390_keyfile_entries));
 
 		break;
 
@@ -2703,6 +2946,9 @@ test_roundtrip_conversion (gconstpointer test_data)
 
 			g_assert_cmpint (nm_setting_wired_get_mtu (s_eth), ==, ETH_MTU);
 			g_assert_cmpint (nm_setting_wired_get_mtu (s_eth2), ==, ETH_MTU);
+
+			g_assert_cmpint (nm_setting_wired_get_num_s390_options (s_eth2), ==, nm_setting_wired_get_num_s390_options (s_eth));
+
 			break;
 
 		case 1:
@@ -2979,6 +3225,60 @@ test_routing_rule (gconstpointer test_data)
 
 /*****************************************************************************/
 
+static void
+test_parse_tc_handle (void)
+{
+#define _parse_tc_handle(str, exp) \
+	G_STMT_START { \
+		gs_free_error GError *_error = NULL; \
+		GError **_perror = nmtst_get_rand_bool () ? &_error : NULL; \
+		guint32 _v; \
+		const guint32 _v_exp = (exp); \
+		\
+		_v = _nm_utils_parse_tc_handle (""str"", _perror); \
+		\
+		if (_v != _v_exp) \
+			g_error ("%s:%d: \"%s\" gave %08x but %08x expected.", __FILE__, __LINE__, ""str"", _v, _v_exp); \
+		\
+		if (_v == TC_H_UNSPEC) \
+			g_assert (!_perror || *_perror); \
+		else \
+			g_assert (!_perror || !*_perror); \
+		\
+	} G_STMT_END
+
+#define _parse_tc_handle_inval(str)           _parse_tc_handle (str, TC_H_UNSPEC)
+#define _parse_tc_handle_valid(str, maj, min) _parse_tc_handle (str, TC_H_MAKE (((guint32) (maj)) << 16, ((guint16) (min))))
+
+	_parse_tc_handle_inval ("");
+	_parse_tc_handle_inval (" ");
+	_parse_tc_handle_inval (" \n");
+	_parse_tc_handle_valid ("1", 1, 0);
+	_parse_tc_handle_valid(" 1 ", 1, 0);
+	_parse_tc_handle_valid ("1:", 1, 0);
+	_parse_tc_handle_valid ("1:  ", 1, 0);
+	_parse_tc_handle_valid ("1:0", 1, 0);
+	_parse_tc_handle_valid ("1   :0", 1, 0);
+	_parse_tc_handle_valid ("1   \t\n\f\r:0", 1, 0);
+	_parse_tc_handle_inval ("1   \t\n\f\r\v:0");
+	_parse_tc_handle_valid (" 1 : 0  ", 1, 0);
+	_parse_tc_handle_inval (" \t\v\n1: 0");
+	_parse_tc_handle_valid ("1:2", 1, 2);
+	_parse_tc_handle_valid ("01:02", 1, 2);
+	_parse_tc_handle_inval ("0x01:0x02");
+	_parse_tc_handle_valid ("  01:   02", 1, 2);
+	_parse_tc_handle_valid ("019:   020", 0x19, 0x20);
+	_parse_tc_handle_valid ("FFFF:   020", 0xFFFF, 0x20);
+	_parse_tc_handle_valid ("FfFF:   ffff", 0xFFFF, 0xFFFF);
+	_parse_tc_handle_valid ("FFFF", 0xFFFF, 0);
+	_parse_tc_handle_inval ("0xFFFF");
+	_parse_tc_handle_inval ("10000");
+	_parse_tc_handle_valid ("\t\n\f\r FFFF", 0xFFFF, 0);
+	_parse_tc_handle_inval ("\t\n\f\r \vFFFF");
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int
@@ -3026,7 +3326,6 @@ main (int argc, char **argv)
 
 	g_test_add_func ("/libnm/settings/bridge/vlans", test_bridge_vlans);
 
-#if WITH_JSON_VALIDATION
 	g_test_add_func ("/libnm/settings/team/sync_runner_from_config_roundrobin",
 	                 test_runner_roundrobin_sync_from_config);
 	g_test_add_func ("/libnm/settings/team/sync_runner_from_config_broadcast",
@@ -3055,7 +3354,6 @@ main (int argc, char **argv)
 	g_test_add_func ("/libnm/settings/team-port/sync_from_config_lacp_prio", test_team_port_lacp_prio);
 	g_test_add_func ("/libnm/settings/team-port/sync_from_config_lacp_key", test_team_port_lacp_key);
 	g_test_add_func ("/libnm/settings/team-port/sycn_from_config_full", test_team_port_full_config);
-#endif
 
 	g_test_add_data_func ("/libnm/settings/roundtrip-conversion/general/0",   GINT_TO_POINTER (0), test_roundtrip_conversion);
 	g_test_add_data_func ("/libnm/settings/roundtrip-conversion/wireguard/1", GINT_TO_POINTER (1), test_roundtrip_conversion);
@@ -3064,5 +3362,9 @@ main (int argc, char **argv)
 
 	g_test_add_data_func ("/libnm/settings/routing-rule/1", GINT_TO_POINTER (0), test_routing_rule);
 
+	g_test_add_func ("/libnm/parse-tc-handle", test_parse_tc_handle);
+
+	g_test_add_func ("/libnm/test_team_setting", test_team_setting);
+
 	return g_test_run ();
 }
diff --git a/libnm-core/tests/test-settings-defaults.c b/libnm-core/tests/test-settings-defaults.c
index 9e640543..c5156fc9 100644
--- a/libnm-core/tests/test-settings-defaults.c
+++ b/libnm-core/tests/test-settings-defaults.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  *
  * This program is free software; you can redistribute it and/or modify