about summary refs log tree commit diff
path: root/libnm-core
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2017-12-12 15:53:07 +0100
committerMichael Biebl <biebl@debian.org>2017-12-12 15:53:07 +0100
commitafcd268ea7b1149fbfb66bce4eca659b675da0a2 (patch)
treec3fca2203ad17434daf3ccf576582bd66aa41ab2 /libnm-core
parent417f6015c3dc8c47cf27daa59f64e0e36c521b9c (diff)
New upstream version 1.10.2 upstream/1.10.2
Diffstat (limited to 'libnm-core')
-rw-r--r--libnm-core/nm-connection.c18
-rw-r--r--libnm-core/nm-connection.h4
-rw-r--r--libnm-core/nm-core-internal.h50
-rw-r--r--libnm-core/nm-core-types.h3
-rw-r--r--libnm-core/nm-dbus-interface.h44
-rw-r--r--libnm-core/nm-dbus-types.xml55
-rw-r--r--libnm-core/nm-errors.h2
-rw-r--r--libnm-core/nm-keyfile-reader.c122
-rw-r--r--libnm-core/nm-keyfile-utils.c2
-rw-r--r--libnm-core/nm-keyfile-writer.c146
-rw-r--r--libnm-core/nm-property-compare.c34
-rw-r--r--libnm-core/nm-setting-bond.c150
-rw-r--r--libnm-core/nm-setting-bridge.c42
-rw-r--r--libnm-core/nm-setting-ip-config.c7
-rw-r--r--libnm-core/nm-setting-ip-config.h1
-rw-r--r--libnm-core/nm-setting-ip4-config.c8
-rw-r--r--libnm-core/nm-setting-ip6-config.c10
-rw-r--r--libnm-core/nm-setting-tc-config.c1576
-rw-r--r--libnm-core/nm-setting-tc-config.h191
-rw-r--r--libnm-core/nm-setting-team-port.c471
-rw-r--r--libnm-core/nm-setting-team-port.h41
-rw-r--r--libnm-core/nm-setting-team.c1593
-rw-r--r--libnm-core/nm-setting-team.h167
-rw-r--r--libnm-core/nm-setting-wireless-security.c5
-rw-r--r--libnm-core/nm-utils-private.h120
-rw-r--r--libnm-core/nm-utils.c1625
-rw-r--r--libnm-core/nm-utils.h20
-rw-r--r--libnm-core/nm-version.h6
-rw-r--r--libnm-core/tests/test-general.c2
-rw-r--r--libnm-core/tests/test-keyfile.c5
-rw-r--r--libnm-core/tests/test-setting-8021x.c404
-rw-r--r--libnm-core/tests/test-setting-bond.c247
-rw-r--r--libnm-core/tests/test-setting-dcb.c321
-rw-r--r--libnm-core/tests/test-setting.c1632
34 files changed, 7942 insertions, 1182 deletions
diff --git a/libnm-core/nm-connection.c b/libnm-core/nm-connection.c
index d5e28f7b..9c88cfd6 100644
--- a/libnm-core/nm-connection.c
+++ b/libnm-core/nm-connection.c
@@ -16,7 +16,7 @@
  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301 USA.
  *
- * Copyright 2007 - 2013 Red Hat, Inc.
+ * Copyright 2007 - 2017 Red Hat, Inc.
  * Copyright 2007 - 2008 Novell, Inc.
  */
 
@@ -2531,6 +2531,22 @@ nm_connection_get_setting_serial (NMConnection *connection)
 }
 
 /**
+ * nm_connection_get_setting_tc_config:
+ * @connection: the #NMConnection
+ *
+ * A shortcut to return any #NMSettingTCConfig the connection might contain.
+ *
+ * Returns: (transfer none): an #NMSettingTCConfig if the connection contains one, otherwise %NULL
+ *
+ * Since: 1.10.2
+ **/
+NMSettingTCConfig *
+nm_connection_get_setting_tc_config (NMConnection *connection)
+{
+	return _connection_get_setting_check (connection, NM_TYPE_SETTING_TC_CONFIG);
+}
+
+/**
  * nm_connection_get_setting_tun:
  * @connection: the #NMConnection
  *
diff --git a/libnm-core/nm-connection.h b/libnm-core/nm-connection.h
index c6b23a00..95e11f1f 100644
--- a/libnm-core/nm-connection.h
+++ b/libnm-core/nm-connection.h
@@ -16,7 +16,7 @@
  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301 USA.
  *
- * Copyright 2007 - 2013 Red Hat, Inc.
+ * Copyright 2007 - 2017 Red Hat, Inc.
  * Copyright 2007 - 2008 Novell, Inc.
  */
 
@@ -229,6 +229,8 @@ NMSettingPppoe *           nm_connection_get_setting_pppoe             (NMConnec
 NM_AVAILABLE_IN_1_6
 NMSettingProxy *           nm_connection_get_setting_proxy             (NMConnection *connection);
 NMSettingSerial *          nm_connection_get_setting_serial            (NMConnection *connection);
+NM_AVAILABLE_IN_1_10_2
+NMSettingTCConfig *        nm_connection_get_setting_tc_config         (NMConnection *connection);
 NMSettingTun *             nm_connection_get_setting_tun               (NMConnection *connection);
 NMSettingVpn *             nm_connection_get_setting_vpn               (NMConnection *connection);
 NMSettingWimax *           nm_connection_get_setting_wimax             (NMConnection *connection);
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h
index 59ebfcb1..5a27d438 100644
--- a/libnm-core/nm-core-internal.h
+++ b/libnm-core/nm-core-internal.h
@@ -1,5 +1,4 @@
 /* -*- 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
@@ -16,7 +15,7 @@
  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301 USA.
  *
- * (C) Copyright 2014 Red Hat, Inc.
+ * (C) Copyright 2014 - 2017 Red Hat, Inc.
  */
 
 #ifndef NM_CORE_NM_INTERNAL_H
@@ -63,6 +62,7 @@
 #include "nm-setting-ppp.h"
 #include "nm-setting-pppoe.h"
 #include "nm-setting-serial.h"
+#include "nm-setting-tc-config.h"
 #include "nm-setting-team-port.h"
 #include "nm-setting-team.h"
 #include "nm-setting-tun.h"
@@ -80,6 +80,20 @@
 #include "nm-core-types-internal.h"
 #include "nm-vpn-editor-plugin.h"
 
+/* IEEE 802.1D-1998 timer values */
+#define NM_BR_MIN_HELLO_TIME    1
+#define NM_BR_MAX_HELLO_TIME    10
+
+#define NM_BR_MIN_FORWARD_DELAY 2
+#define NM_BR_MAX_FORWARD_DELAY 30
+
+#define NM_BR_MIN_MAX_AGE       6
+#define NM_BR_MAX_MAX_AGE       40
+
+/* IEEE 802.1D-1998 Table 7.4 */
+#define NM_BR_MIN_AGEING_TIME   0
+#define NM_BR_MAX_AGEING_TIME   1000000
+
 /* NM_SETTING_COMPARE_FLAG_INFERRABLE: check whether a device-generated
  * connection can be replaced by a already-defined connection. This flag only
  * takes into account properties marked with the %NM_SETTING_PARAM_INFERRABLE
@@ -230,10 +244,6 @@ gssize _nm_utils_ptrarray_find_first (gconstpointer *list, gssize len, gconstpoi
 gssize _nm_utils_ptrarray_find_binary_search (gconstpointer *list, gsize len, gconstpointer needle, GCompareDataFunc cmpfcn, gpointer user_data);
 gssize _nm_utils_array_find_binary_search (gconstpointer list, gsize elem_size, gsize len, gconstpointer needle, GCompareDataFunc cmpfcn, gpointer user_data);
 
-char **     _nm_utils_strsplit_set (const char *str,
-                                    const char *delimiters,
-                                    int max_tokens);
-
 GSList *    _nm_utils_strv_to_slist (char **strv, gboolean deep_copy);
 char **     _nm_utils_slist_to_strv (GSList *slist, gboolean deep_copy);
 
@@ -429,11 +439,24 @@ NMSettingBluetooth *_nm_connection_get_setting_bluetooth_for_nap (NMConnection *
 
 /*****************************************************************************/
 
+const char *nm_utils_inet_ntop (int addr_family, gconstpointer addr, char *dst);
+
 gboolean _nm_utils_inet6_is_token (const struct in6_addr *in6addr);
 
 /*****************************************************************************/
 
-gboolean    _nm_utils_team_config_equal (const char *conf1, const char *conf2, gboolean port);
+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);
+
+gboolean _nm_utils_team_config_set (char **conf,
+                                    const char *key,
+                                    const char *key2,
+                                    const char *key3,
+                                    const GValue *value);
 
 /*****************************************************************************/
 
@@ -449,4 +472,17 @@ nm_setting_ip_config_get_addr_family (NMSettingIPConfig *s_ip)
 
 /*****************************************************************************/
 
+guint32 _nm_utils_parse_tc_handle                (const char *str,
+                                                  GError **error);
+void _nm_utils_string_append_tc_parent           (GString *string,
+                                                  const char *prefix,
+                                                  guint32 parent);
+void _nm_utils_string_append_tc_qdisc_rest       (GString *string,
+                                                  NMTCQdisc *qdisc);
+gboolean _nm_utils_string_append_tc_tfilter_rest (GString *string,
+                                                  NMTCTfilter *tfilter,
+                                                  GError **error);
+
+/*****************************************************************************/
+
 #endif
diff --git a/libnm-core/nm-core-types.h b/libnm-core/nm-core-types.h
index ae680b7a..6a274333 100644
--- a/libnm-core/nm-core-types.h
+++ b/libnm-core/nm-core-types.h
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright 2014 Red Hat, Inc.
+ * Copyright 2014 - 2017 Red Hat, Inc.
  */
 
 #ifndef __NM_CORE_TYPES_H__
@@ -57,6 +57,7 @@ typedef struct _NMSettingOvsPort          NMSettingOvsPort;
 typedef struct _NMSettingPpp              NMSettingPpp;
 typedef struct _NMSettingPppoe            NMSettingPppoe;
 typedef struct _NMSettingSerial           NMSettingSerial;
+typedef struct _NMSettingTCConfig         NMSettingTCConfig;
 typedef struct _NMSettingTeam             NMSettingTeam;
 typedef struct _NMSettingTeamPort         NMSettingTeamPort;
 typedef struct _NMSettingTun              NMSettingTun;
diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h
index 7f75c849..98c94154 100644
--- a/libnm-core/nm-dbus-interface.h
+++ b/libnm-core/nm-dbus-interface.h
@@ -893,4 +893,48 @@ typedef enum { /*< flags >*/
 	NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES          = (1LL <<  5),
 } NMActivationStateFlags;
 
+/**
+ * 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_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.
+ * @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.
+ *   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.
+ * @NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT: usually, when the connection
+ *   has autoconnect enabled and is modified, it becomes elegible to autoconnect
+ *   right away. Setting this flag, disables autoconnect until the connection
+ *   is manually activated.
+ *
+ * Since: 1.10.2
+ */
+typedef enum { /*< flags >*/
+	NM_SETTINGS_UPDATE2_FLAG_NONE                       = 0,
+	NM_SETTINGS_UPDATE2_FLAG_TO_DISK                    = (1LL <<  0),
+	NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY                  = (1LL <<  1),
+	NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED         = (1LL <<  2),
+	NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY             = (1LL <<  3),
+	NM_SETTINGS_UPDATE2_FLAG_VOLATILE                   = (1LL <<  4),
+	NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT          = (1LL <<  5),
+} NMSettingsUpdate2Flags;
+
 #endif /* __NM_DBUS_INTERFACE_H__ */
diff --git a/libnm-core/nm-dbus-types.xml b/libnm-core/nm-dbus-types.xml
index ba58a615..bc0c023b 100644
--- a/libnm-core/nm-dbus-types.xml
+++ b/libnm-core/nm-dbus-types.xml
@@ -1603,4 +1603,59 @@
     </refsect3>
   </refsect2>
 
+  <refsect2 id="NMSettingsUpdate2Flags" role="enum">
+    <title>enum NMSettingsUpdate2Flags</title>
+    <indexterm zone="NMSettingsUpdate2Flags">
+      <primary>NMSettingsUpdate2Flags</primary>
+    </indexterm>
+    <para><para>Since: 1.10.2</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_UPDATE2_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_UPDATE2_FLAG_TO_DISK</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>to persist the connection to disk.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>to make the connection in-memory only. If the connection was previously persistent, the corresponding file on disk is not deleted but merely the connection is decoupled from the file on disk. If you later delete an in-memory connection, the connection on disk will be deleted as well.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>this is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, but if the connection has a corresponding file on disk, the association between the connection and the file is forgotten but the file is not modified. The difference to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY is if you later save the connection again to disk, a new file name will be chosen without overwriting the remaining file on disk. Also, if you delete the connection later, the file on disk will not be deleted.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>this is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, but if the connection has a corresponding file on disk, the file on disk will be deleted.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_VOLATILE</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>This can be specified with either %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED or %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY. After making the connection in-memory only, the connection is marked as volatile. That means, if the connection is currently not active it will be deleted right away. Otherwise, it is marked to for deletion once the connection deactivates. A volatile connection cannot autoactivate again (because it's about to be deleted), but a manual activation will clear the volatile flag.</para><para></para></entry>
+            </row>
+            <row role="constant">
+              <entry role="enum_member_name"><para>NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT</para><para></para></entry>
+              <entry role="enum_member_value"><para>= <literal>(1LL</literal></para><para></para></entry>
+              <entry role="enum_member_description"><para>usually, when the connection has autoconnect enabled and is modified, it becomes elegible 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>
+
 </refentry>
diff --git a/libnm-core/nm-errors.h b/libnm-core/nm-errors.h
index 6bacc8da..bdd6c223 100644
--- a/libnm-core/nm-errors.h
+++ b/libnm-core/nm-errors.h
@@ -261,6 +261,7 @@ GQuark nm_secret_agent_error_quark (void);
  * @NM_SETTINGS_ERROR_READ_ONLY_CONNECTION: attempted to modify a read-only connection
  * @NM_SETTINGS_ERROR_UUID_EXISTS: a connection with that UUID already exists
  * @NM_SETTINGS_ERROR_INVALID_HOSTNAME: attempted to set an invalid hostname
+ * @NM_SETTINGS_ERROR_INVALID_ARGUMENTS: invalid arguments
  *
  * Errors related to the settings/persistent configuration interface of
  * NetworkManager.
@@ -277,6 +278,7 @@ typedef enum {
 	NM_SETTINGS_ERROR_READ_ONLY_CONNECTION, /*< nick=ReadOnlyConnection >*/
 	NM_SETTINGS_ERROR_UUID_EXISTS,          /*< nick=UuidExists >*/
 	NM_SETTINGS_ERROR_INVALID_HOSTNAME,     /*< nick=InvalidHostname >*/
+	NM_SETTINGS_ERROR_INVALID_ARGUMENTS,    /*< nick=InvalidArguments >*/
 } NMSettingsError;
 
 GQuark nm_settings_error_quark (void);
diff --git a/libnm-core/nm-keyfile-reader.c b/libnm-core/nm-keyfile-reader.c
index 0ac417cd..20b5a3fc 100644
--- a/libnm-core/nm-keyfile-reader.c
+++ b/libnm-core/nm-keyfile-reader.c
@@ -16,7 +16,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2008 - 2009 Novell, Inc.
- * Copyright (C) 2008 - 2015 Red Hat, Inc.
+ * Copyright (C) 2008 - 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -30,6 +30,7 @@
 #include <sys/types.h>
 #include <arpa/inet.h>
 #include <string.h>
+#include <linux/pkt_sched.h>
 
 #include "nm-common-macros.h"
 #include "nm-core-internal.h"
@@ -1313,6 +1314,102 @@ team_config_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key
 	g_object_set (G_OBJECT (setting), key, conf, NULL);
 }
 
+static void
+qdisc_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GPtrArray *qdiscs;
+	gs_strfreev gchar **keys = NULL;
+	gsize n_keys = 0;
+	int i;
+
+	qdiscs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_qdisc_unref);
+
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
+	if (!keys || n_keys == 0)
+		return;
+
+	for (i = 0; i < n_keys; i++) {
+		NMTCQdisc *qdisc;
+		const char *qdisc_parent;
+		gs_free char *qdisc_rest = NULL;
+		gs_free char *qdisc_str = NULL;
+		gs_free_error GError *err = NULL;
+
+		if (!g_str_has_prefix (keys[i], "qdisc."))
+			continue;
+
+		qdisc_parent = keys[i] + sizeof ("qdisc.") - 1;
+		qdisc_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL);
+		qdisc_str = g_strdup_printf ("%s%s %s",
+		                             _nm_utils_parse_tc_handle (qdisc_parent, NULL) != TC_H_UNSPEC ? "parent " : "",
+		                             qdisc_parent,
+		                             qdisc_rest);
+
+		qdisc = nm_utils_tc_qdisc_from_str (qdisc_str, &err);
+		if (!qdisc) {
+			handle_warn (info, keys[i], NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid qdisc: %s"),
+			             err->message);
+		} else {
+			g_ptr_array_add (qdiscs, qdisc);
+		}
+	}
+
+	if (qdiscs->len >= 1)
+		g_object_set (setting, key, qdiscs, NULL);
+
+	g_ptr_array_unref (qdiscs);
+}
+
+static void
+tfilter_parser (KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+	const char *setting_name = nm_setting_get_name (setting);
+	GPtrArray *tfilters;
+	gs_strfreev gchar **keys = NULL;
+	gsize n_keys = 0;
+	int i;
+
+	tfilters = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_tfilter_unref);
+
+	keys = nm_keyfile_plugin_kf_get_keys (info->keyfile, setting_name, &n_keys, NULL);
+	if (!keys || n_keys == 0)
+		return;
+
+	for (i = 0; i < n_keys; i++) {
+		NMTCTfilter *tfilter;
+		const char *tfilter_parent;
+		gs_free char *tfilter_rest = NULL;
+		gs_free char *tfilter_str = NULL;
+		gs_free_error GError *err = NULL;
+
+		if (!g_str_has_prefix (keys[i], "tfilter."))
+			continue;
+
+		tfilter_parent = keys[i] + sizeof ("tfilter.") - 1;
+		tfilter_rest = nm_keyfile_plugin_kf_get_string (info->keyfile, setting_name, keys[i], NULL);
+		tfilter_str = g_strdup_printf ("%s%s %s",
+		                             _nm_utils_parse_tc_handle (tfilter_parent, NULL) != TC_H_UNSPEC ? "parent " : "",
+		                             tfilter_parent,
+		                             tfilter_rest);
+
+		tfilter = nm_utils_tc_tfilter_from_str (tfilter_str, &err);
+		if (!tfilter) {
+			handle_warn (info, keys[i], NM_KEYFILE_WARN_SEVERITY_WARN,
+			             _("invalid tfilter: %s"),
+			             err->message);
+		} else {
+			g_ptr_array_add (tfilters, tfilter);
+		}
+	}
+
+	if (tfilters->len >= 1)
+		g_object_set (setting, key, tfilters, NULL);
+
+	g_ptr_array_unref (tfilters);
+}
+
 typedef struct {
 	const char *setting_name;
 	const char *key;
@@ -1439,6 +1536,14 @@ static KeyParser key_parsers[] = {
 	  NM_SETTING_TEAM_CONFIG,
 	  TRUE,
 	  team_config_parser },
+        { NM_SETTING_TC_CONFIG_SETTING_NAME,
+          NM_SETTING_TC_CONFIG_QDISCS,
+	  FALSE,
+          qdisc_parser },
+        { NM_SETTING_TC_CONFIG_SETTING_NAME,
+          NM_SETTING_TC_CONFIG_TFILTERS,
+	  FALSE,
+          tfilter_parser },
 	{ NULL, NULL, FALSE }
 };
 
@@ -1480,6 +1585,13 @@ read_one_setting_value (NMSetting *setting,
 	    && !strcmp (key, NM_SETTING_CONNECTION_READ_ONLY))
 		return;
 
+	if (   (   NM_IS_SETTING_TEAM (setting)
+	        || NM_IS_SETTING_TEAM_PORT (setting))
+	    && !NM_IN_STRSET (key, NM_SETTING_TEAM_CONFIG)) {
+		/* silently ignore all team properties (except "config"). */
+		return;
+	}
+
 	setting_name = nm_setting_get_name (setting);
 
 	/* Look through the list of handlers for non-standard format key values */
@@ -1491,15 +1603,11 @@ read_one_setting_value (NMSetting *setting,
 		parser++;
 	}
 
-	/* VPN properties don't have the exact key name */
 	if (NM_IS_SETTING_VPN (setting))
 		check_for_key = FALSE;
-
-	if (NM_IS_SETTING_USER (setting))
+	else if (NM_IS_SETTING_USER (setting))
 		check_for_key = FALSE;
-
-	/* Bonding 'options' don't have the exact key name. The options are right under [bond] group. */
-	if (NM_IS_SETTING_BOND (setting))
+	else if (NM_IS_SETTING_BOND (setting))
 		check_for_key = FALSE;
 
 	/* Check for the exact key in the GKeyFile if required.  Most setting
diff --git a/libnm-core/nm-keyfile-utils.c b/libnm-core/nm-keyfile-utils.c
index 1b7860d8..88583319 100644
--- a/libnm-core/nm-keyfile-utils.c
+++ b/libnm-core/nm-keyfile-utils.c
@@ -442,7 +442,7 @@ _keyfile_key_encode (const char *name,
 		        && g_ascii_isxdigit (name[i + 2]))
 		    || (   ch == ' '
 		        && name[i + 1] == '\0'))
-			g_string_append_printf (str, "\\%2X", ch);
+			g_string_append_printf (str, "\\%02X", ch);
 		else
 			g_string_append_c (str, (char) ch);
 	}
diff --git a/libnm-core/nm-keyfile-writer.c b/libnm-core/nm-keyfile-writer.c
index 30d519c4..0333b644 100644
--- a/libnm-core/nm-keyfile-writer.c
+++ b/libnm-core/nm-keyfile-writer.c
@@ -16,7 +16,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2008 Novell, Inc.
- * Copyright (C) 2008 - 2015 Red Hat, Inc.
+ * Copyright (C) 2008 - 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -254,6 +254,72 @@ route_writer (KeyfileWriterInfo *info,
 }
 
 static void
+qdisc_writer (KeyfileWriterInfo *info,
+              NMSetting *setting,
+              const char *key,
+              const GValue *value)
+{
+	gsize i;
+	GPtrArray *array;
+
+	array = (GPtrArray *) g_value_get_boxed (value);
+	if (!array || !array->len)
+		return;
+
+	for (i = 0; i < array->len; i++) {
+		NMTCQdisc *qdisc = array->pdata[i];
+		GString *key_name = g_string_sized_new (16);
+		GString *value_str = g_string_sized_new (60);
+
+		g_string_append (key_name, "qdisc.");
+		_nm_utils_string_append_tc_parent (key_name, NULL,
+		                                   nm_tc_qdisc_get_parent (qdisc));
+		_nm_utils_string_append_tc_qdisc_rest (value_str, qdisc);
+
+		nm_keyfile_plugin_kf_set_string (info->keyfile,
+		                                 NM_SETTING_TC_CONFIG_SETTING_NAME,
+		                                 key_name->str,
+		                                 value_str->str);
+
+		g_string_free (key_name, TRUE);
+		g_string_free (value_str, TRUE);
+	}
+}
+
+static void
+tfilter_writer (KeyfileWriterInfo *info,
+              NMSetting *setting,
+              const char *key,
+              const GValue *value)
+{
+	gsize i;
+	GPtrArray *array;
+
+	array = (GPtrArray *) g_value_get_boxed (value);
+	if (!array || !array->len)
+		return;
+
+	for (i = 0; i < array->len; i++) {
+		NMTCTfilter *tfilter = array->pdata[i];
+		GString *key_name = g_string_sized_new (16);
+		GString *value_str = g_string_sized_new (60);
+
+		g_string_append (key_name, "tfilter.");
+		_nm_utils_string_append_tc_parent (key_name, NULL,
+		                                   nm_tc_tfilter_get_parent (tfilter));
+		_nm_utils_string_append_tc_tfilter_rest (value_str, tfilter, NULL);
+
+		nm_keyfile_plugin_kf_set_string (info->keyfile,
+		                                 NM_SETTING_TC_CONFIG_SETTING_NAME,
+		                                 key_name->str,
+		                                 value_str->str);
+
+		g_string_free (key_name, TRUE);
+		g_string_free (value_str, TRUE);
+	}
+}
+
+static void
 write_hash_of_string (GKeyFile *file,
                       NMSetting *setting,
                       const char *key,
@@ -501,6 +567,15 @@ cert_writer (KeyfileWriterInfo *info,
 	cert_writer_default (info->connection, info->keyfile, &type_data);
 }
 
+static void
+null_writer (KeyfileWriterInfo *info,
+             NMSetting *setting,
+             const char *key,
+             const GValue *value)
+{
+	/* skip */
+}
+
 /*****************************************************************************/
 
 typedef struct {
@@ -576,6 +651,75 @@ static KeyWriter key_writers[] = {
 	{ NM_SETTING_802_1X_SETTING_NAME,
 	  NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
 	  cert_writer },
+	{ NM_SETTING_TC_CONFIG_SETTING_NAME,
+	  NM_SETTING_TC_CONFIG_QDISCS,
+	  qdisc_writer },
+	{ NM_SETTING_TC_CONFIG_SETTING_NAME,
+	  NM_SETTING_TC_CONFIG_TFILTERS,
+	  tfilter_writer },
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_NOTIFY_PEERS_COUNT,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_MCAST_REJOIN_COUNT,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_RUNNER,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_RUNNER_HWADDR_POLICY,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_RUNNER_TX_HASH,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_RUNNER_TX_BALANCER,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_RUNNER_ACTIVE,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_RUNNER_FAST_RATE,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_RUNNER_SYS_PRIO,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_RUNNER_MIN_PORTS,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY,
+	  null_writer},
+	{ NM_SETTING_TEAM_SETTING_NAME,
+	  NM_SETTING_TEAM_LINK_WATCHERS,
+	  null_writer},
+	{ NM_SETTING_TEAM_PORT_SETTING_NAME,
+	  NM_SETTING_TEAM_PORT_QUEUE_ID,
+	  null_writer},
+	{ NM_SETTING_TEAM_PORT_SETTING_NAME,
+	  NM_SETTING_TEAM_PORT_PRIO,
+	  null_writer},
+	{ NM_SETTING_TEAM_PORT_SETTING_NAME,
+	  NM_SETTING_TEAM_PORT_STICKY,
+	  null_writer},
+	{ NM_SETTING_TEAM_PORT_SETTING_NAME,
+	  NM_SETTING_TEAM_PORT_LACP_PRIO,
+	  null_writer},
+	{ NM_SETTING_TEAM_PORT_SETTING_NAME,
+	  NM_SETTING_TEAM_PORT_LACP_KEY,
+	  null_writer},
+	{ NM_SETTING_TEAM_PORT_SETTING_NAME,
+	  NM_SETTING_TEAM_PORT_LINK_WATCHERS,
+	  null_writer},
 	{ NULL, NULL, NULL }
 };
 
diff --git a/libnm-core/nm-property-compare.c b/libnm-core/nm-property-compare.c
index d13d019f..64ed663c 100644
--- a/libnm-core/nm-property-compare.c
+++ b/libnm-core/nm-property-compare.c
@@ -56,6 +56,38 @@ _nm_property_compare_collection (GVariant *value1, GVariant *value2)
 }
 
 static gint
+_nm_property_compare_vardict (GVariant *value1, GVariant *value2)
+{
+	GVariantIter iter;
+	int len1, len2;
+	const char *key;
+	GVariant *val1, *val2;
+
+	len1 = g_variant_n_children (value1);
+	len2 = g_variant_n_children (value2);
+
+	if (len1 != len2)
+		return len1 < len2 ? -1 : 1;
+
+	g_variant_iter_init (&iter, value1);
+	while (g_variant_iter_next (&iter, "{&sv}", &key, &val1)) {
+		if (!g_variant_lookup (value2, key, "v", &val2)) {
+			g_variant_unref (val1);
+			return -1;
+		}
+		if (!g_variant_equal (val1, val2)) {
+			g_variant_unref (val1);
+			g_variant_unref (val2);
+			return -1;
+		}
+		g_variant_unref (val1);
+		g_variant_unref (val2);
+	}
+
+	return 0;
+}
+
+static gint
 _nm_property_compare_strdict (GVariant *value1, GVariant *value2)
 {
 	GVariantIter iter;
@@ -106,6 +138,8 @@ nm_property_compare (GVariant *value1, GVariant *value2)
 		ret = g_variant_compare (value1, value2);
 	else if (g_variant_is_of_type (value1, G_VARIANT_TYPE ("a{ss}")))
 		ret = _nm_property_compare_strdict (value1, value2);
+	else if (g_variant_is_of_type (value1, G_VARIANT_TYPE ("a{sv}")))
+		ret = _nm_property_compare_vardict (value1, value2);
 	else if (g_variant_type_is_array (type1))
 		ret = _nm_property_compare_collection (value1, value2);
 	else if (g_variant_type_is_tuple (type1))
diff --git a/libnm-core/nm-setting-bond.c b/libnm-core/nm-setting-bond.c
index d12940f6..a44972cf 100644
--- a/libnm-core/nm-setting-bond.c
+++ b/libnm-core/nm-setting-bond.c
@@ -34,6 +34,8 @@
 #include "nm-setting-infiniband.h"
 #include "nm-core-internal.h"
 
+/*****************************************************************************/
+
 /**
  * SECTION:nm-setting-bond
  * @short_description: Describes connection properties for bonds
@@ -42,15 +44,7 @@
  * necessary for bond connections.
  **/
 
-G_DEFINE_TYPE_WITH_CODE (NMSettingBond, nm_setting_bond, NM_TYPE_SETTING,
-                         _nm_register_setting (BOND, NM_SETTING_PRIORITY_HW_BASE))
-NM_SETTING_REGISTER_TYPE (NM_TYPE_SETTING_BOND)
-
-#define NM_SETTING_BOND_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_SETTING_BOND, NMSettingBondPrivate))
-
-typedef struct {
-	GHashTable *options;
-} NMSettingBondPrivate;
+/*****************************************************************************/
 
 enum {
 	PROP_0,
@@ -59,6 +53,19 @@ enum {
 };
 
 typedef struct {
+	GHashTable *options;
+	NMUtilsNamedValue *options_idx_cache;
+} NMSettingBondPrivate;
+
+G_DEFINE_TYPE_WITH_CODE (NMSettingBond, nm_setting_bond, NM_TYPE_SETTING,
+                         _nm_register_setting (BOND, NM_SETTING_PRIORITY_HW_BASE))
+NM_SETTING_REGISTER_TYPE (NM_TYPE_SETTING_BOND)
+
+#define NM_SETTING_BOND_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_SETTING_BOND, NMSettingBondPrivate))
+
+/*****************************************************************************/
+
+typedef struct {
 	const char *opt;
 	const char *val;
 	guint opt_type;
@@ -104,18 +111,7 @@ static const BondDefault defaults[] = {
 	{ NM_SETTING_BOND_OPTION_LP_INTERVAL,      "1",          NM_BOND_OPTION_TYPE_INT, 1, G_MAXINT },
 };
 
-/**
- * nm_setting_bond_new:
- *
- * Creates a new #NMSettingBond object with default values.
- *
- * Returns: (transfer full): the new empty #NMSettingBond object
- **/
-NMSetting *
-nm_setting_bond_new (void)
-{
-	return (NMSetting *) g_object_new (NM_TYPE_SETTING_BOND, NULL);
-}
+/*****************************************************************************/
 
 /**
  * nm_setting_bond_get_num_options:
@@ -163,26 +159,38 @@ nm_setting_bond_get_option (NMSettingBond *setting,
                             const char **out_value)
 {
 	NMSettingBondPrivate *priv;
-	GList *keys;
-	const char *_key = NULL, *_value = NULL;
+	guint i, len;
+	GHashTableIter iter;
+	const char *key, *value;
 
 	g_return_val_if_fail (NM_IS_SETTING_BOND (setting), FALSE);
 
 	priv = NM_SETTING_BOND_GET_PRIVATE (setting);
 
-	if (idx >= nm_setting_bond_get_num_options (setting))
+	len = g_hash_table_size (priv->options);
+	if (idx >= len)
 		return FALSE;
 
-	keys = g_hash_table_get_keys (priv->options);
-	_key = g_list_nth_data (keys, idx);
-	_value = g_hash_table_lookup (priv->options, _key);
+	if (!G_UNLIKELY (priv->options_idx_cache)) {
+		NMUtilsNamedValue *options;
+
+		i = 0;
+		options = g_new (NMUtilsNamedValue, len);
+		g_hash_table_iter_init (&iter, priv->options);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) {
+			options[i].name = key;
+			options[i].value_str = value;
+			i++;
+		}
+		nm_assert (i == len);
 
-	if (out_name)
-		*out_name = _key;
-	if (out_value)
-		*out_value = _value;
+		g_qsort_with_data (options, len, sizeof (options[0]),
+		                   nm_utils_named_entry_cmp_with_data, NULL);
+		priv->options_idx_cache = options;
+	}
 
-	g_list_free (keys);
+	NM_SET_OUT (out_name, priv->options_idx_cache[idx].name);
+	NM_SET_OUT (out_value, priv->options_idx_cache[idx].value_str);
 	return TRUE;
 }
 
@@ -362,6 +370,7 @@ nm_setting_bond_add_option (NMSettingBond *setting,
 
 	priv = NM_SETTING_BOND_GET_PRIVATE (setting);
 
+	nm_clear_g_free (&priv->options_idx_cache);
 	g_hash_table_insert (priv->options, g_strdup (name), g_strdup (value));
 
 	if (   !strcmp (name, NM_SETTING_BOND_OPTION_MIIMON)
@@ -395,6 +404,7 @@ gboolean
 nm_setting_bond_remove_option (NMSettingBond *setting,
                                const char *name)
 {
+	NMSettingBondPrivate *priv;
 	gboolean found;
 
 	g_return_val_if_fail (NM_IS_SETTING_BOND (setting), FALSE);
@@ -402,7 +412,10 @@ nm_setting_bond_remove_option (NMSettingBond *setting,
 	if (!nm_setting_bond_validate_option (name, NULL))
 		return FALSE;
 
-	found = g_hash_table_remove (NM_SETTING_BOND_GET_PRIVATE (setting)->options, name);
+	priv = NM_SETTING_BOND_GET_PRIVATE (setting);
+
+	nm_clear_g_free (&priv->options_idx_cache);
+	found = g_hash_table_remove (priv->options, name);
 	if (found)
 		g_object_notify (G_OBJECT (setting), NM_SETTING_BOND_OPTIONS);
 	return found;
@@ -514,6 +527,8 @@ _nm_setting_bond_mode_from_string (const char *str)
 	return NM_BOND_MODE_UNKNOWN;
 }
 
+/*****************************************************************************/
+
 #define BIT(x) (1 << (x))
 
 static const struct {
@@ -812,6 +827,8 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	return TRUE;
 }
 
+/*****************************************************************************/
+
 static gboolean
 options_hash_match (NMSettingBond *s_bond,
                     GHashTable *options1,
@@ -887,25 +904,22 @@ compare_property (NMSetting *setting,
 	return parent_class->compare_property (setting, other, prop_spec, flags);
 }
 
-static void
-nm_setting_bond_init (NMSettingBond *setting)
-{
-	NMSettingBondPrivate *priv = NM_SETTING_BOND_GET_PRIVATE (setting);
-
-	priv->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-
-	/* Default values: */
-	nm_setting_bond_add_option (setting, NM_SETTING_BOND_OPTION_MODE, "balance-rr");
-}
+/*****************************************************************************/
 
 static void
-finalize (GObject *object)
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
 {
 	NMSettingBondPrivate *priv = NM_SETTING_BOND_GET_PRIVATE (object);
 
-	g_hash_table_destroy (priv->options);
-
-	G_OBJECT_CLASS (nm_setting_bond_parent_class)->finalize (object);
+	switch (prop_id) {
+	case PROP_OPTIONS:
+		g_value_take_boxed (value, _nm_utils_copy_strdict (priv->options));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
 }
 
 static void
@@ -916,6 +930,7 @@ set_property (GObject *object, guint prop_id,
 
 	switch (prop_id) {
 	case PROP_OPTIONS:
+		nm_clear_g_free (&priv->options_idx_cache);
 		g_hash_table_unref (priv->options);
 		priv->options = _nm_utils_copy_strdict (g_value_get_boxed (value));
 		break;
@@ -925,20 +940,41 @@ set_property (GObject *object, guint prop_id,
 	}
 }
 
+/*****************************************************************************/
+
 static void
-get_property (GObject *object, guint prop_id,
-              GValue *value, GParamSpec *pspec)
+nm_setting_bond_init (NMSettingBond *setting)
+{
+	NMSettingBondPrivate *priv = NM_SETTING_BOND_GET_PRIVATE (setting);
+
+	priv->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+	/* Default values: */
+	nm_setting_bond_add_option (setting, NM_SETTING_BOND_OPTION_MODE, "balance-rr");
+}
+
+/**
+ * nm_setting_bond_new:
+ *
+ * Creates a new #NMSettingBond object with default values.
+ *
+ * Returns: (transfer full): the new empty #NMSettingBond object
+ **/
+NMSetting *
+nm_setting_bond_new (void)
+{
+	return (NMSetting *) g_object_new (NM_TYPE_SETTING_BOND, NULL);
+}
+
+static void
+finalize (GObject *object)
 {
 	NMSettingBondPrivate *priv = NM_SETTING_BOND_GET_PRIVATE (object);
 
-	switch (prop_id) {
-	case PROP_OPTIONS:
-		g_value_take_boxed (value, _nm_utils_copy_strdict (priv->options));
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
+	nm_clear_g_free (&priv->options_idx_cache);
+	g_hash_table_destroy (priv->options);
+
+	G_OBJECT_CLASS (nm_setting_bond_parent_class)->finalize (object);
 }
 
 static void
diff --git a/libnm-core/nm-setting-bridge.c b/libnm-core/nm-setting-bridge.c
index 53300384..152cdf65 100644
--- a/libnm-core/nm-setting-bridge.c
+++ b/libnm-core/nm-setting-bridge.c
@@ -213,20 +213,6 @@ nm_setting_bridge_get_multicast_snooping (NMSettingBridge *setting)
 	return NM_SETTING_BRIDGE_GET_PRIVATE (setting)->multicast_snooping;
 }
 
-/* IEEE 802.1D-1998 timer values */
-#define BR_MIN_HELLO_TIME    1
-#define BR_MAX_HELLO_TIME    10
-
-#define BR_MIN_FORWARD_DELAY 2
-#define BR_MAX_FORWARD_DELAY 30
-
-#define BR_MIN_MAX_AGE       6
-#define BR_MAX_MAX_AGE       40
-
-/* IEEE 802.1D-1998 Table 7.4 */
-#define BR_MIN_AGEING_TIME   0
-#define BR_MAX_AGEING_TIME   1000000
-
 static inline gboolean
 check_range (guint32 val,
              guint32 min,
@@ -265,32 +251,32 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 	}
 
 	if (!check_range (priv->forward_delay,
-	                  BR_MIN_FORWARD_DELAY,
-	                  BR_MAX_FORWARD_DELAY,
+	                  NM_BR_MIN_FORWARD_DELAY,
+	                  NM_BR_MAX_FORWARD_DELAY,
 	                  !priv->stp,
 	                  NM_SETTING_BRIDGE_FORWARD_DELAY,
 	                  error))
 		return FALSE;
 
 	if (!check_range (priv->hello_time,
-	                  BR_MIN_HELLO_TIME,
-	                  BR_MAX_HELLO_TIME,
+	                  NM_BR_MIN_HELLO_TIME,
+	                  NM_BR_MAX_HELLO_TIME,
 	                  !priv->stp,
 	                  NM_SETTING_BRIDGE_HELLO_TIME,
 	                  error))
 		return FALSE;
 
 	if (!check_range (priv->max_age,
-	                  BR_MIN_MAX_AGE,
-	                  BR_MAX_MAX_AGE,
+	                  NM_BR_MIN_MAX_AGE,
+	                  NM_BR_MAX_MAX_AGE,
 	                  !priv->stp,
 	                  NM_SETTING_BRIDGE_MAX_AGE,
 	                  error))
 		return FALSE;
 
 	if (!check_range (priv->ageing_time,
-	                  BR_MIN_AGEING_TIME,
-	                  BR_MAX_AGEING_TIME,
+	                  NM_BR_MIN_AGEING_TIME,
+	                  NM_BR_MAX_AGEING_TIME,
 	                  !priv->stp,
 	                  NM_SETTING_BRIDGE_AGEING_TIME,
 	                  error))
@@ -443,10 +429,10 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *setting_class)
 	 * ---end---
 	 * ---ifcfg-rh---
 	 * property: mac-address
-	 * variable: MACADDR(+)
+	 * variable: BRIDGE_MACADDR(+)
 	 * description: MAC address of the bridge. Note that this requires a recent
 	 *   kernel support, originally introduced in 3.15 upstream kernel)
-	 *   MACADDR for bridges is an NM extension.
+	 *   BRIDGE_MACADDR for bridges is an NM extension.
 	 * ---end---
 	 */
 	g_object_class_install_property
@@ -522,7 +508,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *setting_class)
 	g_object_class_install_property
 		(object_class, PROP_FORWARD_DELAY,
 		 g_param_spec_uint (NM_SETTING_BRIDGE_FORWARD_DELAY, "", "",
-		                    0, BR_MAX_FORWARD_DELAY, 15,
+		                    0, NM_BR_MAX_FORWARD_DELAY, 15,
 		                    G_PARAM_READWRITE |
 		                    G_PARAM_CONSTRUCT |
 		                    NM_SETTING_PARAM_INFERRABLE |
@@ -544,7 +530,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *setting_class)
 	g_object_class_install_property
 		(object_class, PROP_HELLO_TIME,
 		 g_param_spec_uint (NM_SETTING_BRIDGE_HELLO_TIME, "", "",
-		                    0, BR_MAX_HELLO_TIME, 2,
+		                    0, NM_BR_MAX_HELLO_TIME, 2,
 		                    G_PARAM_READWRITE |
 		                    G_PARAM_CONSTRUCT |
 		                    NM_SETTING_PARAM_INFERRABLE |
@@ -566,7 +552,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *setting_class)
 	g_object_class_install_property
 		(object_class, PROP_MAX_AGE,
 		 g_param_spec_uint (NM_SETTING_BRIDGE_MAX_AGE, "", "",
-		                    0, BR_MAX_MAX_AGE, 20,
+		                    0, NM_BR_MAX_MAX_AGE, 20,
 		                    G_PARAM_READWRITE |
 		                    G_PARAM_CONSTRUCT |
 		                    NM_SETTING_PARAM_INFERRABLE |
@@ -588,7 +574,7 @@ nm_setting_bridge_class_init (NMSettingBridgeClass *setting_class)
 	g_object_class_install_property
 		(object_class, PROP_AGEING_TIME,
 		 g_param_spec_uint (NM_SETTING_BRIDGE_AGEING_TIME, "", "",
-		                    0, BR_MAX_AGEING_TIME, 300,
+		                    NM_BR_MIN_AGEING_TIME, NM_BR_MAX_AGEING_TIME, 300,
 		                    G_PARAM_READWRITE |
 		                    G_PARAM_CONSTRUCT |
 		                    NM_SETTING_PARAM_INFERRABLE |
diff --git a/libnm-core/nm-setting-ip-config.c b/libnm-core/nm-setting-ip-config.c
index 7265fdc5..779d4428 100644
--- a/libnm-core/nm-setting-ip-config.c
+++ b/libnm-core/nm-setting-ip-config.c
@@ -16,7 +16,7 @@
  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301 USA.
  *
- * Copyright 2007 - 2014 Red Hat, Inc.
+ * Copyright 2007 - 2017 Red Hat, Inc.
  * Copyright 2007 - 2008 Novell, Inc.
  */
 
@@ -1238,13 +1238,14 @@ nm_ip_route_set_attribute (NMIPRoute *route, const char *name, GVariant *value)
 }
 
 #define ATTR_SPEC_PTR(name, type, v4, v6, str_type) \
-	&(NMVariantAttributeSpec) { 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,  FALSE, 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 ),
@@ -1790,7 +1791,7 @@ nm_setting_ip_config_get_num_dns_options (NMSettingIPConfig *setting)
  * or the options are left undefined. The latter means to use
  * a default configuration, while the former explicitly means "no-options".
  *
- * Returns: whether DNS options are initalized or left unset (the default).
+ * Returns: whether DNS options are initialized or left unset (the default).
  **/
 gboolean
 nm_setting_ip_config_has_dns_options (NMSettingIPConfig *setting)
diff --git a/libnm-core/nm-setting-ip-config.h b/libnm-core/nm-setting-ip-config.h
index 84ce8465..96e61bb5 100644
--- a/libnm-core/nm-setting-ip-config.h
+++ b/libnm-core/nm-setting-ip-config.h
@@ -146,6 +146,7 @@ gboolean     nm_ip_route_attribute_validate  (const char *name,
 #define NM_IP_ROUTE_ATTRIBUTE_SRC            "src"
 #define NM_IP_ROUTE_ATTRIBUTE_FROM           "from"
 #define NM_IP_ROUTE_ATTRIBUTE_TOS            "tos"
+#define NM_IP_ROUTE_ATTRIBUTE_ONLINK         "onlink"
 #define NM_IP_ROUTE_ATTRIBUTE_WINDOW         "window"
 #define NM_IP_ROUTE_ATTRIBUTE_CWND           "cwnd"
 #define NM_IP_ROUTE_ATTRIBUTE_INITCWND       "initcwnd"
diff --git a/libnm-core/nm-setting-ip4-config.c b/libnm-core/nm-setting-ip4-config.c
index db3a12be..26e7f505 100644
--- a/libnm-core/nm-setting-ip4-config.c
+++ b/libnm-core/nm-setting-ip4-config.c
@@ -689,6 +689,14 @@ nm_setting_ip4_config_class_init (NMSettingIP4ConfigClass *ip4_class)
 	 */
 
 	/* ---ifcfg-rh---
+	 * property: dns-options
+	 * variable: RES_OPTIONS(+)
+	 * description: List of DNS options to be added to /etc/resolv.conf
+	 * example: RES_OPTIONS=ndots:2 timeout:3
+	 * ---end---
+	 */
+
+	/* ---ifcfg-rh---
 	 * property: dns-priority
 	 * variable: IPV4_DNS_PRIORITY(+)
 	 * description: The priority for DNS servers of this connection. Lower values have higher priority.
diff --git a/libnm-core/nm-setting-ip6-config.c b/libnm-core/nm-setting-ip6-config.c
index cc6381f0..00e9391d 100644
--- a/libnm-core/nm-setting-ip6-config.c
+++ b/libnm-core/nm-setting-ip6-config.c
@@ -550,7 +550,7 @@ nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *ip6_class)
 
 	/* ---ifcfg-rh---
 	 * property: dns-search
-	 * variable: DOMAIN
+	 * variable: IPV6_DOMAIN
 	 * format:   string (space-separated domains)
 	 * description: List of DNS search domains.
 	 * ---end---
@@ -671,6 +671,14 @@ nm_setting_ip6_config_class_init (NMSettingIP6ConfigClass *ip6_class)
 	 * ---end---
 	 */
 
+	/* ---ifcfg-rh---
+	 * property: dns-options
+	 * variable: IPV6_RES_OPTIONS(+)
+	 * description: List of DNS options to be added to /etc/resolv.conf
+	 * example: IPV6_RES_OPTIONS=ndots:2 timeout:3
+	 * ---end---
+	 */
+
 	/**
 	 * NMSettingIP6Config:ip6-privacy:
 	 *
diff --git a/libnm-core/nm-setting-tc-config.c b/libnm-core/nm-setting-tc-config.c
new file mode 100644
index 00000000..a4960038
--- /dev/null
+++ b/libnm-core/nm-setting-tc-config.c
@@ -0,0 +1,1576 @@
+/*
+ * 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 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include <linux/pkt_sched.h>
+
+#include "nm-setting-tc-config.h"
+#include "nm-setting-private.h"
+
+/**
+ * SECTION:nm-setting-tc-config
+ * @short_description: Describes connection properties for the Linux Traffic Control
+ * @include: nm-setting-tc-config.h
+ **/
+
+/*****************************************************************************/
+
+G_DEFINE_BOXED_TYPE (NMTCQdisc, nm_tc_qdisc, nm_tc_qdisc_dup, nm_tc_qdisc_unref)
+
+struct NMTCQdisc {
+	guint refcount;
+
+	char *kind;
+	guint32 handle;
+	guint32 parent;
+};
+
+/**
+ * nm_tc_qdisc_new:
+ * @kind: name of the queueing discipline
+ * @parent: the parent queueing discipline
+ * @error: location to store error, or %NULL
+ *
+ * Creates a new #NMTCQdisc object.
+ *
+ * Returns: (transfer full): the new #NMTCQdisc object, or %NULL on error
+ *
+ * Since: 1.10.2
+ **/
+NMTCQdisc *
+nm_tc_qdisc_new (const char *kind,
+                 guint32 parent,
+                 GError **error)
+{
+	NMTCQdisc *qdisc;
+
+	if (!kind || !*kind || strchr (kind, ' ') || strchr (kind, '\t')) {
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             _("'%s' is not a valid kind"), kind);
+		return NULL;
+	}
+
+	if (!parent) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("parent handle missing"));
+		return NULL;
+	}
+
+	qdisc = g_slice_new0 (NMTCQdisc);
+	qdisc->refcount = 1;
+
+	qdisc->kind = g_strdup (kind);
+	qdisc->parent = parent;
+
+	return qdisc;
+}
+
+/**
+ * nm_tc_qdisc_ref:
+ * @qdisc: the #NMTCQdisc
+ *
+ * Increases the reference count of the object.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_tc_qdisc_ref (NMTCQdisc *qdisc)
+{
+	g_return_if_fail (qdisc != NULL);
+	g_return_if_fail (qdisc->refcount > 0);
+
+	qdisc->refcount++;
+}
+
+/**
+ * nm_tc_qdisc_unref:
+ * @qdisc: the #NMTCQdisc
+ *
+ * Decreases the reference count of the object.  If the reference count
+ * reaches zero, the object will be destroyed.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_tc_qdisc_unref (NMTCQdisc *qdisc)
+{
+	g_return_if_fail (qdisc != NULL);
+	g_return_if_fail (qdisc->refcount > 0);
+
+	qdisc->refcount--;
+	if (qdisc->refcount == 0) {
+		g_free (qdisc->kind);
+		g_slice_free (NMTCQdisc, qdisc);
+	}
+}
+
+/**
+ * nm_tc_qdisc_equal:
+ * @qdisc: the #NMTCQdisc
+ * @other: the #NMTCQdisc to compare @qdisc to.
+ *
+ * Determines if two #NMTCQdisc objects contain the same kind, * handle
+ * and parent.
+ *
+ * Returns: %TRUE if the objects contain the same values, %FALSE if they do not.
+ *
+ * Since: 1.10.2
+ **/
+gboolean
+nm_tc_qdisc_equal (NMTCQdisc *qdisc, NMTCQdisc *other)
+{
+	g_return_val_if_fail (qdisc != NULL, FALSE);
+	g_return_val_if_fail (qdisc->refcount > 0, FALSE);
+
+	g_return_val_if_fail (other != NULL, FALSE);
+	g_return_val_if_fail (other->refcount > 0, FALSE);
+
+	if (   qdisc->handle != other->handle
+	    || qdisc->parent != other->parent
+	    || g_strcmp0 (qdisc->kind, other->kind) != 0)
+		return FALSE;
+
+	return TRUE;
+}
+
+/**
+ * nm_tc_qdisc_dup:
+ * @qdisc: the #NMTCQdisc
+ *
+ * Creates a copy of @qdisc
+ *
+ * Returns: (transfer full): a copy of @qdisc
+ *
+ * Since: 1.10.2
+ **/
+NMTCQdisc *
+nm_tc_qdisc_dup (NMTCQdisc *qdisc)
+{
+	NMTCQdisc *copy;
+
+	g_return_val_if_fail (qdisc != NULL, NULL);
+	g_return_val_if_fail (qdisc->refcount > 0, NULL);
+
+	copy = nm_tc_qdisc_new (qdisc->kind, qdisc->parent, NULL);
+	nm_tc_qdisc_set_handle (copy, qdisc->handle);
+
+	return copy;
+}
+
+/**
+ * nm_tc_qdisc_get_kind:
+ * @qdisc: the #NMTCQdisc
+ *
+ * Returns:
+ *
+ * Since: 1.10.2
+ **/
+const char *
+nm_tc_qdisc_get_kind (NMTCQdisc *qdisc)
+{
+	g_return_val_if_fail (qdisc != NULL, NULL);
+	g_return_val_if_fail (qdisc->refcount > 0, NULL);
+
+	return qdisc->kind;
+}
+
+/**
+ * nm_tc_qdisc_get_handle:
+ * @qdisc: the #NMTCQdisc
+ *
+ * Returns: the queueing discipline handle
+ *
+ * Since: 1.10.2
+ **/
+guint32
+nm_tc_qdisc_get_handle (NMTCQdisc *qdisc)
+{
+	g_return_val_if_fail (qdisc != NULL, TC_H_UNSPEC);
+	g_return_val_if_fail (qdisc->refcount > 0, TC_H_UNSPEC);
+
+	return qdisc->handle;
+}
+
+/**
+ * nm_tc_qdisc_set_handle:
+ * @qdisc: the #NMTCQdisc
+ * @handle: the queueing discipline handle
+ *
+ * Sets the queueing discipline handle.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_tc_qdisc_set_handle (NMTCQdisc *qdisc, guint32 handle)
+{
+	g_return_if_fail (qdisc != NULL);
+	g_return_if_fail (qdisc->refcount > 0);
+
+	qdisc->handle = handle;
+}
+
+/**
+ * nm_tc_qdisc_get_parent:
+ * @qdisc: the #NMTCQdisc
+ *
+ * Returns: the parent class
+ *
+ * Since: 1.10.2
+ **/
+guint32
+nm_tc_qdisc_get_parent (NMTCQdisc *qdisc)
+{
+	g_return_val_if_fail (qdisc != NULL, TC_H_UNSPEC);
+	g_return_val_if_fail (qdisc->refcount > 0, TC_H_UNSPEC);
+
+	return qdisc->parent;
+}
+
+/*****************************************************************************/
+
+G_DEFINE_BOXED_TYPE (NMTCAction, nm_tc_action, nm_tc_action_dup, nm_tc_action_unref)
+
+struct NMTCAction {
+	guint refcount;
+
+	char *kind;
+
+	GHashTable *attributes;
+};
+
+/**
+ * nm_tc_action_new:
+ * @kind: name of the queueing discipline
+ * @error: location to store error, or %NULL
+ *
+ * Creates a new #NMTCAction object.
+ *
+ * Returns: (transfer full): the new #NMTCAction object, or %NULL on error
+ *
+ * Since: 1.10.2
+ **/
+NMTCAction *
+nm_tc_action_new (const char *kind,
+                  GError **error)
+{
+	NMTCAction *action;
+
+	if (!kind || !*kind || strchr (kind, ' ') || strchr (kind, '\t')) {
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             _("'%s' is not a valid kind"), kind);
+		return NULL;
+	}
+
+	action = g_slice_new0 (NMTCAction);
+	action->refcount = 1;
+
+	action->kind = g_strdup (kind);
+
+	return action;
+}
+
+/**
+ * nm_tc_action_ref:
+ * @action: the #NMTCAction
+ *
+ * Increases the reference count of the object.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_tc_action_ref (NMTCAction *action)
+{
+	g_return_if_fail (action != NULL);
+	g_return_if_fail (action->refcount > 0);
+
+	action->refcount++;
+}
+
+/**
+ * nm_tc_action_unref:
+ * @action: the #NMTCAction
+ *
+ * Decreases the reference count of the object.  If the reference count
+ * reaches zero, the object will be destroyed.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_tc_action_unref (NMTCAction *action)
+{
+	g_return_if_fail (action != NULL);
+	g_return_if_fail (action->refcount > 0);
+
+	action->refcount--;
+	if (action->refcount == 0) {
+		g_free (action->kind);
+		if (action->attributes)
+			g_hash_table_unref (action->attributes);
+		g_slice_free (NMTCAction, action);
+	}
+}
+
+/**
+ * nm_tc_action_equal:
+ * @action: the #NMTCAction
+ * @other: the #NMTCAction to compare @action to.
+ *
+ * Determines if two #NMTCAction objects contain the same kind, family,
+ * handle, parent and info.
+ *
+ * Returns: %TRUE if the objects contain the same values, %FALSE if they do not.
+ *
+ * Since: 1.10.2
+ **/
+gboolean
+nm_tc_action_equal (NMTCAction *action, NMTCAction *other)
+{
+	GHashTableIter iter;
+	const char *key;
+	GVariant *value, *value2;
+	guint n;
+
+	g_return_val_if_fail (!action || action->refcount > 0, FALSE);
+	g_return_val_if_fail (!other || other->refcount > 0, FALSE);
+
+	if (action == other)
+		return TRUE;
+	if (!action || !other)
+		return FALSE;
+
+	if (g_strcmp0 (action->kind, other->kind) != 0)
+		return FALSE;
+
+	n = action->attributes ? g_hash_table_size (action->attributes) : 0;
+	if (n != (other->attributes ? g_hash_table_size (other->attributes) : 0))
+		return FALSE;
+	if (n) {
+		g_hash_table_iter_init (&iter, action->attributes);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value)) {
+			value2 = g_hash_table_lookup (other->attributes, key);
+			if (!value2)
+				return FALSE;
+			if (!g_variant_equal (value, value2))
+				return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+/**
+ * nm_tc_action_dup:
+ * @action: the #NMTCAction
+ *
+ * Creates a copy of @action
+ *
+ * Returns: (transfer full): a copy of @action
+ *
+ * Since: 1.10.2
+ **/
+NMTCAction *
+nm_tc_action_dup (NMTCAction *action)
+{
+	NMTCAction *copy;
+
+	g_return_val_if_fail (action != NULL, NULL);
+	g_return_val_if_fail (action->refcount > 0, NULL);
+
+	copy = nm_tc_action_new (action->kind, NULL);
+
+	if (action->attributes) {
+		GHashTableIter iter;
+		const char *key;
+		GVariant *value;
+
+		g_hash_table_iter_init (&iter, action->attributes);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value))
+			nm_tc_action_set_attribute (copy, key, value);
+	}
+
+	return copy;
+}
+
+/**
+ * nm_tc_action_get_kind:
+ * @action: the #NMTCAction
+ *
+ * Returns:
+ *
+ * Since: 1.10.2
+ **/
+const char *
+nm_tc_action_get_kind (NMTCAction *action)
+{
+	g_return_val_if_fail (action != NULL, NULL);
+	g_return_val_if_fail (action->refcount > 0, NULL);
+
+	return action->kind;
+}
+
+/**
+ * nm_tc_action_get_attribute_names:
+ * @action: the #NMTCAction
+ *
+ * Gets an array of attribute names defined on @action.
+ *
+ * Returns: (transfer full): a %NULL-terminated array of attribute names,
+ **/
+char **
+nm_tc_action_get_attribute_names (NMTCAction *action)
+{
+	GHashTableIter iter;
+	const char *key;
+	GPtrArray *names;
+
+	g_return_val_if_fail (action != NULL, NULL);
+
+	names = g_ptr_array_new ();
+
+	if (action->attributes) {
+		g_hash_table_iter_init (&iter, action->attributes);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL))
+			g_ptr_array_add (names, g_strdup (key));
+	}
+	g_ptr_array_add (names, NULL);
+
+	return (char **) g_ptr_array_free (names, FALSE);
+}
+
+/**
+ * nm_tc_action_get_attribute:
+ * @action: the #NMTCAction
+ * @name: the name of an action attribute
+ *
+ * Gets the value of the attribute with name @name on @action
+ *
+ * Returns: (transfer none): the value of the attribute with name @name on
+ *   @action, or %NULL if @action has no such attribute.
+ **/
+GVariant *
+nm_tc_action_get_attribute (NMTCAction *action, const char *name)
+{
+	g_return_val_if_fail (action != NULL, NULL);
+	g_return_val_if_fail (name != NULL && *name != '\0', NULL);
+
+	if (action->attributes)
+		return g_hash_table_lookup (action->attributes, name);
+	else
+		return NULL;
+}
+
+/**
+ * nm_tc_action_set_attribute:
+ * @action: the #NMTCAction
+ * @name: the name of an action attribute
+ * @value: (transfer none) (allow-none): the value
+ *
+ * Sets or clears the named attribute on @action to the given value.
+ **/
+void
+nm_tc_action_set_attribute (NMTCAction *action, const char *name, GVariant *value)
+{
+	g_return_if_fail (action != NULL);
+	g_return_if_fail (name != NULL && *name != '\0');
+	g_return_if_fail (strcmp (name, "kind") != 0);
+
+	if (!action->attributes) {
+		action->attributes = g_hash_table_new_full (g_str_hash, g_str_equal,
+		                                            g_free, (GDestroyNotify) g_variant_unref);
+	}
+
+	if (value)
+		g_hash_table_insert (action->attributes, g_strdup (name), g_variant_ref_sink (value));
+	else
+		g_hash_table_remove (action->attributes, name);
+}
+
+/*****************************************************************************/
+
+G_DEFINE_BOXED_TYPE (NMTCTfilter, nm_tc_tfilter, nm_tc_tfilter_dup, nm_tc_tfilter_unref)
+
+struct NMTCTfilter {
+	guint refcount;
+
+	char *kind;
+	guint32 handle;
+	guint32 parent;
+	NMTCAction *action;
+};
+
+/**
+ * nm_tc_tfilter_new:
+ * @kind: name of the queueing discipline
+ * @parent: the parent queueing discipline
+ * @error: location to store error, or %NULL
+ *
+ * Creates a new #NMTCTfilter object.
+ *
+ * Returns: (transfer full): the new #NMTCTfilter object, or %NULL on error
+ *
+ * Since: 1.10.2
+ **/
+NMTCTfilter *
+nm_tc_tfilter_new (const char *kind,
+                   guint32 parent,
+                   GError **error)
+{
+	NMTCTfilter *tfilter;
+
+	if (!kind || !*kind || strchr (kind, ' ') || strchr (kind, '\t')) {
+		g_set_error (error,
+		             NM_CONNECTION_ERROR,
+		             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		             _("'%s' is not a valid kind"), kind);
+		return NULL;
+	}
+
+	if (!parent) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("parent handle missing"));
+		return NULL;
+	}
+
+	tfilter = g_slice_new0 (NMTCTfilter);
+	tfilter->refcount = 1;
+
+	tfilter->kind = g_strdup (kind);
+	tfilter->parent = parent;
+
+	return tfilter;
+}
+
+/**
+ * nm_tc_tfilter_ref:
+ * @tfilter: the #NMTCTfilter
+ *
+ * Increases the reference count of the object.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_tc_tfilter_ref (NMTCTfilter *tfilter)
+{
+	g_return_if_fail (tfilter != NULL);
+	g_return_if_fail (tfilter->refcount > 0);
+
+	tfilter->refcount++;
+}
+
+/**
+ * nm_tc_tfilter_unref:
+ * @tfilter: the #NMTCTfilter
+ *
+ * Decreases the reference count of the object.  If the reference count
+ * reaches zero, the object will be destroyed.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_tc_tfilter_unref (NMTCTfilter *tfilter)
+{
+	g_return_if_fail (tfilter != NULL);
+	g_return_if_fail (tfilter->refcount > 0);
+
+	tfilter->refcount--;
+	if (tfilter->refcount == 0) {
+		g_free (tfilter->kind);
+		if (tfilter->action)
+			nm_tc_action_unref (tfilter->action);
+		g_slice_free (NMTCTfilter, tfilter);
+	}
+}
+
+/**
+ * nm_tc_tfilter_equal:
+ * @tfilter: the #NMTCTfilter
+ * @other: the #NMTCTfilter to compare @tfilter to.
+ *
+ * Determines if two #NMTCTfilter objects contain the same kind, family,
+ * handle, parent and info.
+ *
+ * Returns: %TRUE if the objects contain the same values, %FALSE if they do not.
+ *
+ * Since: 1.10.2
+ **/
+gboolean
+nm_tc_tfilter_equal (NMTCTfilter *tfilter, NMTCTfilter *other)
+{
+	g_return_val_if_fail (tfilter != NULL, FALSE);
+	g_return_val_if_fail (tfilter->refcount > 0, FALSE);
+
+	g_return_val_if_fail (other != NULL, FALSE);
+	g_return_val_if_fail (other->refcount > 0, FALSE);
+
+	if (   tfilter->handle != other->handle
+	    || tfilter->parent != other->parent
+	    || g_strcmp0 (tfilter->kind, other->kind) != 0
+	    || !nm_tc_action_equal (tfilter->action, other->action))
+		return FALSE;
+
+	return TRUE;
+}
+
+/**
+ * nm_tc_tfilter_dup:
+ * @tfilter: the #NMTCTfilter
+ *
+ * Creates a copy of @tfilter
+ *
+ * Returns: (transfer full): a copy of @tfilter
+ *
+ * Since: 1.10.2
+ **/
+NMTCTfilter *
+nm_tc_tfilter_dup (NMTCTfilter *tfilter)
+{
+	NMTCTfilter *copy;
+
+	g_return_val_if_fail (tfilter != NULL, NULL);
+	g_return_val_if_fail (tfilter->refcount > 0, NULL);
+
+	copy = nm_tc_tfilter_new (tfilter->kind, tfilter->parent, NULL);
+	nm_tc_tfilter_set_handle (copy, tfilter->handle);
+	nm_tc_tfilter_set_action (copy, tfilter->action);
+
+	return copy;
+}
+
+/**
+ * nm_tc_tfilter_get_kind:
+ * @tfilter: the #NMTCTfilter
+ *
+ * Returns:
+ *
+ * Since: 1.10.2
+ **/
+const char *
+nm_tc_tfilter_get_kind (NMTCTfilter *tfilter)
+{
+	g_return_val_if_fail (tfilter != NULL, NULL);
+	g_return_val_if_fail (tfilter->refcount > 0, NULL);
+
+	return tfilter->kind;
+}
+
+/**
+ * nm_tc_tfilter_get_handle:
+ * @tfilter: the #NMTCTfilter
+ *
+ * Returns: the queueing discipline handle
+ *
+ * Since: 1.10.2
+ **/
+guint32
+nm_tc_tfilter_get_handle (NMTCTfilter *tfilter)
+{
+	g_return_val_if_fail (tfilter != NULL, TC_H_UNSPEC);
+	g_return_val_if_fail (tfilter->refcount > 0, TC_H_UNSPEC);
+
+	return tfilter->handle;
+}
+
+/**
+ * nm_tc_tfilter_set_handle:
+ * @tfilter: the #NMTCTfilter
+ * @handle: the queueing discipline handle
+ *
+ * Sets the queueing discipline handle.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_tc_tfilter_set_handle (NMTCTfilter *tfilter, guint32 handle)
+{
+	g_return_if_fail (tfilter != NULL);
+	g_return_if_fail (tfilter->refcount > 0);
+
+	tfilter->handle = handle;
+}
+
+/**
+ * nm_tc_tfilter_get_parent:
+ * @tfilter: the #NMTCTfilter
+ *
+ * Returns: the parent class
+ *
+ * Since: 1.10.2
+ **/
+guint32
+nm_tc_tfilter_get_parent (NMTCTfilter *tfilter)
+{
+	g_return_val_if_fail (tfilter != NULL, TC_H_UNSPEC);
+	g_return_val_if_fail (tfilter->refcount > 0, TC_H_UNSPEC);
+
+	return tfilter->parent;
+}
+
+/**
+ * nm_tc_tfilter_get_action:
+ * @tfilter: the #NMTCTfilter
+ *
+ * Returns: the action associated with a traffic filter.
+ *
+ * Since: 1.10.2
+ **/
+NMTCAction *
+nm_tc_tfilter_get_action (NMTCTfilter *tfilter)
+{
+	g_return_val_if_fail (tfilter != NULL, TC_H_UNSPEC);
+	g_return_val_if_fail (tfilter->refcount > 0, TC_H_UNSPEC);
+
+	if (tfilter->action == NULL)
+		return NULL;
+
+	return tfilter->action;
+}
+
+/**
+ * nm_tc_tfilter_set_action:
+ * @tfilter: the #NMTCTfilter
+ * @action: the action object
+ *
+ * Sets the action associated with a traffic filter.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_tc_tfilter_set_action (NMTCTfilter *tfilter, NMTCAction *action)
+{
+	g_return_if_fail (tfilter != NULL);
+	g_return_if_fail (tfilter->refcount > 0);
+
+	if (action)
+		nm_tc_action_ref (action);
+	if (tfilter->action)
+		nm_tc_action_unref (tfilter->action);
+	tfilter->action = action;
+}
+
+/*****************************************************************************/
+
+enum {
+	PROP_0,
+	PROP_QDISCS,
+	PROP_TFILTERS,
+
+	LAST_PROP
+};
+
+/**
+ * NMSettingTCConfig:
+ *
+ * Linux Traffic Contril Settings.
+ *
+ * Since: 1.10.2
+ */
+struct _NMSettingTCConfig {
+        NMSetting parent;
+	GPtrArray *qdiscs;
+	GPtrArray *tfilters;
+};
+
+struct _NMSettingTCConfigClass {
+        NMSettingClass parent;
+};
+
+G_DEFINE_TYPE_WITH_CODE (NMSettingTCConfig, nm_setting_tc_config, NM_TYPE_SETTING,
+                         _nm_register_setting (TC_CONFIG, NM_SETTING_PRIORITY_IP))
+NM_SETTING_REGISTER_TYPE (NM_TYPE_SETTING_TC_CONFIG)
+
+/**
+ * nm_setting_tc_config_new:
+ *
+ * Creates a new #NMSettingTCConfig object with default values.
+ *
+ * Returns: (transfer full): the new empty #NMSettingTCConfig object
+ *
+ * Since: 1.10.2
+ **/
+NMSetting *
+nm_setting_tc_config_new (void)
+{
+	return (NMSetting *) g_object_new (NM_TYPE_SETTING_TC_CONFIG, NULL);
+}
+
+/**
+ * nm_setting_tc_config_get_num_qdiscs:
+ * @setting: the #NMSettingTCConfig
+ *
+ * Returns: the number of configured queueing disciplines
+ *
+ * Since: 1.10.2
+ **/
+guint
+nm_setting_tc_config_get_num_qdiscs (NMSettingTCConfig *self)
+{
+	g_return_val_if_fail (NM_IS_SETTING_TC_CONFIG (self), 0);
+
+	return self->qdiscs->len;
+}
+
+/**
+ * nm_setting_tc_config_get_qdisc:
+ * @setting: the #NMSettingTCConfig
+ * @idx: index number of the qdisc to return
+ *
+ * Returns: (transfer none): the qdisc at index @idx
+ *
+ * Since: 1.10.2
+ **/
+NMTCQdisc *
+nm_setting_tc_config_get_qdisc (NMSettingTCConfig *self, guint idx)
+{
+	g_return_val_if_fail (NM_IS_SETTING_TC_CONFIG (self), NULL);
+	g_return_val_if_fail (idx < self->qdiscs->len, NULL);
+
+	return self->qdiscs->pdata[idx];
+}
+
+/**
+ * nm_setting_tc_config_add_qdisc:
+ * @setting: the #NMSettingTCConfig
+ * @qdisc: the qdisc to add
+ *
+ * Appends a new qdisc and associated information to the setting.  The
+ * given qdisc is duplicated internally and is not changed by this function.
+ * If an identical qdisc (considering attributes as well) already exists, the
+ * qdisc is not added and the function returns %FALSE.
+ *
+ * Returns: %TRUE if the qdisc was added; %FALSE if the qdisc was already known.
+ *
+ * Since: 1.10.2
+ **/
+gboolean
+nm_setting_tc_config_add_qdisc (NMSettingTCConfig *self,
+                                NMTCQdisc *qdisc)
+{
+	guint i;
+
+	g_return_val_if_fail (NM_IS_SETTING_TC_CONFIG (self), FALSE);
+	g_return_val_if_fail (qdisc != NULL, FALSE);
+
+	for (i = 0; i < self->qdiscs->len; i++) {
+		if (nm_tc_qdisc_equal (self->qdiscs->pdata[i], qdisc))
+			return FALSE;
+	}
+
+	g_ptr_array_add (self->qdiscs, nm_tc_qdisc_dup (qdisc));
+	g_object_notify (G_OBJECT (self), NM_SETTING_TC_CONFIG_QDISCS);
+	return TRUE;
+}
+
+/**
+ * nm_setting_tc_config_remove_qdisc:
+ * @setting: the #NMSettingTCConfig
+ * @idx: index number of the qdisc
+ *
+ * Removes the qdisc at index @idx.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_setting_tc_config_remove_qdisc (NMSettingTCConfig *self, guint idx)
+{
+	g_return_if_fail (NM_IS_SETTING_TC_CONFIG (self));
+
+	g_return_if_fail (idx < self->qdiscs->len);
+
+	g_ptr_array_remove_index (self->qdiscs, idx);
+	g_object_notify (G_OBJECT (self), NM_SETTING_TC_CONFIG_QDISCS);
+}
+
+/**
+ * nm_setting_tc_config_remove_qdisc_by_value:
+ * @setting: the #NMSettingTCConfig
+ * @qdisc: the qdisc to remove
+ *
+ * Removes the first matching qdisc that matches @qdisc.
+ *
+ * Returns: %TRUE if the qdisc was found and removed; %FALSE if it was not.
+ *
+ * Since: 1.10.2
+ **/
+gboolean
+nm_setting_tc_config_remove_qdisc_by_value (NMSettingTCConfig *self,
+                                            NMTCQdisc *qdisc)
+{
+	guint i;
+
+	g_return_val_if_fail (NM_IS_SETTING_TC_CONFIG (self), FALSE);
+	g_return_val_if_fail (qdisc != NULL, FALSE);
+
+	for (i = 0; i < self->qdiscs->len; i++) {
+		if (nm_tc_qdisc_equal (self->qdiscs->pdata[i], qdisc)) {
+			g_ptr_array_remove_index (self->qdiscs, i);
+			g_object_notify (G_OBJECT (self), NM_SETTING_TC_CONFIG_QDISCS);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+/**
+ * nm_setting_tc_config_clear_qdiscs:
+ * @setting: the #NMSettingTCConfig
+ *
+ * Removes all configured queueing disciplines.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_setting_tc_config_clear_qdiscs (NMSettingTCConfig *self)
+{
+	g_return_if_fail (NM_IS_SETTING_TC_CONFIG (self));
+
+	g_ptr_array_set_size (self->qdiscs, 0);
+	g_object_notify (G_OBJECT (self), NM_SETTING_TC_CONFIG_QDISCS);
+}
+
+/*****************************************************************************/
+/**
+ * nm_setting_tc_config_get_num_tfilters:
+ * @setting: the #NMSettingTCConfig
+ *
+ * Returns: the number of configured queueing disciplines
+ *
+ * Since: 1.10.2
+ **/
+guint
+nm_setting_tc_config_get_num_tfilters (NMSettingTCConfig *self)
+{
+	g_return_val_if_fail (NM_IS_SETTING_TC_CONFIG (self), 0);
+
+	return self->tfilters->len;
+}
+
+/**
+ * nm_setting_tc_config_get_tfilter:
+ * @setting: the #NMSettingTCConfig
+ * @idx: index number of the tfilter to return
+ *
+ * Returns: (transfer none): the tfilter at index @idx
+ *
+ * Since: 1.10.2
+ **/
+NMTCTfilter *
+nm_setting_tc_config_get_tfilter (NMSettingTCConfig *self, guint idx)
+{
+	g_return_val_if_fail (NM_IS_SETTING_TC_CONFIG (self), NULL);
+	g_return_val_if_fail (idx < self->tfilters->len, NULL);
+
+	return self->tfilters->pdata[idx];
+}
+
+/**
+ * nm_setting_tc_config_add_tfilter:
+ * @setting: the #NMSettingTCConfig
+ * @tfilter: the tfilter to add
+ *
+ * Appends a new tfilter and associated information to the setting.  The
+ * given tfilter is duplicated internally and is not changed by this function.
+ * If an identical tfilter (considering attributes as well) already exists, the
+ * tfilter is not added and the function returns %FALSE.
+ *
+ * Returns: %TRUE if the tfilter was added; %FALSE if the tfilter was already known.
+ *
+ * Since: 1.10.2
+ **/
+gboolean
+nm_setting_tc_config_add_tfilter (NMSettingTCConfig *self,
+                                  NMTCTfilter *tfilter)
+{
+	guint i;
+
+	g_return_val_if_fail (NM_IS_SETTING_TC_CONFIG (self), FALSE);
+	g_return_val_if_fail (tfilter != NULL, FALSE);
+
+	for (i = 0; i < self->tfilters->len; i++) {
+		if (nm_tc_tfilter_equal (self->tfilters->pdata[i], tfilter))
+			return FALSE;
+	}
+
+	g_ptr_array_add (self->tfilters, nm_tc_tfilter_dup (tfilter));
+	g_object_notify (G_OBJECT (self), NM_SETTING_TC_CONFIG_TFILTERS);
+	return TRUE;
+}
+
+/**
+ * nm_setting_tc_config_remove_tfilter:
+ * @setting: the #NMSettingTCConfig
+ * @idx: index number of the tfilter
+ *
+ * Removes the tfilter at index @idx.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_setting_tc_config_remove_tfilter (NMSettingTCConfig *self, guint idx)
+{
+	g_return_if_fail (NM_IS_SETTING_TC_CONFIG (self));
+	g_return_if_fail (idx < self->tfilters->len);
+
+	g_ptr_array_remove_index (self->tfilters, idx);
+	g_object_notify (G_OBJECT (self), NM_SETTING_TC_CONFIG_TFILTERS);
+}
+
+/**
+ * nm_setting_tc_config_remove_tfilter_by_value:
+ * @setting: the #NMSettingTCConfig
+ * @tfilter: the tfilter to remove
+ *
+ * Removes the first matching tfilter that matches @tfilter.
+ *
+ * Returns: %TRUE if the tfilter was found and removed; %FALSE if it was not.
+ *
+ * Since: 1.10.2
+ **/
+gboolean
+nm_setting_tc_config_remove_tfilter_by_value (NMSettingTCConfig *self,
+                                              NMTCTfilter *tfilter)
+{
+	guint i;
+
+	g_return_val_if_fail (NM_IS_SETTING_TC_CONFIG (self), FALSE);
+	g_return_val_if_fail (tfilter != NULL, FALSE);
+
+	for (i = 0; i < self->tfilters->len; i++) {
+		if (nm_tc_tfilter_equal (self->tfilters->pdata[i], tfilter)) {
+			g_ptr_array_remove_index (self->tfilters, i);
+			g_object_notify (G_OBJECT (self), NM_SETTING_TC_CONFIG_TFILTERS);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+/**
+ * nm_setting_tc_config_clear_tfilters:
+ * @setting: the #NMSettingTCConfig
+ *
+ * Removes all configured queueing disciplines.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_setting_tc_config_clear_tfilters (NMSettingTCConfig *self)
+{
+	g_return_if_fail (NM_IS_SETTING_TC_CONFIG (self));
+
+	g_ptr_array_set_size (self->tfilters, 0);
+	g_object_notify (G_OBJECT (self), NM_SETTING_TC_CONFIG_TFILTERS);
+}
+
+/*****************************************************************************/
+
+static void
+set_property (GObject *object, guint prop_id,
+              const GValue *value, GParamSpec *pspec)
+{
+	NMSettingTCConfig *self = NM_SETTING_TC_CONFIG (object);
+
+	switch (prop_id) {
+	case PROP_QDISCS:
+		g_ptr_array_unref (self->qdiscs);
+		self->qdiscs = _nm_utils_copy_array (g_value_get_boxed (value),
+		                                     (NMUtilsCopyFunc) nm_tc_qdisc_dup,
+		                                     (GDestroyNotify) nm_tc_qdisc_unref);
+		break;
+	case PROP_TFILTERS:
+		g_ptr_array_unref (self->tfilters);
+		self->tfilters = _nm_utils_copy_array (g_value_get_boxed (value),
+		                                       (NMUtilsCopyFunc) nm_tc_tfilter_dup,
+		                                       (GDestroyNotify) nm_tc_tfilter_unref);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMSettingTCConfig *self = NM_SETTING_TC_CONFIG (object);
+
+	switch (prop_id) {
+	case PROP_QDISCS:
+		g_value_take_boxed (value, _nm_utils_copy_array (self->qdiscs,
+		                                                 (NMUtilsCopyFunc) nm_tc_qdisc_dup,
+		                                                 (GDestroyNotify) nm_tc_qdisc_unref));
+		break;
+	case PROP_TFILTERS:
+		g_value_take_boxed (value, _nm_utils_copy_array (self->tfilters,
+		                                                 (NMUtilsCopyFunc) nm_tc_tfilter_dup,
+		                                                 (GDestroyNotify) nm_tc_tfilter_unref));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+finalize (GObject *object)
+{
+	NMSettingTCConfig *self = NM_SETTING_TC_CONFIG (object);
+
+	g_ptr_array_unref (self->qdiscs);
+	g_ptr_array_unref (self->tfilters);
+
+	G_OBJECT_CLASS (nm_setting_tc_config_parent_class)->finalize (object);
+}
+
+static gboolean
+compare_property (NMSetting *setting,
+                  NMSetting *other,
+                  const GParamSpec *prop_spec,
+                  NMSettingCompareFlags flags)
+{
+	NMSettingTCConfig *a_tc_config = NM_SETTING_TC_CONFIG (setting);
+	NMSettingTCConfig *b_tc_config = NM_SETTING_TC_CONFIG (other);
+	NMSettingClass *parent_class;
+	guint i;
+
+	if (nm_streq (prop_spec->name, NM_SETTING_TC_CONFIG_QDISCS)) {
+		if (a_tc_config->qdiscs->len != b_tc_config->qdiscs->len)
+			return FALSE;
+		for (i = 0; i < a_tc_config->qdiscs->len; i++) {
+			if (!nm_tc_qdisc_equal (a_tc_config->qdiscs->pdata[i], b_tc_config->qdiscs->pdata[i]))
+				return FALSE;
+		}
+		return TRUE;
+	}
+
+	if (nm_streq (prop_spec->name, NM_SETTING_TC_CONFIG_TFILTERS)) {
+		if (a_tc_config->tfilters->len != b_tc_config->tfilters->len)
+			return FALSE;
+		for (i = 0; i < a_tc_config->tfilters->len; i++) {
+			if (!nm_tc_tfilter_equal (a_tc_config->tfilters->pdata[i], b_tc_config->tfilters->pdata[i]))
+				return FALSE;
+		}
+		return TRUE;
+	}
+
+	/* Otherwise chain up to parent to handle generic compare */
+	parent_class = NM_SETTING_CLASS (nm_setting_tc_config_parent_class);
+	return parent_class->compare_property (setting, other, prop_spec, flags);
+}
+
+static void
+nm_setting_tc_config_init (NMSettingTCConfig *self)
+{
+	self->qdiscs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_qdisc_unref);
+	self->tfilters = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_tfilter_unref);
+}
+
+/**
+ * _qdiscs_to_variant:
+ * @qdiscs: (element-type NMTCQdisc): an array of #NMTCQdisc objects
+ *
+ * Utility function to convert a #GPtrArray of #NMTCQdisc objects representing
+ * TC qdiscs into a #GVariant of type 'aa{sv}' representing an array
+ * of NetworkManager TC qdiscs.
+ *
+ * Returns: (transfer none): a new floating #GVariant representing @qdiscs.
+ **/
+static GVariant *
+_qdiscs_to_variant (GPtrArray *qdiscs)
+{
+	GVariantBuilder builder;
+	int i;
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+
+	if (qdiscs) {
+		for (i = 0; i < qdiscs->len; i++) {
+			NMTCQdisc *qdisc = qdiscs->pdata[i];
+			GVariantBuilder qdisc_builder;
+
+			g_variant_builder_init (&qdisc_builder, G_VARIANT_TYPE_VARDICT);
+
+			g_variant_builder_add (&qdisc_builder, "{sv}", "kind",
+			                       g_variant_new_string (nm_tc_qdisc_get_kind (qdisc)));
+
+			g_variant_builder_add (&qdisc_builder, "{sv}", "handle",
+			                       g_variant_new_uint32 (nm_tc_qdisc_get_handle (qdisc)));
+
+			g_variant_builder_add (&qdisc_builder, "{sv}", "parent",
+			                       g_variant_new_uint32 (nm_tc_qdisc_get_parent (qdisc)));
+
+			g_variant_builder_add (&builder, "a{sv}", &qdisc_builder);
+		}
+	}
+
+	return g_variant_builder_end (&builder);
+}
+
+/**
+ * _qdiscs_from_variant:
+ * @value: a #GVariant of type 'aa{sv}'
+ *
+ * Utility function to convert a #GVariant representing a list of TC qdiscs
+ * into a #GPtrArray of * #NMTCQdisc objects.
+ *
+ * Returns: (transfer full) (element-type NMTCQdisc): a newly allocated
+ *   #GPtrArray of #NMTCQdisc objects
+ **/
+static GPtrArray *
+_qdiscs_from_variant (GVariant *value)
+{
+	GPtrArray *qdiscs;
+	GVariant *qdisc_var;
+	GVariantIter iter;
+	GError *error = NULL;
+
+	g_return_val_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}")), NULL);
+
+	g_variant_iter_init (&iter, value);
+	qdiscs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_qdisc_unref);
+
+	while (g_variant_iter_next (&iter, "@a{sv}", &qdisc_var)) {
+		const char *kind;
+		guint32 handle;
+		guint32 parent;
+		NMTCQdisc *qdisc;
+
+		if (   !g_variant_lookup (qdisc_var, "kind", "&s", &kind)
+		    || !g_variant_lookup (qdisc_var, "parent", "u", &parent)) {
+			//g_warning ("Ignoring invalid qdisc");
+			goto next;
+		}
+
+		qdisc = nm_tc_qdisc_new (kind, parent, &error);
+		if (!qdisc) {
+			//g_warning ("Ignoring invalid qdisc: %s", error->message);
+			g_clear_error (&error);
+			goto next;
+		}
+
+		if (g_variant_lookup (qdisc_var, "handle", "u", &handle))
+			nm_tc_qdisc_set_handle (qdisc, handle);
+
+		g_ptr_array_add (qdiscs, qdisc);
+next:
+		g_variant_unref (qdisc_var);
+	}
+
+	return qdiscs;
+}
+
+static GVariant *
+tc_qdiscs_get (NMSetting *setting,
+               const char *property)
+{
+	GPtrArray *qdiscs;
+	GVariant *ret;
+
+	g_object_get (setting, NM_SETTING_TC_CONFIG_QDISCS, &qdiscs, NULL);
+	ret = _qdiscs_to_variant (qdiscs);
+	g_ptr_array_unref (qdiscs);
+
+	return ret;
+}
+
+static gboolean
+tc_qdiscs_set (NMSetting *setting,
+               GVariant *connection_dict,
+               const char *property,
+               GVariant *value,
+               NMSettingParseFlags parse_flags,
+               GError **error)
+{
+	GPtrArray *qdiscs;
+
+	qdiscs = _qdiscs_from_variant (value);
+	g_object_set (setting, NM_SETTING_TC_CONFIG_QDISCS, qdiscs, NULL);
+	g_ptr_array_unref (qdiscs);
+
+	return TRUE;
+}
+
+static GVariant *
+_action_to_variant (NMTCAction *action)
+{
+	GVariantBuilder builder;
+	gs_strfreev char **attrs = nm_tc_action_get_attribute_names (action);
+	int i;
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+
+	g_variant_builder_add (&builder, "{sv}", "kind",
+	                       g_variant_new_string (nm_tc_action_get_kind (action)));
+
+	for (i = 0; attrs[i]; i++) {
+		g_variant_builder_add (&builder, "{sv}", attrs[i],
+				       nm_tc_action_get_attribute (action, attrs[i]));
+	}
+
+	return g_variant_builder_end (&builder);
+}
+
+/**
+ * _tfilters_to_variant:
+ * @tfilters: (element-type NMTCTfilter): an array of #NMTCTfilter objects
+ *
+ * Utility function to convert a #GPtrArray of #NMTCTfilter objects representing
+ * TC tfilters into a #GVariant of type 'aa{sv}' representing an array
+ * of NetworkManager TC tfilters.
+ *
+ * Returns: (transfer none): a new floating #GVariant representing @tfilters.
+ **/
+static GVariant *
+_tfilters_to_variant (GPtrArray *tfilters)
+{
+	GVariantBuilder builder;
+	int i;
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+
+	if (tfilters) {
+		for (i = 0; i < tfilters->len; i++) {
+			NMTCTfilter *tfilter = tfilters->pdata[i];
+			NMTCAction  *action = nm_tc_tfilter_get_action (tfilter);
+			GVariantBuilder tfilter_builder;
+
+			g_variant_builder_init (&tfilter_builder, G_VARIANT_TYPE ("a{sv}"));
+
+			g_variant_builder_add (&tfilter_builder, "{sv}", "kind",
+			                       g_variant_new_string (nm_tc_tfilter_get_kind (tfilter)));
+			g_variant_builder_add (&tfilter_builder, "{sv}", "handle",
+			                       g_variant_new_uint32 (nm_tc_tfilter_get_handle (tfilter)));
+			g_variant_builder_add (&tfilter_builder, "{sv}", "parent",
+			                       g_variant_new_uint32 (nm_tc_tfilter_get_parent (tfilter)));
+
+			if (action) {
+				g_variant_builder_add (&tfilter_builder, "{sv}", "action",
+				                       _action_to_variant (action));
+			}
+
+			g_variant_builder_add (&builder, "a{sv}", &tfilter_builder);
+		}
+	}
+
+	return g_variant_builder_end (&builder);
+}
+
+/**
+ * _tfilters_from_variant:
+ * @value: a #GVariant of type 'aa{sv}'
+ *
+ * Utility function to convert a #GVariant representing a list of TC tfilters
+ * into a #GPtrArray of * #NMTCTfilter objects.
+ *
+ * Returns: (transfer full) (element-type NMTCTfilter): a newly allocated
+ *   #GPtrArray of #NMTCTfilter objects
+ **/
+static GPtrArray *
+_tfilters_from_variant (GVariant *value)
+{
+	GPtrArray *tfilters;
+	GVariant *tfilter_var;
+	GVariantIter iter;
+	GError *error = NULL;
+
+	g_return_val_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}")), NULL);
+
+	g_variant_iter_init (&iter, value);
+	tfilters = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_tc_tfilter_unref);
+
+	while (g_variant_iter_next (&iter, "@a{sv}", &tfilter_var)) {
+		NMTCTfilter *tfilter = NULL;
+		const char *kind;
+		guint32 handle;
+		guint32 parent;
+		NMTCAction *action;
+		const char *action_kind = NULL;
+		char *action_key;
+		GVariantIter action_iter;
+		GVariant *action_var = NULL;
+		GVariant *action_val;
+
+		if (   !g_variant_lookup (tfilter_var, "kind", "&s", &kind)
+		    || !g_variant_lookup (tfilter_var, "parent", "u", &parent)) {
+			//g_warning ("Ignoring invalid tfilter");
+			goto next;
+		}
+
+		tfilter = nm_tc_tfilter_new (kind, parent, &error);
+		if (!tfilter) {
+			//g_warning ("Ignoring invalid tfilter: %s", error->message);
+			g_clear_error (&error);
+			goto next;
+		}
+
+		if (g_variant_lookup (tfilter_var, "handle", "u", &handle))
+			nm_tc_tfilter_set_handle (tfilter, handle);
+
+		action_var = g_variant_lookup_value (tfilter_var, "action", G_VARIANT_TYPE_VARDICT);
+
+
+		if (action_var) {
+			if (!g_variant_lookup (action_var, "kind", "&s", &action_kind)) {
+				//g_warning ("Ignoring tfilter with invalid action");
+				goto next;
+			}
+
+			action = nm_tc_action_new (action_kind, &error);
+			if (!action) {
+				//g_warning ("Ignoring tfilter with invalid action: %s", error->message);
+				g_clear_error (&error);
+				goto next;
+			}
+
+			g_variant_iter_init (&action_iter, action_var);
+			while (g_variant_iter_next (&action_iter, "{&sv}", &action_key, &action_val)) {
+				if (strcmp (action_key, "kind") != 0)
+					nm_tc_action_set_attribute (action, action_key, action_val);
+				g_variant_unref (action_val);
+			}
+
+			nm_tc_tfilter_set_action (tfilter, action);
+			nm_tc_action_unref (action);
+		}
+
+		nm_tc_tfilter_ref (tfilter);
+		g_ptr_array_add (tfilters, tfilter);
+next:
+		if (tfilter)
+			nm_tc_tfilter_unref (tfilter);
+		if (action_var)
+			g_variant_unref (action_var);
+		g_variant_unref (tfilter_var);
+	}
+
+	return tfilters;
+}
+
+static GVariant *
+tc_tfilters_get (NMSetting *setting,
+                 const char *property)
+{
+	GPtrArray *tfilters;
+	GVariant *ret;
+
+	g_object_get (setting, NM_SETTING_TC_CONFIG_TFILTERS, &tfilters, NULL);
+	ret = _tfilters_to_variant (tfilters);
+	g_ptr_array_unref (tfilters);
+
+	return ret;
+}
+
+static gboolean
+tc_tfilters_set (NMSetting *setting,
+                 GVariant *connection_dict,
+                 const char *property,
+                 GVariant *value,
+                 NMSettingParseFlags parse_flags,
+                 GError **error)
+{
+	GPtrArray *tfilters;
+
+	tfilters = _tfilters_from_variant (value);
+	g_object_set (setting, NM_SETTING_TC_CONFIG_TFILTERS, tfilters, NULL);
+	g_ptr_array_unref (tfilters);
+
+	return TRUE;
+}
+
+static void
+nm_setting_tc_config_class_init (NMSettingTCConfigClass *setting_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (setting_class);
+	NMSettingClass *parent_class = NM_SETTING_CLASS (setting_class);
+
+	/* virtual methods */
+	object_class->set_property     = set_property;
+	object_class->get_property     = get_property;
+	object_class->finalize         = finalize;
+	parent_class->compare_property = compare_property;
+
+	/* Properties */
+
+	/**
+	 * NMSettingTCConfig:qdiscs:
+	 *
+	 * Array of TC queuening disciplines.
+	 *
+	 * Element-Type: NMTCQdisc
+	 **/
+	/* ---ifcfg-rh---
+	 * property: qdiscs
+	 * variable: QDISC1, QDISC2, ...
+	 * description: Queueing disciplines
+	 * example: QDISC1=ingress, QDISC2="root handle 1234: fq_codel"
+	 * ---end---
+	 */
+	g_object_class_install_property
+		(object_class, PROP_QDISCS,
+		 g_param_spec_boxed (NM_SETTING_TC_CONFIG_QDISCS, "", "",
+		                     G_TYPE_PTR_ARRAY,
+		                     G_PARAM_READWRITE |
+		                     NM_SETTING_PARAM_INFERRABLE |
+		                     G_PARAM_STATIC_STRINGS));
+
+	_nm_setting_class_override_property (parent_class,
+	                                     NM_SETTING_TC_CONFIG_QDISCS,
+	                                     G_VARIANT_TYPE ("aa{sv}"),
+	                                     tc_qdiscs_get,
+	                                     tc_qdiscs_set,
+	                                     NULL);
+
+	/**
+	 * NMSettingTCConfig:tfilters:
+	 *
+	 * Array of TC traffic filters.
+	 *
+	 * Element-Type: NMTCTfilter
+	 **/
+	/* ---ifcfg-rh---
+	 * property: qdiscs
+	 * variable: FILTER1, FILTER2, ...
+	 * description: Traffic filters
+	 * example: FILTER1="parent ffff: matchall action simple sdata Input", ...
+	 * ---end---
+	 */
+	g_object_class_install_property
+		(object_class, PROP_TFILTERS,
+		 g_param_spec_boxed (NM_SETTING_TC_CONFIG_TFILTERS, "", "",
+		                     G_TYPE_PTR_ARRAY,
+		                     G_PARAM_READWRITE |
+		                     NM_SETTING_PARAM_INFERRABLE |
+		                     G_PARAM_STATIC_STRINGS));
+
+	_nm_setting_class_override_property (parent_class,
+	                                     NM_SETTING_TC_CONFIG_TFILTERS,
+	                                     G_VARIANT_TYPE ("aa{sv}"),
+	                                     tc_tfilters_get,
+	                                     tc_tfilters_set,
+	                                     NULL);
+}
diff --git a/libnm-core/nm-setting-tc-config.h b/libnm-core/nm-setting-tc-config.h
new file mode 100644
index 00000000..736d5ad1
--- /dev/null
+++ b/libnm-core/nm-setting-tc-config.h
@@ -0,0 +1,191 @@
+/*
+ * 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 2017 Red Hat, Inc.
+ */
+
+#ifndef NM_SETTING_TC_CONFIG_H
+#define NM_SETTING_TC_CONFIG_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
+
+NM_AVAILABLE_IN_1_10_2
+typedef struct NMTCQdisc NMTCQdisc;
+
+NM_AVAILABLE_IN_1_10_2
+GType       nm_tc_qdisc_get_type             (void);
+
+NM_AVAILABLE_IN_1_10_2
+NMTCQdisc  *nm_tc_qdisc_new                  (const char *kind,
+                                              guint32 parent,
+                                              GError **error);
+
+NM_AVAILABLE_IN_1_10_2
+void        nm_tc_qdisc_ref                  (NMTCQdisc *qdisc);
+NM_AVAILABLE_IN_1_10_2
+void        nm_tc_qdisc_unref                (NMTCQdisc *qdisc);
+NM_AVAILABLE_IN_1_10_2
+gboolean    nm_tc_qdisc_equal                (NMTCQdisc *qdisc,
+                                              NMTCQdisc *other);
+
+NM_AVAILABLE_IN_1_10_2
+NMTCQdisc  *nm_tc_qdisc_dup                  (NMTCQdisc  *qdisc);
+
+
+NM_AVAILABLE_IN_1_10_2
+const char *nm_tc_qdisc_get_kind             (NMTCQdisc *qdisc);
+NM_AVAILABLE_IN_1_10_2
+guint32     nm_tc_qdisc_get_handle           (NMTCQdisc *qdisc);
+NM_AVAILABLE_IN_1_10_2
+void        nm_tc_qdisc_set_handle           (NMTCQdisc *qdisc,
+                                              guint32 handle);
+NM_AVAILABLE_IN_1_10_2
+guint32     nm_tc_qdisc_get_parent           (NMTCQdisc *qdisc);
+
+NM_AVAILABLE_IN_1_10_2
+typedef struct NMTCAction NMTCAction;
+
+NM_AVAILABLE_IN_1_10_2
+GType       nm_tc_action_get_type            (void);
+
+NM_AVAILABLE_IN_1_10_2
+NMTCAction  *nm_tc_action_new                (const char *kind,
+                                              GError **error);
+
+NM_AVAILABLE_IN_1_10_2
+void        nm_tc_action_ref                 (NMTCAction *action);
+NM_AVAILABLE_IN_1_10_2
+void        nm_tc_action_unref               (NMTCAction *action);
+NM_AVAILABLE_IN_1_10_2
+gboolean    nm_tc_action_equal               (NMTCAction *action,
+                                              NMTCAction *other);
+
+NM_AVAILABLE_IN_1_10_2
+NMTCAction  *nm_tc_action_dup                (NMTCAction  *action);
+
+
+NM_AVAILABLE_IN_1_10_2
+const char *nm_tc_action_get_kind            (NMTCAction *action);
+
+NM_AVAILABLE_IN_1_10_2
+char      **nm_tc_action_get_attribute_names (NMTCAction *action);
+NM_AVAILABLE_IN_1_10_2
+GVariant   *nm_tc_action_get_attribute       (NMTCAction *action,
+                                              const char *name);
+NM_AVAILABLE_IN_1_10_2
+void        nm_tc_action_set_attribute       (NMTCAction *action,
+                                              const char *name,
+                                              GVariant *value);
+
+NM_AVAILABLE_IN_1_10_2
+typedef struct NMTCTfilter NMTCTfilter;
+
+NM_AVAILABLE_IN_1_10_2
+GType       nm_tc_tfilter_get_type           (void);
+
+NM_AVAILABLE_IN_1_10_2
+NMTCTfilter  *nm_tc_tfilter_new              (const char *kind,
+                                              guint32 parent,
+                                              GError **error);
+
+NM_AVAILABLE_IN_1_10_2
+void        nm_tc_tfilter_ref                (NMTCTfilter *tfilter);
+NM_AVAILABLE_IN_1_10_2
+void        nm_tc_tfilter_unref              (NMTCTfilter *tfilter);
+NM_AVAILABLE_IN_1_10_2
+gboolean    nm_tc_tfilter_equal              (NMTCTfilter *tfilter,
+                                              NMTCTfilter *other);
+
+NM_AVAILABLE_IN_1_10_2
+NMTCTfilter  *nm_tc_tfilter_dup              (NMTCTfilter  *tfilter);
+
+NM_AVAILABLE_IN_1_10_2
+const char *nm_tc_tfilter_get_kind           (NMTCTfilter *tfilter);
+NM_AVAILABLE_IN_1_10_2
+guint32     nm_tc_tfilter_get_handle         (NMTCTfilter *tfilter);
+NM_AVAILABLE_IN_1_10_2
+void        nm_tc_tfilter_set_handle         (NMTCTfilter *tfilter,
+                                              guint32 handle);
+NM_AVAILABLE_IN_1_10_2
+guint32     nm_tc_tfilter_get_parent         (NMTCTfilter *tfilter);
+NM_AVAILABLE_IN_1_10_2
+NMTCAction *nm_tc_tfilter_get_action         (NMTCTfilter *tfilter);
+NM_AVAILABLE_IN_1_10_2
+void        nm_tc_tfilter_set_action         (NMTCTfilter *tfilter, NMTCAction *action);
+
+
+#define NM_TYPE_SETTING_TC_CONFIG            (nm_setting_tc_config_get_type ())
+#define NM_SETTING_TC_CONFIG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTING_TC_CONFIG, NMSettingTCConfig))
+#define NM_SETTING_TC_CONFIG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTING_TC_CONFIG, NMSettingTCConfigClass))
+#define NM_IS_SETTING_TC_CONFIG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SETTING_TC_CONFIG))
+#define NM_IS_SETTING_TC_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SETTING_TC_CONFIG))
+#define NM_SETTING_TC_CONFIG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SETTING_TC_CONFIG, NMSettingTCConfigClass))
+
+#define NM_SETTING_TC_CONFIG_SETTING_NAME    "tc"
+
+#define NM_SETTING_TC_CONFIG_QDISCS          "qdiscs"
+#define NM_SETTING_TC_CONFIG_TFILTERS        "tfilters"
+
+typedef struct _NMSettingTCConfigClass NMSettingTCConfigClass;
+
+GType nm_setting_tc_config_get_type (void);
+
+NM_AVAILABLE_IN_1_10_2
+NMSetting *nm_setting_tc_config_new (void);
+
+NM_AVAILABLE_IN_1_10_2
+guint      nm_setting_tc_config_get_num_qdiscs          (NMSettingTCConfig *setting);
+NM_AVAILABLE_IN_1_10_2
+NMTCQdisc *nm_setting_tc_config_get_qdisc               (NMSettingTCConfig *setting,
+                                                         guint idx);
+NM_AVAILABLE_IN_1_10_2
+gboolean   nm_setting_tc_config_add_qdisc               (NMSettingTCConfig *setting,
+                                                         NMTCQdisc *qdisc);
+NM_AVAILABLE_IN_1_10_2
+void       nm_setting_tc_config_remove_qdisc            (NMSettingTCConfig *setting,
+                                                         guint idx);
+NM_AVAILABLE_IN_1_10_2
+gboolean   nm_setting_tc_config_remove_qdisc_by_value   (NMSettingTCConfig *setting,
+                                                         NMTCQdisc *qdisc);
+NM_AVAILABLE_IN_1_10_2
+void       nm_setting_tc_config_clear_qdiscs            (NMSettingTCConfig *setting);
+
+NM_AVAILABLE_IN_1_10_2
+guint      nm_setting_tc_config_get_num_tfilters        (NMSettingTCConfig *setting);
+NM_AVAILABLE_IN_1_10_2
+NMTCTfilter *nm_setting_tc_config_get_tfilter           (NMSettingTCConfig *setting,
+                                                         guint idx);
+NM_AVAILABLE_IN_1_10_2
+gboolean   nm_setting_tc_config_add_tfilter             (NMSettingTCConfig *setting,
+                                                         NMTCTfilter *tfilter);
+NM_AVAILABLE_IN_1_10_2
+void       nm_setting_tc_config_remove_tfilter          (NMSettingTCConfig *setting,
+                                                         guint idx);
+NM_AVAILABLE_IN_1_10_2
+gboolean   nm_setting_tc_config_remove_tfilter_by_value (NMSettingTCConfig *setting,
+                                                         NMTCTfilter *tfilter);
+NM_AVAILABLE_IN_1_10_2
+void       nm_setting_tc_config_clear_tfilters          (NMSettingTCConfig *setting);
+
+G_END_DECLS
+
+#endif /* NM_SETTING_TC_CONFIG_H */
diff --git a/libnm-core/nm-setting-team-port.c b/libnm-core/nm-setting-team-port.c
index c0309665..5ab8f691 100644
--- a/libnm-core/nm-setting-team-port.c
+++ b/libnm-core/nm-setting-team-port.c
@@ -15,6 +15,7 @@
  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301 USA.
  *
+ * Copyright 2017 Red Hat, Inc.
  * Copyright 2013 Jiri Pirko <jiri@resnulli.us>
  */
 
@@ -29,7 +30,6 @@
 #include "nm-utils-private.h"
 #include "nm-connection-private.h"
 #include "nm-setting-connection.h"
-#include "nm-setting-team.h"
 
 /**
  * SECTION:nm-setting-team-port
@@ -47,14 +47,40 @@ NM_SETTING_REGISTER_TYPE (NM_TYPE_SETTING_TEAM_PORT)
 
 typedef struct {
 	char *config;
+	int queue_id;
+	int prio;
+	gboolean sticky;
+	int lacp_prio;
+	int lacp_key;
+	GPtrArray *link_watchers; /* Array of NMTeamLinkWatcher */
 } NMSettingTeamPortPrivate;
 
+/* Keep aligned with _prop_to_keys[] */
 enum {
 	PROP_0,
 	PROP_CONFIG,
+	PROP_QUEUE_ID,
+	PROP_PRIO,
+	PROP_STICKY,
+	PROP_LACP_PRIO,
+	PROP_LACP_KEY,
+	PROP_LINK_WATCHERS,
 	LAST_PROP
 };
 
+/* Keep aligned with team-port properties enum */
+static const _NMUtilsTeamPropertyKeys _prop_to_keys[LAST_PROP] = {
+	[PROP_0] =             { NULL, NULL, NULL, 0 },
+	[PROP_CONFIG] =        { NULL, NULL, NULL, 0 },
+	[PROP_QUEUE_ID] =      { "queue_id", NULL, NULL, NM_SETTING_TEAM_PORT_QUEUE_ID_DEFAULT },
+	[PROP_PRIO] =          { "prio", NULL, NULL, 0 },
+	[PROP_STICKY] =        { "sticky", NULL, NULL, 0 },
+	[PROP_LACP_PRIO] =     { "lacp_prio", NULL, NULL, NM_SETTING_TEAM_PORT_LACP_PRIO_DEFAULT },
+	[PROP_LACP_KEY] =      { "lacp_key", NULL, NULL, 0 },
+	[PROP_LINK_WATCHERS] = { "link_watch", NULL, NULL, 0 }
+};
+
+
 /**
  * nm_setting_team_port_new:
  *
@@ -82,6 +108,239 @@ nm_setting_team_port_get_config (NMSettingTeamPort *setting)
 	return NM_SETTING_TEAM_PORT_GET_PRIVATE (setting)->config;
 }
 
+/**
+ * nm_setting_team_port_get_queue_id:
+ * @setting: the #NMSettingTeamPort
+ *
+ * Returns: the #NMSettingTeamPort:queue_id property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gint
+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;
+}
+
+/**
+ * nm_setting_team_port_get_prio:
+ * @setting: the #NMSettingTeamPort
+ *
+ * Returns: the #NMSettingTeamPort:prio property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gint
+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;
+}
+
+/**
+ * nm_setting_team_port_get_sticky:
+ * @setting: the #NMSettingTeamPort
+ *
+ * Returns: the #NMSettingTeamPort:sticky property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gboolean
+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;
+}
+
+/**
+ * nm_setting_team_port_get_lacp_prio:
+ * @setting: the #NMSettingTeamPort
+ *
+ * Returns: the #NMSettingTeamPort:lacp-prio property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gint
+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;
+}
+
+/**
+ * nm_setting_team_port_get_lacp_key:
+ * @setting: the #NMSettingTeamPort
+ *
+ * Returns: the #NMSettingTeamPort:lacp-key property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gint
+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;
+}
+
+/**
+ * nm_setting_team_port_get_num_link_watchers:
+ * @setting: the #NMSettingTeamPort
+ *
+ * Returns: the number of configured link watchers
+ *
+ * Since: 1.10.2
+ **/
+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;
+}
+
+/**
+ * nm_setting_team_port_get_link_watcher:
+ * @setting: the #NMSettingTeamPort
+ * @idx: index number of the link watcher to return
+ *
+ * Returns: (transfer none): the link watcher at index @idx.
+ *
+ * Since: 1.10.2
+ **/
+NMTeamLinkWatcher *
+nm_setting_team_port_get_link_watcher (NMSettingTeamPort *setting, guint idx)
+{
+	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
+
+	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];
+}
+
+/**
+ * nm_setting_team_port_add_link_watcher:
+ * @setting: the #NMSettingTeamPort
+ * @link_watcher: the link watcher to add
+ *
+ * Appends a new link watcher to the setting.
+ *
+ * Returns: %TRUE if the link watcher is added; %FALSE if an identical link
+ * watcher was already there.
+ *
+ * Since: 1.10.2
+ **/
+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));
+	g_object_notify (G_OBJECT (setting), NM_SETTING_TEAM_PORT_LINK_WATCHERS);
+	return TRUE;
+}
+
+/**
+ * nm_setting_team_port_remove_link_watcher:
+ * @setting: the #NMSettingTeamPort
+ * @idx: index number of the link watcher to remove
+ *
+ * Removes the link watcher at index #idx.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_setting_team_port_remove_link_watcher (NMSettingTeamPort *setting, guint idx)
+{
+	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
+
+	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);
+	g_object_notify (G_OBJECT (setting), NM_SETTING_TEAM_PORT_LINK_WATCHERS);
+}
+
+/**
+ * nm_setting_team_port_remove_link_watcher_by_value:
+ * @setting: the #NMSettingTeamPort
+ * @link_watcher: the link watcher to remove
+ *
+ * Removes the link watcher entry matching link_watcher.
+ *
+ * Returns: %TRUE if the link watcher was found and removed, %FALSE otherwise.
+ *
+ * Since: 1.10.2
+ **/
+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);
+
+	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);
+			g_object_notify (G_OBJECT (setting), NM_SETTING_TEAM_PORT_LINK_WATCHERS);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+/**
+ * nm_setting_team_port_clear_link_watchers:
+ * @setting: the #NMSettingTeamPort
+ *
+ * Removes all configured link watchers.
+ *
+ * Since: 1.10.2
+ **/
+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));
+
+	g_ptr_array_set_size (priv->link_watchers, 0);
+	g_object_notify (G_OBJECT (setting), NM_SETTING_TEAM_PORT_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));
+}
+
 static gboolean
 verify (NMSetting *setting, NMConnection *connection, GError **error)
 {
@@ -150,6 +409,8 @@ compare_property (NMSetting *setting,
                   NMSettingCompareFlags flags)
 {
 	NMSettingClass *parent_class;
+	NMSettingTeamPortPrivate *a_priv, *b_priv;
+	guint i, j;
 
 	/* If we are trying to match a connection in order to assume it (and thus
 	 * @flags contains INFERRABLE), use the "relaxed" matching for team
@@ -162,6 +423,24 @@ compare_property (NMSetting *setting,
 		                                    NM_SETTING_TEAM_PORT_GET_PRIVATE (other)->config,
 		                                    TRUE);
 	}
+	if (nm_streq0 (prop_spec->name, NM_SETTING_TEAM_PORT_LINK_WATCHERS)) {
+		a_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (setting);
+		b_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (other);
+
+		if (a_priv->link_watchers->len != b_priv->link_watchers->len)
+			return FALSE;
+		for (i = 0; i < a_priv->link_watchers->len; i++) {
+			for (j = 0; j < b_priv->link_watchers->len; j++) {
+				if (nm_team_link_watcher_equal (a_priv->link_watchers->pdata[i],
+				                                b_priv->link_watchers->pdata[j])) {
+					break;
+				}
+			}
+			if (j == b_priv->link_watchers->len)
+				return FALSE;
+		}
+		return TRUE;
+	}
 
 	/* Otherwise chain up to parent to handle generic compare */
 	parent_class = NM_SETTING_CLASS (nm_setting_team_port_parent_class);
@@ -171,23 +450,92 @@ compare_property (NMSetting *setting,
 static void
 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);
 }
 
+#define JSON_TO_VAL(typ, id)   _nm_utils_json_extract_##typ (priv->config, _prop_to_keys[id], TRUE)
+
 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;
 
 	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;
+		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;
+		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;
+		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;
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
 	}
+	if (align_config)
+		_nm_utils_json_append_gvalue (&priv->config, _prop_to_keys[prop_id], align_value);
 }
 
 static void
@@ -195,11 +543,32 @@ get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
 	NMSettingTeamPort *setting = NM_SETTING_TEAM_PORT (object);
+	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));
 		break;
+	case PROP_QUEUE_ID:
+		g_value_set_int (value, priv->queue_id);
+		break;
+	case PROP_PRIO:
+		g_value_set_int (value, priv->prio);
+		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,
+		                                                 (GDestroyNotify) nm_team_link_watcher_unref));
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -212,6 +581,7 @@ finalize (GObject *object)
 	NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE (object);
 
 	g_free (priv->config);
+	g_ptr_array_unref (priv->link_watchers);
 
 	G_OBJECT_CLASS (nm_setting_team_port_parent_class)->finalize (object);
 }
@@ -253,4 +623,103 @@ nm_setting_team_port_class_init (NMSettingTeamPortClass *setting_class)
 		                      G_PARAM_READWRITE |
 		                      NM_SETTING_PARAM_INFERRABLE |
 		                      G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeamPort:queue-id:
+	 *
+	 * Corresponds to the teamd ports.PORTIFNAME.queue_id.
+	 * When set to -1 means the parameter is skipped from the json config.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_QUEUE_ID,
+		 g_param_spec_int (NM_SETTING_TEAM_PORT_QUEUE_ID, "", "",
+		                   G_MININT32, G_MAXINT32, 0,
+		                   G_PARAM_READWRITE |
+		                   G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeamPort:prio:
+	 *
+	 * Corresponds to the teamd ports.PORTIFNAME.prio.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_PRIO,
+		 g_param_spec_int (NM_SETTING_TEAM_PORT_PRIO, "", "",
+		                   G_MININT32, G_MAXINT32, 0,
+		                   G_PARAM_READWRITE |
+		                   G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeamPort:sticky:
+	 *
+	 * Corresponds to the teamd ports.PORTIFNAME.sticky.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_STICKY,
+		 g_param_spec_boolean (NM_SETTING_TEAM_PORT_STICKY, "", "",
+		                       FALSE,
+		                       G_PARAM_READWRITE |
+		                       G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeamPort:lacp-prio:
+	 *
+	 * Corresponds to the teamd ports.PORTIFNAME.lacp_prio.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_LACP_PRIO,
+		 g_param_spec_int (NM_SETTING_TEAM_PORT_LACP_PRIO, "", "",
+		                   G_MININT32, G_MAXINT32, 0,
+		                   G_PARAM_READWRITE |
+		                   G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeamPort:lacp-key:
+	 *
+	 * Corresponds to the teamd ports.PORTIFNAME.lacp_key.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_LACP_KEY,
+		 g_param_spec_int (NM_SETTING_TEAM_PORT_LACP_KEY, "", "",
+		                   G_MININT32, G_MAXINT32, 0,
+		                   G_PARAM_READWRITE |
+		                   G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeamPort:link-watchers:
+	 *
+	 * Link watchers configuration for the connection: each link watcher is
+	 * defined by a dictionary, whose keys depend upon the selected link
+	 * watcher. Available link watchers are 'ethtool', 'nsna_ping' and
+	 * 'arp_ping' and it is specified in the dictionary with the key 'name'.
+	 * Available keys are:   ethtool: 'delay-up', 'delay-down', 'init-wait';
+	 * nsna_ping: 'init-wait', 'interval', 'missed-max', 'target-host';
+	 * arp_ping: all the ones in nsna_ping and 'source-host', 'validate-active',
+	 * 'validate-incative', 'send-always'. See teamd.conf man for more details.
+	 *
+	 * Element-Type: NMTeamLinkWatcher
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_LINK_WATCHERS,
+		 g_param_spec_boxed (NM_SETTING_TEAM_PORT_LINK_WATCHERS, "", "",
+		                     G_TYPE_PTR_ARRAY,
+		                     G_PARAM_READWRITE |
+		                     G_PARAM_STATIC_STRINGS));
+	_nm_setting_class_transform_property (parent_class,
+	                                      NM_SETTING_TEAM_PORT_LINK_WATCHERS,
+	                                      G_VARIANT_TYPE ("aa{sv}"),
+	                                      team_link_watchers_to_dbus,
+	                                      team_link_watchers_from_dbus);
+
 }
diff --git a/libnm-core/nm-setting-team-port.h b/libnm-core/nm-setting-team-port.h
index 4fcc5108..26b2e098 100644
--- a/libnm-core/nm-setting-team-port.h
+++ b/libnm-core/nm-setting-team-port.h
@@ -26,6 +26,7 @@
 #endif
 
 #include "nm-setting.h"
+#include "nm-setting-team.h"
 
 G_BEGIN_DECLS
 
@@ -38,7 +39,16 @@ G_BEGIN_DECLS
 
 #define NM_SETTING_TEAM_PORT_SETTING_NAME "team-port"
 
-#define NM_SETTING_TEAM_PORT_CONFIG     "config"
+#define NM_SETTING_TEAM_PORT_CONFIG        "config"
+#define NM_SETTING_TEAM_PORT_QUEUE_ID      "queue-id"
+#define NM_SETTING_TEAM_PORT_PRIO          "prio"
+#define NM_SETTING_TEAM_PORT_STICKY        "sticky"
+#define NM_SETTING_TEAM_PORT_LACP_PRIO     "lacp-prio"
+#define NM_SETTING_TEAM_PORT_LACP_KEY      "lacp-key"
+#define NM_SETTING_TEAM_PORT_LINK_WATCHERS "link-watchers"
+
+#define NM_SETTING_TEAM_PORT_QUEUE_ID_DEFAULT   -1
+#define NM_SETTING_TEAM_PORT_LACP_PRIO_DEFAULT 255
 
 /**
  * NMSettingTeamPort:
@@ -61,7 +71,34 @@ GType nm_setting_team_port_get_type (void);
 NMSetting *  nm_setting_team_port_new (void);
 
 const char * nm_setting_team_port_get_config (NMSettingTeamPort *setting);
-
+NM_AVAILABLE_IN_1_10_2
+gint nm_setting_team_port_get_queue_id (NMSettingTeamPort *setting);
+NM_AVAILABLE_IN_1_10_2
+gint nm_setting_team_port_get_prio (NMSettingTeamPort *setting);
+NM_AVAILABLE_IN_1_10_2
+gboolean nm_setting_team_port_get_sticky (NMSettingTeamPort *setting);
+NM_AVAILABLE_IN_1_10_2
+gint nm_setting_team_port_get_lacp_prio (NMSettingTeamPort *setting);
+NM_AVAILABLE_IN_1_10_2
+gint nm_setting_team_port_get_lacp_key (NMSettingTeamPort *setting);
+NM_AVAILABLE_IN_1_10_2
+guint nm_setting_team_port_get_num_link_watchers (NMSettingTeamPort *setting);
+NM_AVAILABLE_IN_1_10_2
+NMTeamLinkWatcher *
+nm_setting_team_port_get_link_watcher (NMSettingTeamPort *setting, guint idx);
+NM_AVAILABLE_IN_1_10_2
+gboolean
+nm_setting_team_port_add_link_watcher (NMSettingTeamPort *setting,
+                                       NMTeamLinkWatcher *link_watcher);
+NM_AVAILABLE_IN_1_10_2
+void
+nm_setting_team_port_remove_link_watcher (NMSettingTeamPort *setting, guint idx);
+NM_AVAILABLE_IN_1_10_2
+gboolean
+nm_setting_team_port_remove_link_watcher_by_value (NMSettingTeamPort *setting,
+                                                   NMTeamLinkWatcher *link_watcher);
+NM_AVAILABLE_IN_1_10_2
+void nm_setting_team_port_clear_link_watchers (NMSettingTeamPort *setting);
 G_END_DECLS
 
 #endif /* __NM_SETTING_TEAM_PORT_H__ */
diff --git a/libnm-core/nm-setting-team.c b/libnm-core/nm-setting-team.c
index 0a09c4a6..d3688278 100644
--- a/libnm-core/nm-setting-team.c
+++ b/libnm-core/nm-setting-team.c
@@ -15,6 +15,7 @@
  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301 USA.
  *
+ * Copyright 2017 Red Hat, Inc.
  * Copyright 2013 Jiri Pirko <jiri@resnulli.us>
  */
 
@@ -36,6 +37,526 @@
  * necessary for team connections.
  **/
 
+/*****************************************************************************
+ * NMTeamLinkWatch
+ *****************************************************************************/
+
+G_DEFINE_BOXED_TYPE (NMTeamLinkWatcher, nm_team_link_watcher,
+                     nm_team_link_watcher_dup, nm_team_link_watcher_unref)
+
+enum LinkWatcherTypes {
+	LINK_WATCHER_ETHTOOL   = 0,
+	LINK_WATCHER_NSNA_PING = 1,
+	LINK_WATCHER_ARP_PING  = 2
+};
+
+static const char* _link_watcher_name[] = {
+	[LINK_WATCHER_ETHTOOL]   = NM_TEAM_LINK_WATCHER_ETHTOOL,
+	[LINK_WATCHER_NSNA_PING] = NM_TEAM_LINK_WATCHER_NSNA_PING,
+	[LINK_WATCHER_ARP_PING]  = NM_TEAM_LINK_WATCHER_ARP_PING
+};
+
+struct NMTeamLinkWatcher {
+	guint refcount;
+
+	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 {
+			int init_wait;
+			int interval;
+			int missed_max;
+			char *target_host;
+		} nsna_ping;
+		struct {
+			int init_wait;
+			int interval;
+			int missed_max;
+			char *target_host;
+			char *source_host;
+			NMTeamLinkWatcherArpPingFlags flags;
+		} arp_ping;
+	};
+};
+
+#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_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_STMT_END
+
+/**
+ * nm_team_link_watcher_new_ethtool:
+ * @delay_up: delay_up value
+ * @delay_down: delay_down value
+ * @error: this call never fails, so this var is not used but kept for format
+ *   consistency with the link_watcher constructors of other type
+ *
+ * Creates a new ethtool #NMTeamLinkWatcher object
+ *
+ * Returns: (transfer full): the new #NMTeamLinkWatcher object
+ *
+ * Since: 1.10.2
+ **/
+NMTeamLinkWatcher *
+nm_team_link_watcher_new_ethtool (gint delay_up,
+                                  gint delay_down,
+                                  GError **error)
+{
+	NMTeamLinkWatcher *watcher;
+	const char *val_fail = NULL;
+
+	if (delay_up < 0 || delay_up > G_MAXINT32)
+		val_fail = "delay-up";
+	if (delay_down < 0 || delay_down > G_MAXINT32)
+		val_fail = "delay-down";
+	if (val_fail) {
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		             _("%s is out of range [0, %d]"), val_fail, G_MAXINT32);
+		return NULL;
+	}
+
+	watcher = g_slice_new0 (NMTeamLinkWatcher);
+	watcher->refcount = 1;
+
+	watcher->type = LINK_WATCHER_ETHTOOL;
+	watcher->ethtool.delay_up = delay_up;
+	watcher->ethtool.delay_down = delay_down;
+
+	return watcher;
+}
+
+/**
+ * nm_team_link_watcher_new_nsna_ping:
+ * @init_wait: init_wait value
+ * @interval: interval value
+ * @missed_max: missed_max value
+ * @target_host: the host name or the ipv6 address that will be used as
+ *   target address in the NS packet
+ * @error: (out) (allow-none): location to store the error on failure
+ *
+ * Creates a new nsna_ping #NMTeamLinkWatcher object
+ *
+ * Returns: (transfer full): the new #NMTeamLinkWatcher object, or %NULL on error
+ *
+ * Since: 1.10.2
+ **/
+NMTeamLinkWatcher *
+nm_team_link_watcher_new_nsna_ping (gint init_wait,
+                                    gint interval,
+                                    gint missed_max,
+                                    const char *target_host,
+                                    GError **error)
+{
+	NMTeamLinkWatcher *watcher;
+	const char *val_fail = NULL;
+
+	if (!target_host) {
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		             _("Missing target-host in nsna_ping link watcher"));
+		return NULL;
+	}
+
+	if (strpbrk (target_host, " \\/\t=\"\'")) {
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		             _("target-host '%s' contains invalid characters"), target_host);
+		return NULL;
+	}
+
+	if (init_wait < 0 || init_wait > G_MAXINT32)
+		val_fail = "init-wait";
+	if (interval < 0 || interval > G_MAXINT32)
+		val_fail = "interval";
+	if (missed_max < 0 || missed_max > G_MAXINT32)
+		val_fail = "missed-max";
+	if (val_fail) {
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		             _("%s is out of range [0, %d]"), val_fail, G_MAXINT32);
+		return NULL;
+	}
+
+	watcher = g_slice_new0 (NMTeamLinkWatcher);
+	watcher->refcount = 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);
+
+	return watcher;
+}
+
+/**
+ * nm_team_link_watcher_new_arp_ping:
+ * @init_wait: init_wait value
+ * @interval: interval value
+ * @missed_max: missed_max value
+ * @target_host: the host name or the ip address that will be used as destination
+ *   address in the arp request
+ * @source_host: the host name or the ip address that will be used as source
+ *   address in the arp request
+ * @flags: the watcher #NMTeamLinkWatcherArpPingFlags
+ * @error: (out) (allow-none): location to store the error on failure
+ *
+ * Creates a new arp_ping #NMTeamLinkWatcher object
+ *
+ * Returns: (transfer full): the new #NMTeamLinkWatcher object, or %NULL on error
+ *
+ * Since: 1.10.2
+ **/
+NMTeamLinkWatcher *
+nm_team_link_watcher_new_arp_ping (gint init_wait,
+                                   gint interval,
+                                   gint missed_max,
+                                   const char *target_host,
+                                   const char *source_host,
+                                   NMTeamLinkWatcherArpPingFlags flags,
+                                   GError **error)
+{
+	NMTeamLinkWatcher *watcher;
+	const char *val_fail = NULL;
+
+	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");
+		return NULL;
+	}
+
+	if (strpbrk (target_host, " \\/\t=\"\'")) {
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		             _("target-host '%s' contains invalid characters"), target_host);
+		return NULL;
+	}
+
+	if (strpbrk (target_host, " \\/\t=\"\'")) {
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		             _("source-host '%s' contains invalid characters"), source_host);
+		return NULL;
+	}
+
+	if (init_wait < 0 || init_wait > G_MAXINT32)
+		val_fail = "init-wait";
+	if (interval < 0 || interval > G_MAXINT32)
+		val_fail = "interval";
+	if (missed_max < 0 || missed_max > G_MAXINT32)
+		val_fail = "missed-max";
+	if (val_fail) {
+		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+		             _("%s is out of range [0, %d]"), val_fail, G_MAXINT32);
+		return NULL;
+	}
+
+	watcher = g_slice_new0 (NMTeamLinkWatcher);
+	watcher->refcount = 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;
+
+	return watcher;
+}
+
+/**
+ * nm_team_link_watcher_ref:
+ * @watcher: the #NMTeamLinkWatcher
+ *
+ * Increases the reference count of the object.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_team_link_watcher_ref (NMTeamLinkWatcher *watcher){
+	_CHECK_WATCHER_VOID (watcher);
+
+	watcher->refcount++;
+}
+
+/**
+ * nm_team_link_watcher_unref:
+ * @watcher: the #NMTeamLinkWatcher
+ *
+ * Decreases the reference count of the object.  If the reference count
+ * reaches zero, the object will be destroyed.
+ *
+ * Since: 1.10.2
+ **/
+void
+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);
+	}
+}
+
+/**
+ * nm_team_link_watcher_equal:
+ * @watcher: the #NMTeamLinkWatcher
+ * @other: the #NMTeamLinkWatcher to compare @watcher to.
+ *
+ * Determines if two #NMTeamLinkWatcher objects contain the same values
+ * in all the properties.
+ *
+ * Returns: %TRUE if the objects contain the same values, %FALSE if they do not.
+ *
+ * Since: 1.10.2
+ **/
+gboolean
+nm_team_link_watcher_equal (NMTeamLinkWatcher *watcher, 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.flags != other->arp_ping.flags)
+		return FALSE;
+
+	return TRUE;
+}
+
+/**
+ * nm_team_link_watcher_dup:
+ * @watcher: the #NMTeamLinkWatcher
+ *
+ * Creates a copy of @watcher
+ *
+ * Returns: (transfer full): a copy of @watcher
+ *
+ * Since: 1.10.2
+ **/
+NMTeamLinkWatcher *
+nm_team_link_watcher_dup (NMTeamLinkWatcher *watcher)
+{
+	_CHECK_WATCHER (watcher, NULL);
+
+	switch (watcher->type) {
+	case LINK_WATCHER_ETHTOOL:
+		return nm_team_link_watcher_new_ethtool (watcher->ethtool.delay_up,
+		                                         watcher->ethtool.delay_down,
+		                                         NULL);
+		break;
+	case LINK_WATCHER_NSNA_PING:
+		return nm_team_link_watcher_new_nsna_ping (watcher->nsna_ping.init_wait,
+		                                           watcher->nsna_ping.interval,
+		                                           watcher->nsna_ping.missed_max,
+		                                           watcher->nsna_ping.target_host,
+		                                           NULL);
+		break;
+	case LINK_WATCHER_ARP_PING:
+		return nm_team_link_watcher_new_arp_ping (watcher->arp_ping.init_wait,
+		                                          watcher->arp_ping.interval,
+		                                          watcher->arp_ping.missed_max,
+		                                          watcher->arp_ping.target_host,
+		                                          watcher->arp_ping.source_host,
+		                                          watcher->arp_ping.flags,
+		                                          NULL);
+	default:
+		g_assert_not_reached ();
+		return NULL;
+	}
+}
+
+/**
+ * nm_team_link_watcher_get_name:
+ * @watcher: the #NMTeamLinkWatcher
+ *
+ * Gets the name of the link watcher to be used.
+ *
+ * Since: 1.10.2
+ **/
+const char *
+nm_team_link_watcher_get_name (NMTeamLinkWatcher *watcher)
+{
+	_CHECK_WATCHER (watcher, NULL);
+
+	return _link_watcher_name[watcher->type];
+}
+
+/**
+ * nm_team_link_watcher_get_delay_up:
+ * @watcher: the #NMTeamLinkWatcher
+ *
+ * Gets the delay_up interval (in milliseconds) that elapses between the link
+ * coming up and the runner beeing notified about it.
+ *
+ * Since: 1.10.2
+ **/
+int
+nm_team_link_watcher_get_delay_up (NMTeamLinkWatcher *watcher)
+{
+	_CHECK_WATCHER (watcher, 0);
+
+	if (watcher->type != LINK_WATCHER_ETHTOOL)
+		return -1;
+	return watcher->ethtool.delay_up;
+}
+
+/**
+ * nm_team_link_watcher_get_delay_down:
+ * @watcher: the #NMTeamLinkWatcher
+ *
+ * Gets the delay_down interval (in milliseconds) that elapses between the link
+ * going down and the runner beeing notified about it.
+ *
+ * Since: 1.10.2
+ **/
+int
+nm_team_link_watcher_get_delay_down (NMTeamLinkWatcher *watcher)
+{
+	_CHECK_WATCHER (watcher, 0);
+
+	if (watcher->type != LINK_WATCHER_ETHTOOL)
+		return -1;
+	return watcher->ethtool.delay_down;
+}
+
+/**
+ * nm_team_link_watcher_get_init_wait:
+ * @watcher: the #NMTeamLinkWatcher
+ *
+ * Gets the init_wait interval (in milliseconds) that the team slave should
+ * wait before sending the first packet to the target host.
+ *
+ * Since: 1.10.2
+ **/
+int
+nm_team_link_watcher_get_init_wait (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;
+}
+
+/**
+ * nm_team_link_watcher_get_interval:
+ * @watcher: the #NMTeamLinkWatcher
+ *
+ * Gets the interval (in milliseconds) that the team slave should wait between
+ * sending two check packets to the target host.
+ *
+ * Since: 1.10.2
+ **/
+int
+nm_team_link_watcher_get_interval (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;
+}
+
+/**
+ * nm_team_link_watcher_get_missed_max:
+ * @watcher: the #NMTeamLinkWatcher
+ *
+ * Gets the number of missed replies after which the link is considered down.
+ *
+ * Since: 1.10.2
+ **/
+int
+nm_team_link_watcher_get_missed_max (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;
+}
+
+/**
+ * nm_team_link_watcher_get_target_host:
+ * @watcher: the #NMTeamLinkWatcher
+ *
+ * Gets the host name/ip address to be used as destination for the link probing
+ * packets.
+ *
+ * Since: 1.10.2
+ **/
+const char *
+nm_team_link_watcher_get_target_host (NMTeamLinkWatcher *watcher)
+{
+	_CHECK_WATCHER (watcher, NULL);
+
+	return watcher->arp_ping.target_host;
+}
+
+/**
+ * nm_team_link_watcher_get_source_host:
+ * @watcher: the #NMTeamLinkWatcher
+ *
+ * Gets the ip address to be used as source for the link probing packets.
+ *
+ * Since: 1.10.2
+ **/
+const char *
+nm_team_link_watcher_get_source_host (NMTeamLinkWatcher *watcher)
+{
+	_CHECK_WATCHER (watcher, NULL);
+
+	return watcher->arp_ping.source_host;
+}
+
+/**
+ * nm_team_link_watcher_get_flags:
+ * @watcher: the #NMTeamLinkWatcher
+ *
+ * Gets the arp ping watcher flags.
+ *
+ * Since: 1.10.2
+ **/
+NMTeamLinkWatcherArpPingFlags
+nm_team_link_watcher_get_flags (NMTeamLinkWatcher *watcher)
+{
+	_CHECK_WATCHER (watcher, 0);
+
+	return watcher->arp_ping.flags;
+}
+
+/*****************************************************************************/
+
 G_DEFINE_TYPE_WITH_CODE (NMSettingTeam, nm_setting_team, NM_TYPE_SETTING,
                          _nm_register_setting (TEAM, NM_SETTING_PRIORITY_HW_BASE))
 NM_SETTING_REGISTER_TYPE (NM_TYPE_SETTING_TEAM)
@@ -44,14 +565,67 @@ NM_SETTING_REGISTER_TYPE (NM_TYPE_SETTING_TEAM)
 
 typedef struct {
 	char *config;
+	gint notify_peers_count;
+	gint notify_peers_interval;
+	gint mcast_rejoin_count;
+	gint mcast_rejoin_interval;
+	char *runner;
+	char *runner_hwaddr_policy;
+	GPtrArray *runner_tx_hash;
+	char *runner_tx_balancer;
+	gint runner_tx_balancer_interval;
+	gboolean runner_active;
+	gboolean runner_fast_rate;
+	gint runner_sys_prio;
+	gint runner_min_ports;
+	char *runner_agg_select_policy;
+	GPtrArray *link_watchers; /* Array of NMTeamLinkWatcher */
 } NMSettingTeamPrivate;
 
+/* Keep aligned with _prop_to_keys[] */
 enum {
 	PROP_0,
 	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,
 	LAST_PROP
 };
 
+/* Keep aligned with team properties enum */
+static const _NMUtilsTeamPropertyKeys _prop_to_keys[LAST_PROP] = {
+	[PROP_0] =                           { NULL, NULL, NULL, 0 },
+	[PROP_CONFIG] =                      { NULL, NULL, NULL, 0 },
+	[PROP_NOTIFY_PEERS_COUNT] =          { "notify_peers", "count", NULL, 0 },
+	[PROP_NOTIFY_PEERS_INTERVAL] =       { "notify_peers", "interval", NULL, 0 },
+	[PROP_MCAST_REJOIN_COUNT] =          { "mcast_rejoin", "count", NULL, 0 },
+	[PROP_MCAST_REJOIN_INTERVAL] =       { "mcast_rejoin", "interval", NULL, 0 },
+	[PROP_RUNNER] =                      { "runner", "name", NULL,
+	                                       {.default_str = NM_SETTING_TEAM_RUNNER_DEFAULT} },
+	[PROP_RUNNER_HWADDR_POLICY] =        { "runner", "hwaddr_policy", NULL, 0 },
+	[PROP_RUNNER_TX_HASH] =              { "runner", "tx_hash", NULL, 0 },
+	[PROP_RUNNER_TX_BALANCER] =          { "runner", "tx_balancer", "name", 0 },
+	[PROP_RUNNER_TX_BALANCER_INTERVAL] = { "runner", "tx_balancer", "balancing_interval", -1 },
+	[PROP_RUNNER_ACTIVE] =               { "runner", "active", NULL, 0 },
+	[PROP_RUNNER_FAST_RATE] =            { "runner", "fast_rate", NULL, 0 },
+	[PROP_RUNNER_SYS_PRIO] =             { "runner", "sys_prio", NULL, -1 },
+	[PROP_RUNNER_MIN_PORTS] =            { "runner", "min_ports", NULL, -1 },
+	[PROP_RUNNER_AGG_SELECT_POLICY] =    { "runner", "agg_select_policy", NULL, 0 },
+	[PROP_LINK_WATCHERS] =               { "link_watch", NULL, NULL, 0 }
+};
+
 /**
  * nm_setting_team_new:
  *
@@ -79,10 +653,496 @@ nm_setting_team_get_config (NMSettingTeam *setting)
 	return NM_SETTING_TEAM_GET_PRIVATE (setting)->config;
 }
 
+/**
+ * nm_setting_team_get_notify_peers_count:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:notify-peers-count property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gint
+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;
+}
+
+/**
+ * nm_setting_team_get_notify_peers_interval:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:notify-peers-interval property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gint
+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;
+}
+
+/**
+ * nm_setting_team_get_mcast_rejoin_count:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:mcast-rejoin-count property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gint
+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;
+}
+
+/**
+ * nm_setting_team_get_mcast_rejoin_interval:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:mcast-rejoin-interval property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gint
+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;
+}
+
+/**
+ * nm_setting_team_get_runner:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:runner property of the setting
+ *
+ * Since: 1.10.2
+ **/
+const char *
+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;
+}
+
+/**
+ * nm_setting_team_get_runner_hwaddr_policy:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:runner-hwaddr-policy property of the setting
+ *
+ * Since: 1.10.2
+ **/
+const char *
+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;
+}
+
+/**
+ * nm_setting_team_get_runner_tx_balancer:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:runner-tx-balancer property of the setting
+ *
+ * Since: 1.10.2
+ **/
+const char *
+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;
+}
+
+/**
+ * nm_setting_team_get_runner_tx_balancer_interval:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:runner-tx-balancer_interval property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gint
+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;
+}
+
+/**
+ * nm_setting_team_get_runner_active:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:runner_active property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gboolean
+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;
+}
+
+/**
+ * nm_setting_team_get_runner_fast_rate:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:runner-fast-rate property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gboolean
+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;
+}
+
+/**
+ * nm_setting_team_get_runner_sys_prio:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:runner-sys-prio property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gint
+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;
+}
+
+/**
+ * nm_setting_team_get_runner_min_ports:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:runner-min-ports property of the setting
+ *
+ * Since: 1.10.2
+ **/
+gint
+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;
+}
+
+/**
+ * nm_setting_team_get_runner_agg_select_policy:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the ##NMSettingTeam:runner-agg-select-policy property of the setting
+ *
+ * Since: 1.10.2
+ **/
+const char *
+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;
+}
+
+/**
+ * nm_setting_team_remove_runner_tx_hash_by_value:
+ * @setting: the #NMSetetingTeam
+ * @txhash: the txhash element to remove
+ *
+ * Removes the txhash element #txhash
+ *
+ * Returns: %TRUE if the txhash element was found and removed; %FALSE if it was not.
+ *
+ * Since: 1.10.2
+ **/
+gboolean
+nm_setting_team_remove_runner_tx_hash_by_value (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);
+
+	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);
+			g_object_notify (G_OBJECT (setting), NM_SETTING_TEAM_RUNNER_TX_HASH);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+/**
+ * nm_setting_team_get_num_runner_tx_hash:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the number of elements in txhash
+ *
+ * Since: 1.10.2
+ **/
+guint
+nm_setting_team_get_num_runner_tx_hash (NMSettingTeam *setting)
+{
+	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+
+	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), 0);
+
+	return priv->runner_tx_hash ? priv->runner_tx_hash->len : 0;
+}
+
+/**
+ * nm_setting_team_get_runner_tx_hash
+ * @setting: the #NMSettingTeam
+ * @idx: index number of the txhash element to return
+ *
+ * Returns: the txhash element at index @idx
+ *
+ * Since: 1.10.2
+ **/
+const char *
+nm_setting_team_get_runner_tx_hash (NMSettingTeam *setting, guint idx)
+{
+	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+
+	g_return_val_if_fail (NM_IS_SETTING_TEAM (setting), NULL);
+	g_return_val_if_fail (idx < priv->runner_tx_hash->len, NULL);
+
+	return priv->runner_tx_hash->pdata[idx];
+}
+
+/**
+ * nm_setting_team_remove_runner_tx_hash:
+ * @setting: the #NMSettingTeam
+ * @idx: index number of the element to remove from txhash
+ *
+ * Removes the txhash element at index @idx.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_setting_team_remove_runner_tx_hash (NMSettingTeam *setting, guint idx)
+{
+	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+
+	g_return_if_fail (NM_IS_SETTING_TEAM (setting));
+	g_return_if_fail (idx < priv->runner_tx_hash->len);
+
+	g_ptr_array_remove_index (priv->runner_tx_hash, idx);
+	g_object_notify (G_OBJECT (setting), NM_SETTING_TEAM_RUNNER_TX_HASH);
+}
+
+/**
+ * nm_setting_team_add_runner_tx_hash:
+ * @setting: the #NMSettingTeam
+ * @txhash: the element to add to txhash
+ *
+ * Adds a new txhash element to the setting.
+ *
+ * Returns: %TRUE if the txhash element was added; %FALSE if the element
+ * was already knnown.
+ *
+ * Since: 1.10.2
+ **/
+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_ptr_array_add (priv->runner_tx_hash, g_strdup (txhash));
+	g_object_notify (G_OBJECT (setting), NM_SETTING_TEAM_RUNNER_TX_HASH);
+	return TRUE;
+}
+
+/**
+ * nm_setting_team_get_num_link_watchers:
+ * @setting: the #NMSettingTeam
+ *
+ * Returns: the number of configured link watchers
+ *
+ * Since: 1.10.2
+ **/
+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;
+}
+
+/**
+ * nm_setting_team_get_link_watcher:
+ * @setting: the #NMSettingTeam
+ * @idx: index number of the link watcher to return
+ *
+ * Returns: (transfer none): the link watcher at index @idx.
+ *
+ * Since: 1.10.2
+ **/
+NMTeamLinkWatcher *
+nm_setting_team_get_link_watcher (NMSettingTeam *setting, guint idx)
+{
+	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+
+	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];
+}
+
+/**
+ * nm_setting_team_add_link_watcher:
+ * @setting: the #NMSettingTeam
+ * @link_watcher: the link watcher to add
+ *
+ * Appends a new link watcher to the setting.
+ *
+ * Returns: %TRUE if the link watcher is added; %FALSE if an identical link
+ * watcher was already there.
+ *
+ * Since: 1.10.2
+ **/
+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));
+	g_object_notify (G_OBJECT (setting), NM_SETTING_TEAM_LINK_WATCHERS);
+	return TRUE;
+}
+
+/**
+ * nm_setting_team_remove_link_watcher:
+ * @setting: the #NMSettingTeam
+ * @idx: index number of the link watcher to remove
+ *
+ * Removes the link watcher at index #idx.
+ *
+ * Since: 1.10.2
+ **/
+void
+nm_setting_team_remove_link_watcher (NMSettingTeam *setting, guint idx)
+{
+	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+
+	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);
+	g_object_notify (G_OBJECT (setting), NM_SETTING_TEAM_LINK_WATCHERS);
+}
+
+/**
+ * nm_setting_team_remove_link_watcher_by_value:
+ * @setting: the #NMSettingTeam
+ * @link_watcher: the link watcher to remove
+ *
+ * Removes the link watcher entry matching link_watcher.
+ *
+ * Returns: %TRUE if the link watcher was found and removed, %FALSE otherwise.
+ *
+ * Since: 1.10.2
+ **/
+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);
+
+	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);
+			g_object_notify (G_OBJECT (setting), NM_SETTING_TEAM_LINK_WATCHERS);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+/**
+ * nm_setting_team_clear_link_watchers:
+ * @setting: the #NMSettingTeam
+ *
+ * Removes all configured link watchers.
+ *
+ * Since: 1.10.2
+ **/
+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));
+
+	g_ptr_array_set_size (priv->link_watchers, 0);
+	g_object_notify (G_OBJECT (setting), NM_SETTING_TEAM_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));
+}
+
 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;
@@ -108,6 +1168,61 @@ verify (NMSetting *setting, NMConnection *connection, GError **error)
 		}
 	}
 
+	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_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);
+		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. */
 
@@ -120,7 +1235,9 @@ compare_property (NMSetting *setting,
                   const GParamSpec *prop_spec,
                   NMSettingCompareFlags flags)
 {
+	NMSettingTeamPrivate *a_priv, *b_priv;
 	NMSettingClass *parent_class;
+	guint i, j;
 
 	/* If we are trying to match a connection in order to assume it (and thus
 	 * @flags contains INFERRABLE), use the "relaxed" matching for team
@@ -133,6 +1250,24 @@ compare_property (NMSetting *setting,
 		                                    NM_SETTING_TEAM_GET_PRIVATE (other)->config,
 		                                    FALSE);
 	}
+	if (nm_streq0 (prop_spec->name, NM_SETTING_TEAM_LINK_WATCHERS)) {
+		a_priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+		b_priv = NM_SETTING_TEAM_GET_PRIVATE (other);
+
+		if (a_priv->link_watchers->len != b_priv->link_watchers->len)
+			return FALSE;
+		for (i = 0; i < a_priv->link_watchers->len; i++) {
+			for (j = 0; j < b_priv->link_watchers->len; j++) {
+				if (nm_team_link_watcher_equal (a_priv->link_watchers->pdata[i],
+				                                b_priv->link_watchers->pdata[j])) {
+					break;
+				}
+				if (j == b_priv->link_watchers->len)
+					return FALSE;
+			}
+		}
+		return TRUE;
+	}
 
 	/* Otherwise chain up to parent to handle generic compare */
 	parent_class = NM_SETTING_CLASS (nm_setting_team_parent_class);
@@ -142,6 +1277,13 @@ compare_property (NMSetting *setting,
 static void
 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);
 }
 
 static void
@@ -150,25 +1292,201 @@ 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);
 
 	G_OBJECT_CLASS (nm_setting_team_parent_class)->finalize (object);
 }
 
+
+#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)
+{
+	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
+	char **strv;
+	int 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);
+
+	if (priv->runner_tx_hash) {
+		g_ptr_array_unref (priv->runner_tx_hash);
+		priv->runner_tx_hash = NULL;
+	}
+	strv = JSON_TO_VAL (strv, PROP_RUNNER_TX_HASH);
+	if (strv) {
+		for (i = 0; strv[i]; i++)
+			nm_setting_team_add_runner_tx_hash (setting, strv[i]);
+		g_strfreev (strv);
+	}
+
+	g_ptr_array_unref (priv->link_watchers);
+	priv->link_watchers = JSON_TO_VAL (ptr_array, PROP_LINK_WATCHERS);
+}
+
 static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
+	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;
 
 	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;
+		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;
+		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;
+		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;
+		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;
+		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;
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
 	}
+
+	if (align_config)
+		_nm_utils_json_append_gvalue (&priv->config, _prop_to_keys[prop_id], align_value);
 }
 
 static void
@@ -176,11 +1494,60 @@ get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
 	NMSettingTeam *setting = NM_SETTING_TEAM (object);
+	NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE (setting);
 
 	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);
+		break;
+	case PROP_NOTIFY_PEERS_INTERVAL:
+		g_value_set_int (value, priv->notify_peers_interval);
+		break;
+	case PROP_MCAST_REJOIN_COUNT:
+		g_value_set_int (value, priv->mcast_rejoin_count);
+		break;
+	case PROP_MCAST_REJOIN_INTERVAL:
+		g_value_set_int (value, priv->mcast_rejoin_interval);
+		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:
+		g_value_take_boxed (value, priv->runner_tx_hash ?
+		                    _nm_utils_ptrarray_to_strv (priv->runner_tx_hash): 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,
+		                                                 (GDestroyNotify) nm_team_link_watcher_unref));
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -225,6 +1592,232 @@ nm_setting_team_class_init (NMSettingTeamClass *setting_class)
 		                      NM_SETTING_PARAM_INFERRABLE |
 		                      G_PARAM_STATIC_STRINGS));
 
+	/**
+	 * NMSettingTeam:notify-peers-count:
+	 *
+	 * Corresponds to the teamd notify_peers.count.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_NOTIFY_PEERS_COUNT,
+		 g_param_spec_int (NM_SETTING_TEAM_NOTIFY_PEERS_COUNT, "", "",
+		                   G_MININT32, G_MAXINT32, 0,
+		                   G_PARAM_READWRITE |
+		                   G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:notify-peers-interval:
+	 *
+	 * Corresponds to the teamd notify_peers.interval.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_NOTIFY_PEERS_INTERVAL,
+		 g_param_spec_int (NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL, "", "",
+		                   G_MININT32, G_MAXINT32, 0,
+		                   G_PARAM_READWRITE |
+		                   G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:mcast-rejoin-count:
+	 *
+	 * Corresponds to the teamd mcast_rejoin.count.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_MCAST_REJOIN_COUNT,
+		 g_param_spec_int (NM_SETTING_TEAM_MCAST_REJOIN_COUNT, "", "",
+		                   G_MININT32, G_MAXINT32, 0,
+		                   G_PARAM_READWRITE |
+		                   G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:mcast-rejoin-interval:
+	 *
+	 * Corresponds to the teamd mcast_rejoin.interval.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_MCAST_REJOIN_INTERVAL,
+		 g_param_spec_int (NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL, "", "",
+		                   G_MININT32, G_MAXINT32, 0,
+		                   G_PARAM_READWRITE |
+		                   G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:runner:
+	 *
+	 * Corresponds to the teamd runner.name.
+	 * Permitted values are: "roundrobin", "broadcast", "activebackup",
+	 * "loadbalance", "lacp".
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_RUNNER,
+		 g_param_spec_string (NM_SETTING_TEAM_RUNNER, "", "",
+		                      NULL,
+		                      G_PARAM_READWRITE |
+		                      G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:runner-hwaddr-policy:
+	 *
+	 * Corresponds to the teamd runner.hwaddr_policy.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_RUNNER_HWADDR_POLICY,
+		 g_param_spec_string (NM_SETTING_TEAM_RUNNER_HWADDR_POLICY, "", "",
+		                      NULL,
+		                      G_PARAM_READWRITE |
+		                      G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:runner-tx-hash:
+	 *
+	 * Corresponds to the teamd runner.tx_hash.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_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));
+
+	/**
+	 * NMSettingTeam:runner-tx-balancer:
+	 *
+	 * Corresponds to the teamd runner.tx_balancer.name.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_RUNNER_TX_BALANCER,
+		 g_param_spec_string (NM_SETTING_TEAM_RUNNER_TX_BALANCER, "", "",
+		                      NULL,
+		                      G_PARAM_READWRITE |
+		                      G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:runner-tx-balancer-interval:
+	 *
+	 * Corresponds to the teamd runner.tx_balancer.interval.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_RUNNER_TX_BALANCER_INTERVAL,
+		 g_param_spec_int (NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL, "", "",
+		                   G_MININT32, G_MAXINT32, 0,
+		                   G_PARAM_READWRITE |
+		                   G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:runner-active:
+	 *
+	 * Corresponds to the teamd runner.active.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_RUNNER_ACTIVE,
+		 g_param_spec_boolean (NM_SETTING_TEAM_RUNNER_ACTIVE, "", "",
+		                       FALSE,
+		                       G_PARAM_READWRITE |
+		                       G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:runner-fast-rate:
+	 *
+	 * Corresponds to the teamd runner.fast_rate.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_RUNNER_FAST_RATE,
+		 g_param_spec_boolean (NM_SETTING_TEAM_RUNNER_FAST_RATE, "", "",
+		                       FALSE,
+		                       G_PARAM_READWRITE |
+		                       G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:runner-sys-prio:
+	 *
+	 * Corresponds to the teamd runner.sys_prio.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_RUNNER_SYS_PRIO,
+		 g_param_spec_int (NM_SETTING_TEAM_RUNNER_SYS_PRIO, "", "",
+		                   G_MININT32, G_MAXINT32, 0,
+		                   G_PARAM_READWRITE |
+		                   G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:runner-min-ports:
+	 *
+	 * Corresponds to the teamd runner.min_ports.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_RUNNER_MIN_PORTS,
+		 g_param_spec_int (NM_SETTING_TEAM_RUNNER_MIN_PORTS, "", "",
+		                   G_MININT32, G_MAXINT32, 0,
+		                   G_PARAM_READWRITE |
+		                   G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:runner-agg-select-policy:
+	 *
+	 * Corresponds to the teamd runner.agg_select_policy.
+	 *
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_RUNNER_AGG_SELECT_POLICY,
+		 g_param_spec_string (NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY, "", "",
+		                      NULL,
+		                      G_PARAM_READWRITE |
+		                      G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * NMSettingTeam:link-watchers:
+	 *
+	 * Link watchers configuration for the connection: each link watcher is
+	 * defined by a dictionary, whose keys depend upon the selected link
+	 * watcher. Available link watchers are 'ethtool', 'nsna_ping' and
+	 * 'arp_ping' and it is specified in the dictionary with the key 'name'.
+	 * Available keys are:   ethtool: 'delay-up', 'delay-down', 'init-wait';
+	 * nsna_ping: 'init-wait', 'interval', 'missed-max', 'target-host';
+	 * arp_ping: all the ones in nsna_ping and 'source-host', 'validate-active',
+	 * 'validate-incative', 'send-always'. See teamd.conf man for more details.
+	 *
+	 * Element-Type: NMTeamLinkWatcher
+	 * Since: 1.10.2
+	 **/
+	g_object_class_install_property
+		(object_class, PROP_LINK_WATCHERS,
+		 g_param_spec_boxed (NM_SETTING_TEAM_LINK_WATCHERS, "", "",
+		                     G_TYPE_PTR_ARRAY,
+		                     G_PARAM_READWRITE |
+		                     G_PARAM_STATIC_STRINGS));
+	_nm_setting_class_transform_property (parent_class,
+	                                     NM_SETTING_TEAM_LINK_WATCHERS,
+	                                     G_VARIANT_TYPE ("aa{sv}"),
+	                                     team_link_watchers_to_dbus,
+	                                     team_link_watchers_from_dbus);
+
 	/* ---dbus---
 	 * property: interface-name
 	 * format: string
diff --git a/libnm-core/nm-setting-team.h b/libnm-core/nm-setting-team.h
index 532963aa..45207836 100644
--- a/libnm-core/nm-setting-team.h
+++ b/libnm-core/nm-setting-team.h
@@ -29,6 +29,80 @@
 
 G_BEGIN_DECLS
 
+/**
+ * NMTeamLinkWatcherArpPingFlags:
+ * @NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE: no one among the arp_ping link watcher
+ *    boolean options ('validate_active', 'validate_inactive', 'send_always') is
+ *    enabled (set to true).
+ * @NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE: the arp_ping link watcher
+ *    option 'validate_active' is enabled (set to true).
+ * @NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE: the arp_ping link watcher
+ *    option 'validate_inactive' is enabled (set to true).
+ * @NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS: the arp_ping link watcher option
+ *    'send_always' is enabled (set to true).
+ */
+typedef enum { /*< flags >*/
+	NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE              = 0, /*< skip >*/
+	NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE   = (1 << 1),
+	NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE = (1 << 2),
+	NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS       = (1 << 3)
+} NMTeamLinkWatcherArpPingFlags;
+
+#define NM_TEAM_LINK_WATCHER_ETHTOOL   "ethtool"
+#define NM_TEAM_LINK_WATCHER_ARP_PING  "arp_ping"
+#define NM_TEAM_LINK_WATCHER_NSNA_PING "nsna_ping"
+
+
+typedef struct NMTeamLinkWatcher NMTeamLinkWatcher;
+
+GType nm_team_link_watcher_get_type              (void);
+
+NM_AVAILABLE_IN_1_10_2
+NMTeamLinkWatcher *nm_team_link_watcher_new_ethtool (gint delay_up,
+                                                     gint delay_down,
+                                                     GError **error);
+NM_AVAILABLE_IN_1_10_2
+NMTeamLinkWatcher *nm_team_link_watcher_new_nsna_ping (gint init_wait,
+                                                       gint interval,
+                                                       gint missed_max,
+                                                       const char *target_host,
+                                                       GError **error);
+NM_AVAILABLE_IN_1_10_2
+NMTeamLinkWatcher *nm_team_link_watcher_new_arp_ping (gint init_wait,
+                                                      gint interval,
+                                                      gint missed_max,
+                                                      const char *target_host,
+                                                      const char *source_host,
+                                                      NMTeamLinkWatcherArpPingFlags flags,
+                                                      GError **error);
+NM_AVAILABLE_IN_1_10_2
+void nm_team_link_watcher_ref                    (NMTeamLinkWatcher *watcher);
+NM_AVAILABLE_IN_1_10_2
+void nm_team_link_watcher_unref                  (NMTeamLinkWatcher *watcher);
+NM_AVAILABLE_IN_1_10_2
+gboolean nm_team_link_watcher_equal              (NMTeamLinkWatcher *watcher, NMTeamLinkWatcher *other);
+NM_AVAILABLE_IN_1_10_2
+NMTeamLinkWatcher *nm_team_link_watcher_dup      (NMTeamLinkWatcher *watcher);
+NM_AVAILABLE_IN_1_10_2
+const char *nm_team_link_watcher_get_name        (NMTeamLinkWatcher *watcher);
+NM_AVAILABLE_IN_1_10_2
+int nm_team_link_watcher_get_delay_up            (NMTeamLinkWatcher *watcher);
+NM_AVAILABLE_IN_1_10_2
+int nm_team_link_watcher_get_delay_down          (NMTeamLinkWatcher *watcher);
+NM_AVAILABLE_IN_1_10_2
+int nm_team_link_watcher_get_init_wait           (NMTeamLinkWatcher *watcher);
+NM_AVAILABLE_IN_1_10_2
+int nm_team_link_watcher_get_interval            (NMTeamLinkWatcher *watcher);
+NM_AVAILABLE_IN_1_10_2
+int nm_team_link_watcher_get_missed_max          (NMTeamLinkWatcher *watcher);
+NM_AVAILABLE_IN_1_10_2
+const char *nm_team_link_watcher_get_target_host (NMTeamLinkWatcher *watcher);
+NM_AVAILABLE_IN_1_10_2
+const char *nm_team_link_watcher_get_source_host (NMTeamLinkWatcher *watcher);
+NM_AVAILABLE_IN_1_10_2
+NMTeamLinkWatcherArpPingFlags nm_team_link_watcher_get_flags (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))
 #define NM_SETTING_TEAM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTING_TEAM, NMSettingTeamClass))
@@ -38,7 +112,47 @@ G_BEGIN_DECLS
 
 #define NM_SETTING_TEAM_SETTING_NAME "team"
 
-#define NM_SETTING_TEAM_CONFIG "config"
+#define NM_SETTING_TEAM_CONFIG                      "config"
+#define NM_SETTING_TEAM_NOTIFY_PEERS_COUNT          "notify-peers-count"
+#define NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL       "notify-peers-interval"
+#define NM_SETTING_TEAM_MCAST_REJOIN_COUNT          "mcast-rejoin-count"
+#define NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL       "mcast-rejoin-interval"
+#define NM_SETTING_TEAM_RUNNER                      "runner"
+#define NM_SETTING_TEAM_RUNNER_HWADDR_POLICY        "runner-hwaddr-policy"
+#define NM_SETTING_TEAM_RUNNER_TX_HASH              "runner-tx-hash"
+#define NM_SETTING_TEAM_RUNNER_TX_BALANCER          "runner-tx-balancer"
+#define NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL "runner-tx-balancer-interval"
+#define NM_SETTING_TEAM_RUNNER_ACTIVE               "runner-active"
+#define NM_SETTING_TEAM_RUNNER_FAST_RATE            "runner-fast-rate"
+#define NM_SETTING_TEAM_RUNNER_SYS_PRIO             "runner-sys-prio"
+#define NM_SETTING_TEAM_RUNNER_MIN_PORTS            "runner-min-ports"
+#define NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY    "runner-agg-select-policy"
+#define NM_SETTING_TEAM_LINK_WATCHERS               "link-watchers"
+
+#define NM_SETTING_TEAM_RUNNER_BROADCAST    "broadcast"
+#define NM_SETTING_TEAM_RUNNER_ROUNDROBIN   "roundrobin"
+#define NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP "activebackup"
+#define NM_SETTING_TEAM_RUNNER_LOADBALANCE  "loadbalance"
+#define NM_SETTING_TEAM_RUNNER_LACP         "lacp"
+
+#define NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_SAME_ALL    "same_all"
+#define NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_BY_ACTIVE   "by_active"
+#define NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_ONLY_ACTIVE "only_active"
+
+#define NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY_LACP_PRIO        "lacp_prio"
+#define NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY_LACP_PRIO_STABLE "lacp_prio_stable"
+#define NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY_BANDWIDTH        "bandwidth"
+#define NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY_COUNT            "count"
+#define NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY_PORT_CONFIG      "port_config"
+
+#define NM_SETTING_TEAM_NOTIFY_PEERS_COUNT_ACTIVEBACKUP_DEFAULT 1
+#define NM_SETTING_TEAM_NOTIFY_MCAST_COUNT_ACTIVEBACKUP_DEFAULT 1
+#define NM_SETTING_TEAM_RUNNER_DEFAULT                      NM_SETTING_TEAM_RUNNER_ROUNDROBIN
+#define NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_DEFAULT        NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_SAME_ALL
+#define NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL_DEFAULT 50
+#define NM_SETTING_TEAM_RUNNER_SYS_PRIO_DEFAULT             255
+#define NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY_DEFAULT    NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY_LACP_PRIO
+
 
 /**
  * NMSettingTeam:
@@ -61,7 +175,56 @@ GType nm_setting_team_get_type (void);
 NMSetting *  nm_setting_team_new                (void);
 
 const char * nm_setting_team_get_config (NMSettingTeam *setting);
-
+NM_AVAILABLE_IN_1_10_2
+gint nm_setting_team_get_notify_peers_count (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+gint nm_setting_team_get_notify_peers_interval (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+gint nm_setting_team_get_mcast_rejoin_count (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+gint nm_setting_team_get_mcast_rejoin_interval (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+const char * nm_setting_team_get_runner (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+const char * nm_setting_team_get_runner_hwaddr_policy (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+const char * nm_setting_team_get_runner_tx_balancer (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+gint nm_setting_team_get_runner_tx_balancer_interval (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+gboolean nm_setting_team_get_runner_active (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+gboolean nm_setting_team_get_runner_fast_rate (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+gint nm_setting_team_get_runner_sys_prio (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+gint nm_setting_team_get_runner_min_ports (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+const char * nm_setting_team_get_runner_agg_select_policy (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+gboolean nm_setting_team_remove_runner_tx_hash_by_value (NMSettingTeam *setting, const char *txhash);
+NM_AVAILABLE_IN_1_10_2
+guint nm_setting_team_get_num_runner_tx_hash (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+const char *nm_setting_team_get_runner_tx_hash (NMSettingTeam *setting, guint idx);
+NM_AVAILABLE_IN_1_10_2
+void nm_setting_team_remove_runner_tx_hash (NMSettingTeam *setting, guint idx);
+NM_AVAILABLE_IN_1_10_2
+gboolean nm_setting_team_add_runner_tx_hash (NMSettingTeam *setting, const char *txhash);
+NM_AVAILABLE_IN_1_10_2
+guint nm_setting_team_get_num_link_watchers (NMSettingTeam *setting);
+NM_AVAILABLE_IN_1_10_2
+NMTeamLinkWatcher * nm_setting_team_get_link_watcher (NMSettingTeam *setting, guint idx);
+NM_AVAILABLE_IN_1_10_2
+gboolean nm_setting_team_add_link_watcher (NMSettingTeam *setting,
+                                           NMTeamLinkWatcher *link_watcher);
+NM_AVAILABLE_IN_1_10_2
+void nm_setting_team_remove_link_watcher (NMSettingTeam *setting, guint idx);
+NM_AVAILABLE_IN_1_10_2
+gboolean nm_setting_team_remove_link_watcher_by_value (NMSettingTeam *setting,
+                                                       NMTeamLinkWatcher *link_watcher);
+NM_AVAILABLE_IN_1_10_2
+void nm_setting_team_clear_link_watchers (NMSettingTeam *setting);
 G_END_DECLS
 
 #endif /* __NM_SETTING_TEAM_H__ */
diff --git a/libnm-core/nm-setting-wireless-security.c b/libnm-core/nm-setting-wireless-security.c
index 91bcbe7a..de77a493 100644
--- a/libnm-core/nm-setting-wireless-security.c
+++ b/libnm-core/nm-setting-wireless-security.c
@@ -1814,10 +1814,11 @@ nm_setting_wireless_security_class_init (NMSettingWirelessSecurityClass *setting
 	 **/
 	/* ---ifcfg-rh---
 	 * property: wep-key-type
-	 * variable: KEY<i> or KEY_PASSPHRASE<i>(+)
+	 * variable: KEY<i> or KEY_PASSPHRASE<i>(+); KEY_TYPE(+)
 	 * description: KEY is used for "key" type (10 or 26 hexadecimal characters,
 	 *   or 5 or 13 character string prefixed with "s:"). KEY_PASSPHRASE is used
-	 *   for WEP passphrases.
+	 *   for WEP passphrases. KEY_TYPE specifies the key type and can be either
+	 *   'key' or 'passphrase'. KEY_TYPE is redundant and can be omitted.
 	 * example: KEY1=s:ahoj, KEY1=0a1c45bc02, KEY_PASSPHRASE1=mysupersecretkey
 	 * ---end---
 	 */
diff --git a/libnm-core/nm-utils-private.h b/libnm-core/nm-utils-private.h
index 03c87907..c76b3200 100644
--- a/libnm-core/nm-utils-private.h
+++ b/libnm-core/nm-utils-private.h
@@ -15,7 +15,7 @@
  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301 USA.
  *
- * Copyright 2005 - 2014 Red Hat, Inc.
+ * Copyright 2005 - 2017 Red Hat, Inc.
  */
 
 #ifndef __NM_UTILS_PRIVATE_H__
@@ -33,6 +33,8 @@ struct _NMVariantAttributeSpec {
 	const GVariantType *type;
 	bool v4:1;
 	bool v6:1;
+	bool no_value:1;
+	bool consumes_rest:1;
 	char str_type;
 };
 
@@ -78,4 +80,120 @@ 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);
+
+/* JSON to GValue conversion macros */
+
+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)
+{
+	gs_free GValue *t_value = NULL;
+	int ret;
+
+	t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port);
+	if (!t_value)
+		return key.default_int;
+
+	ret = g_value_get_int (t_value);
+	g_value_unset (t_value);
+	return ret;
+}
+
+static inline gboolean
+_nm_utils_json_extract_boolean (char *conf,
+                                _NMUtilsTeamPropertyKeys key,
+                                gboolean is_port)
+{
+	gs_free GValue *t_value = NULL;
+	gboolean ret;
+
+	t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port);
+	if (!t_value)
+		return key.default_bool;
+
+	ret = g_value_get_boolean (t_value);
+	g_value_unset (t_value);
+	return ret;
+}
+
+static inline char *
+_nm_utils_json_extract_string (char *conf,
+                               _NMUtilsTeamPropertyKeys key,
+                               gboolean is_port)
+{
+	gs_free GValue *t_value = NULL;
+	char *ret;
+
+	t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port);
+	if (!t_value)
+		return g_strdup (key.default_str);
+
+	ret = g_value_dup_string (t_value);
+	g_value_unset (t_value);
+	return ret;
+}
+
+static inline char **
+_nm_utils_json_extract_strv (char *conf,
+                             _NMUtilsTeamPropertyKeys key,
+                             gboolean is_port)
+{
+	gs_free GValue *t_value = NULL;
+	char **ret;
+
+	t_value = _nm_utils_team_config_get (conf, key.key1, key.key2, key.key3, is_port);
+	if (!t_value)
+		return NULL;
+
+	ret = g_strdupv (g_value_get_boxed (t_value));
+	g_value_unset (t_value);
+	return ret;
+}
+
+static inline GPtrArray *
+_nm_utils_json_extract_ptr_array (char *conf,
+                             _NMUtilsTeamPropertyKeys key,
+                             gboolean is_port)
+{
+	gs_free 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)
+		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]));
+	g_value_unset (t_value);
+	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 bc955834..6cc92ec8 100644
--- a/libnm-core/nm-utils.c
+++ b/libnm-core/nm-utils.c
@@ -16,7 +16,7 @@
  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301 USA.
  *
- * Copyright 2005 - 2014 Red Hat, Inc.
+ * Copyright 2005 - 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -33,6 +33,7 @@
 #include <gmodule.h>
 #include <sys/stat.h>
 #include <net/if.h>
+#include <linux/pkt_sched.h>
 
 #if WITH_JANSSON
 #include <jansson.h>
@@ -494,17 +495,45 @@ _nm_utils_strdict_to_dbus (const GValue *prop_value)
 {
 	GHashTable *hash;
 	GHashTableIter iter;
-	gpointer key, value;
+	const char *key, *value;
 	GVariantBuilder builder;
+	guint i, len;
 
 	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
+
 	hash = g_value_get_boxed (prop_value);
-	if (hash) {
-		g_hash_table_iter_init (&iter, hash);
-		while (g_hash_table_iter_next (&iter, &key, &value))
-			g_variant_builder_add (&builder, "{ss}", key, value);
+	if (!hash)
+		goto out;
+	len = g_hash_table_size (hash);
+	if (!len)
+		goto out;
+
+	g_hash_table_iter_init (&iter, hash);
+	if (!g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value))
+		nm_assert_not_reached ();
+
+	if (len == 1)
+		g_variant_builder_add (&builder, "{ss}", key, value);
+	else {
+		gs_free NMUtilsNamedValue *idx = NULL;
+
+		idx = g_new (NMUtilsNamedValue, len);
+		i = 0;
+		do {
+			idx[i].name = key;
+			idx[i].value_str = value;
+			i++;
+		} while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &value));
+		nm_assert (i == len);
+
+		g_qsort_with_data (idx, len, sizeof (idx[0]),
+		                   nm_utils_named_entry_cmp_with_data, NULL);
+
+		for (i = 0; i < len; i++)
+			g_variant_builder_add (&builder, "{ss}", idx[i].name, idx[i].value_str);
 	}
 
+out:
 	return g_variant_builder_end (&builder);
 }
 
@@ -822,41 +851,6 @@ _nm_utils_strv_equal (char **strv1, char **strv2)
 	return !*strv1 && !*strv2;
 }
 
-/**
- * _nm_utils_strsplit_set:
- * @str: string to split
- * @delimiters: string of delimiter characters
- * @max_tokens: the maximum number of tokens to split string into. When it is
- * less than 1, the @str is split completely.
- *
- * Utility function for splitting string into a string array. It is a wrapper
- * for g_strsplit_set(), but it also removes empty strings from the vector as
- * they are not useful in most cases.
- *
- * Returns: (transfer full): a newly allocated NULL-terminated array of strings.
- * The caller must free the returned array with g_strfreev().
- **/
-char **
-_nm_utils_strsplit_set (const char *str, const char *delimiters, int max_tokens)
-{
-	char **result;
-	uint i;
-	uint j;
-
-	result = g_strsplit_set (str, delimiters, max_tokens);
-
-	/* remove empty strings */
-	for (i = 0; result && result[i]; i++) {
-		if (*result[i] == '\0') {
-			g_free (result[i]);
-			for (j = i; result[j]; j++)
-				result[j] = result[j + 1];
-			i--;
-		}
-	}
-	return result;
-}
-
 static gboolean
 device_supports_ap_ciphers (guint32 dev_caps,
                             guint32 ap_flags,
@@ -2070,6 +2064,550 @@ next:
 
 /*****************************************************************************/
 
+static void
+_string_append_tc_handle (GString *string, guint32 handle)
+{
+	g_string_append_printf (string, "%x:", TC_H_MAJ (handle) >> 16);
+	if (TC_H_MIN (handle) != TC_H_UNSPEC)
+		g_string_append_printf (string, "%x", TC_H_MIN (handle));
+}
+
+/**
+ * _nm_utils_string_append_tc_parent:
+ * @string: the string to write the parent handle to
+ * @prefix: optional prefix for the numeric handle
+ * @parent: the parent handle
+ *
+ * This is used to either write out the parent handle to the tc qdisc string
+ * or to pretty-format (use symbolic name for root) the key in keyfile.
+ * The presence of prefix determnines which one is the case.
+ *
+ * Private API due to general uglyness and overall uselessness for anything
+ * sensible.
+ */
+void
+_nm_utils_string_append_tc_parent (GString *string, const char *prefix, guint32 parent)
+{
+	if (parent == TC_H_ROOT) {
+		g_string_append (string, "root");
+	} else {
+		if (prefix) {
+			if (parent == TC_H_INGRESS)
+				return;
+			g_string_append_printf (string, "%s ", prefix);
+		}
+		_string_append_tc_handle (string, parent);
+	}
+
+	if (prefix)
+		g_string_append_c (string, ' ');
+}
+
+/**
+ * _nm_utils_parse_tc_handle:
+ * @str: the string representation of a qdisc handle
+ * @error: location of the error
+ *
+ * Parses tc style handle number into a numeric representation.
+ * Don't use this, use nm_utils_tc_qdisc_from_str() instead.
+ */
+guint32
+_nm_utils_parse_tc_handle (const char *str, GError **error)
+{
+	gint64 maj, min;
+	char *sep;
+
+	maj = g_ascii_strtoll (str, &sep, 0x10);
+	if (*sep == ':')
+		min = g_ascii_strtoll (&sep[1], &sep, 0x10);
+	else
+		min = 0;
+
+	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;
+	}
+
+	return TC_H_MAKE (maj << 16, min);
+}
+
+#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[] = {
+	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' ),
+	NULL,
+};
+
+/*****************************************************************************/
+
+/**
+ * _nm_utils_string_append_tc_qdisc_rest:
+ * @string: the string to write the formatted qdisc to
+ * @qdisc: the %NMTCQdisc
+ *
+ * This formats the rest of the qdisc string but the parent. Useful to format
+ * the keyfile value and nowhere else.
+ * Use nm_utils_tc_qdisc_to_str() that also includes the parent instead.
+ */
+void
+_nm_utils_string_append_tc_qdisc_rest (GString *string, NMTCQdisc *qdisc)
+{
+	guint32 handle = nm_tc_qdisc_get_handle (qdisc);
+	const char *kind = nm_tc_qdisc_get_kind (qdisc);
+
+	if (handle != TC_H_UNSPEC && strcmp (kind, "ingress") != 0) {
+		g_string_append (string, "handle ");
+		_string_append_tc_handle (string, handle);
+		g_string_append_c (string, ' ');
+	}
+
+	g_string_append (string, kind);
+}
+
+/**
+ * nm_utils_tc_qdisc_to_str:
+ * @qdisc: the %NMTCQdisc
+ * @error: location of the error
+ *
+ * Turns the %NMTCQdisc into a tc style string representation of the queueing
+ * discipline.
+ *
+ * Returns: formatted string or %NULL
+ *
+ * Since: 1.10.2
+ */
+char *
+nm_utils_tc_qdisc_to_str (NMTCQdisc *qdisc, GError **error)
+{
+	GString *string;
+
+	string = g_string_sized_new (60);
+
+	_nm_utils_string_append_tc_parent (string, "parent",
+	                                   nm_tc_qdisc_get_parent (qdisc));
+	_nm_utils_string_append_tc_qdisc_rest (string, qdisc);
+
+	return g_string_free (string, FALSE);
+}
+
+
+static gboolean
+_tc_read_common_opts (const char *str,
+                      guint32 *handle,
+                      guint32 *parent,
+                      char **kind,
+                      char **rest,
+                      GError **error)
+{
+	gs_unref_hashtable GHashTable *ht = NULL;
+	GVariant *variant;
+
+        ht = nm_utils_parse_variant_attributes (str,
+                                                ' ', ' ', FALSE,
+                                                tc_object_attribute_spec,
+                                                error);
+	if (!ht)
+		return FALSE;
+
+	if (g_hash_table_contains (ht, "root"))
+		*parent = TC_H_ROOT;
+
+	variant = g_hash_table_lookup (ht, "parent");
+	if (variant) {
+		if (*parent != TC_H_UNSPEC) {
+			g_set_error (error, 1, 0,
+			             _("'%s' unexpected: parent already specified."),
+			             g_variant_get_string (variant, NULL));
+			return FALSE;
+		}
+		*parent = _nm_utils_parse_tc_handle (g_variant_get_string (variant, NULL), error);
+		if (*parent == TC_H_UNSPEC)
+			return FALSE;
+	}
+
+	variant = g_hash_table_lookup (ht, "handle");
+	if (variant) {
+		*handle = _nm_utils_parse_tc_handle (g_variant_get_string (variant, NULL), error);
+		if (*handle == TC_H_UNSPEC)
+			return FALSE;
+		if (TC_H_MIN (*handle)) {
+			g_set_error (error, 1, 0,
+			             _("invalid handle: '%s'"),
+			             g_variant_get_string (variant, NULL));
+			return FALSE;
+		}
+	}
+
+	variant = g_hash_table_lookup (ht, "kind");
+	if (variant) {
+		*kind = g_variant_dup_string (variant, NULL);
+		if (strcmp (*kind, "ingress") == 0) {
+			if (*parent == TC_H_UNSPEC)
+				*parent = TC_H_INGRESS;
+			if (*handle == TC_H_UNSPEC)
+				*handle = TC_H_MAKE (TC_H_INGRESS, 0);
+		}
+	}
+
+	if (*parent == TC_H_UNSPEC) {
+		if (*kind) {
+			g_free (*kind);
+			*kind = NULL;
+		}
+		g_set_error_literal (error, 1, 0, _("parent not specified."));
+		return FALSE;
+	}
+
+	variant = g_hash_table_lookup (ht, "");
+	if (variant)
+		*rest = g_variant_dup_string (variant, NULL);
+
+	return TRUE;
+}
+
+/**
+ * nm_utils_tc_qdisc_from_str:
+ * @str: the string representation of a qdisc
+ * @error: location of the error
+ *
+ * Parces the tc style string qdisc representation of the queueing
+ * discipline to a %NMTCQdisc instance. Supports a subset of the tc language.
+ *
+ * Returns: the %NMTCQdisc or %NULL
+ *
+ * Since: 1.10.2
+ */
+NMTCQdisc *
+nm_utils_tc_qdisc_from_str (const char *str, GError **error)
+{
+	guint32 handle = TC_H_UNSPEC;
+	guint32 parent = TC_H_UNSPEC;
+	gs_free char *kind = NULL;
+	gs_free char *rest = NULL;
+	NMTCQdisc *qdisc = NULL;
+	gs_unref_hashtable GHashTable *ht = NULL;
+
+	nm_assert (str);
+	nm_assert (!error || !*error);
+
+	ht = nm_utils_parse_variant_attributes (str,
+	                                        ' ', ' ', FALSE,
+	                                        tc_object_attribute_spec,
+	                                        error);
+	if (!ht)
+		return NULL;
+
+	if (!_tc_read_common_opts (str, &handle, &parent, &kind, &rest, error))
+		return NULL;
+
+	if (rest) {
+		g_set_error (error, 1, 0, _("unsupported qdisc option: '%s'."), rest);
+		return NULL;
+	}
+
+	qdisc = nm_tc_qdisc_new (kind, parent, error);
+	if (!qdisc)
+		return NULL;
+
+	nm_tc_qdisc_set_handle (qdisc, handle);
+
+	return qdisc;
+}
+/*****************************************************************************/
+
+static const NMVariantAttributeSpec * const tc_action_simple_attribute_spec[] = {
+	TC_ATTR_SPEC_PTR ("sdata",   G_VARIANT_TYPE_BYTESTRING,  FALSE, FALSE, 0   ),
+	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' ),
+	NULL,
+};
+
+static gboolean
+_string_append_tc_action (GString *string, NMTCAction *action, GError **error)
+{
+	gs_unref_hashtable GHashTable *ht = NULL;
+	const char *kind = nm_tc_action_get_kind (action);
+	gs_strfreev char **attr_names = NULL;
+	gs_free char *str = NULL;
+	int i;
+
+	ht = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, NULL);
+
+	g_string_append (string, kind);
+
+	attr_names = nm_tc_action_get_attribute_names (action);
+	for (i = 0; attr_names[i]; i++) {
+		g_hash_table_insert (ht, attr_names[i],
+		                     nm_tc_action_get_attribute (action, attr_names[i]));
+	}
+
+	if (i) {
+		str = nm_utils_format_variant_attributes (ht, ' ', ' ');
+		g_string_append_c (string, ' ');
+		g_string_append (string, str);
+	}
+
+	return TRUE;
+}
+
+/**
+ * nm_utils_tc_action_to_str:
+ * @action: the %NMTCAction
+ * @error: location of the error
+ *
+ * Turns the %NMTCAction into a tc style string representation of the queueing
+ * discipline.
+ *
+ * Returns: formatted string or %NULL
+ *
+ * Since: 1.10.2
+ */
+char *
+nm_utils_tc_action_to_str (NMTCAction *action, GError **error)
+{
+	GString *string;
+
+	string = g_string_sized_new (60);
+	if (!_string_append_tc_action (string, action, error)) {
+		g_string_free (string, TRUE);
+		return NULL;
+	}
+
+	return g_string_free (string, FALSE);
+}
+
+/**
+ * nm_utils_tc_action_from_str:
+ * @str: the string representation of a action
+ * @error: location of the error
+ *
+ * Parces the tc style string action representation of the queueing
+ * discipline to a %NMTCAction instance. Supports a subset of the tc language.
+ *
+ * Returns: the %NMTCAction or %NULL
+ *
+ * Since: 1.10.2
+ */
+NMTCAction *
+nm_utils_tc_action_from_str (const char *str, GError **error)
+{
+	const char *kind = NULL;
+	const char *rest = NULL;
+	NMTCAction *action = NULL;
+	gs_unref_hashtable GHashTable *ht = NULL;
+	gs_unref_hashtable GHashTable *options = NULL;
+	GVariant *variant;
+	const NMVariantAttributeSpec * const *attrs;
+
+	nm_assert (str);
+	nm_assert (!error || !*error);
+
+        ht = nm_utils_parse_variant_attributes (str,
+                                                ' ', ' ', FALSE,
+                                                tc_action_attribute_spec,
+                                                error);
+	if (!ht)
+		return FALSE;
+
+	variant = g_hash_table_lookup (ht, "kind");
+	if (variant) {
+		kind = g_variant_get_string (variant, NULL);
+	} else {
+		g_set_error_literal (error, 1, 0, _("action name missing."));
+		return NULL;
+	}
+
+	kind = g_variant_get_string (variant, NULL);
+	if (strcmp (kind, "simple") == 0)
+		attrs = tc_action_simple_attribute_spec;
+	else
+		attrs = NULL;
+
+	variant = g_hash_table_lookup (ht, "");
+	if (variant)
+		rest = g_variant_get_string (variant, NULL);
+
+	action = nm_tc_action_new (kind, error);
+	if (!action)
+		return NULL;
+
+	if (rest) {
+		GHashTableIter iter;
+		gpointer key, value;
+
+		if (!attrs) {
+			nm_tc_action_unref (action);
+			g_set_error (error, 1, 0, _("unsupported action option: '%s'."), rest);
+			return NULL;
+		}
+
+	        options = nm_utils_parse_variant_attributes (rest,
+	                                                     ' ', ' ', FALSE,
+	                                                     attrs,
+	                                                     error);
+		if (!options) {
+			nm_tc_action_unref (action);
+			return NULL;
+		}
+
+		g_hash_table_iter_init (&iter, options);
+		while (g_hash_table_iter_next (&iter, &key, &value))
+			nm_tc_action_set_attribute (action, key, g_variant_ref_sink (value));
+	}
+
+	return action;
+}
+
+/*****************************************************************************/
+
+/**
+ * _nm_utils_string_append_tc_tfilter_rest:
+ * @string: the string to write the formatted tfilter to
+ * @tfilter: the %NMTCTfilter
+ *
+ * This formats the rest of the tfilter string but the parent. Useful to format
+ * the keyfile value and nowhere else.
+ * Use nm_utils_tc_tfilter_to_str() that also includes the parent instead.
+ */
+gboolean
+_nm_utils_string_append_tc_tfilter_rest (GString *string, NMTCTfilter *tfilter, GError **error)
+{
+	guint32 handle = nm_tc_tfilter_get_handle (tfilter);
+	const char *kind = nm_tc_tfilter_get_kind (tfilter);
+	NMTCAction *action;
+
+	if (handle != TC_H_UNSPEC) {
+		g_string_append (string, "handle ");
+		_string_append_tc_handle (string, handle);
+		g_string_append_c (string, ' ');
+	}
+
+	g_string_append (string, kind);
+
+	action = nm_tc_tfilter_get_action (tfilter);
+	if (action) {
+		g_string_append (string, " action ");
+		if (!_string_append_tc_action (string, action, error))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+/**
+ * nm_utils_tc_tfilter_to_str:
+ * @tfilter: the %NMTCTfilter
+ * @error: location of the error
+ *
+ * Turns the %NMTCTfilter into a tc style string representation of the queueing
+ * discipline.
+ *
+ * Returns: formatted string or %NULL
+ *
+ * Since: 1.10.2
+ */
+char *
+nm_utils_tc_tfilter_to_str (NMTCTfilter *tfilter, GError **error)
+{
+	GString *string;
+
+	string = g_string_sized_new (60);
+
+	_nm_utils_string_append_tc_parent (string, "parent",
+	                                   nm_tc_tfilter_get_parent (tfilter));
+	if (!_nm_utils_string_append_tc_tfilter_rest (string, tfilter, error)) {
+		g_string_free (string, TRUE);
+		return NULL;
+	}
+
+	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' ),
+	NULL,
+};
+
+/**
+ * nm_utils_tc_tfilter_from_str:
+ * @str: the string representation of a tfilter
+ * @error: location of the error
+ *
+ * Parces the tc style string tfilter representation of the queueing
+ * discipline to a %NMTCTfilter instance. Supports a subset of the tc language.
+ *
+ * Returns: the %NMTCTfilter or %NULL
+ *
+ * Since: 1.10.2
+ */
+NMTCTfilter *
+nm_utils_tc_tfilter_from_str (const char *str, GError **error)
+{
+	guint32 handle = TC_H_UNSPEC;
+	guint32 parent = TC_H_UNSPEC;
+	gs_free char *kind = NULL;
+	gs_free char *rest = NULL;
+	NMTCAction *action = NULL;
+	const char *extra_opts = NULL;
+	NMTCTfilter *tfilter = NULL;
+	gs_unref_hashtable GHashTable *ht = NULL;
+	GVariant *variant;
+
+	nm_assert (str);
+	nm_assert (!error || !*error);
+
+	if (!_tc_read_common_opts (str, &handle, &parent, &kind, &rest, error))
+		return NULL;
+
+	if (rest) {
+	        ht = nm_utils_parse_variant_attributes (rest,
+	                                                ' ', ' ', FALSE,
+	                                                tc_tfilter_attribute_spec,
+	                                                error);
+		if (!ht)
+			return NULL;
+
+		variant = g_hash_table_lookup (ht, "");
+		if (variant)
+			extra_opts = g_variant_get_string (variant, NULL);
+
+		if (g_hash_table_contains (ht, "action")) {
+			action = nm_utils_tc_action_from_str (extra_opts, error);
+			if (!action) {
+				g_prefix_error (error, _("invalid action: "));
+				return NULL;
+			}
+		} else {
+			g_set_error (error, 1, 0, _("unsupported tfilter option: '%s'."), rest);
+			return NULL;
+		}
+	}
+
+	tfilter = nm_tc_tfilter_new (kind, parent, error);
+	if (!tfilter)
+		return NULL;
+
+	nm_tc_tfilter_set_handle (tfilter, handle);
+	if (action) {
+		nm_tc_tfilter_set_action (tfilter, action);
+		nm_tc_action_unref (action);
+	}
+
+	return tfilter;
+}
+
+/*****************************************************************************/
+
 /**
  * nm_utils_uuid_generate_buf_:
  * @buf: input buffer, must contain at least 37 bytes
@@ -3762,6 +4300,22 @@ nm_utils_is_uuid (const char *str)
 
 static char _nm_utils_inet_ntop_buffer[NM_UTILS_INET_ADDRSTRLEN];
 
+const char *
+nm_utils_inet_ntop (int addr_family, gconstpointer addr, char *dst)
+{
+	const char *s;
+
+	nm_assert_addr_family (addr_family);
+	nm_assert (addr);
+
+	s = inet_ntop (addr_family,
+	               addr,
+	               dst ? dst : _nm_utils_inet_ntop_buffer,
+	               addr_family == AF_INET6 ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN);
+	nm_assert (s);
+	return s;
+}
+
 /**
  * nm_utils_inet4_ntop: (skip)
  * @inaddr: the address that should be converted to string.
@@ -4292,6 +4846,446 @@ const char **nm_utils_enum_get_values (GType type, gint from, gint to)
 /*****************************************************************************/
 
 #if WITH_JANSSON
+
+/* Added in Jansson v2.3 (released Jan 27 2012) */
+#ifndef json_object_foreach
+#define json_object_foreach(object, key, value) \
+    for(key = json_object_iter_key(json_object_iter(object)); \
+        key && (value = json_object_iter_value(json_object_iter_at (object, key) )); \
+        key = json_object_iter_key(json_object_iter_next(object, json_object_iter_at (object, key))))
+#endif
+
+/* Added in Jansson v2.4 (released Sep 23 2012), but travis.ci has v2.2. */
+#ifndef json_boolean
+#define json_boolean(val) ((val) ? json_true() : json_false())
+#endif
+
+/* Added in Jansson v2.5 (released Sep 19 2013), but travis.ci has v2.2. */
+#ifndef json_array_foreach
+#define json_array_foreach(array, index, value) \
+        for (index = 0; \
+             index < json_array_size(array) && (value = json_array_get(array, index)); \
+             index++)
+#endif
+
+/* Added in Jansson v2.7 */
+#ifndef json_boolean_value
+#define json_boolean_value json_is_true
+#endif
+
+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 inline 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 inline 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 inline 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;
+	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 = json_string_value (json_element);
+		_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 = 50;
+		runner_active = TRUE;
+		runner_sys_prio = 255;
+		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;
+	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"))
+			name = strdup (json_string_value (j_val));
+		else if (nm_streq (j_key, "target_host"))
+			target_host = strdup (json_string_value (j_val));
+		else if (nm_streq (j_key, "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, "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_ping (val1, val2, val3, 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;
+
+	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
@@ -4360,9 +5354,8 @@ _nm_utils_team_config_equal (const char *conf1,
                              gboolean port_config)
 {
 	json_t *json1 = NULL, *json2 = NULL, *json;
-	json_t *array, *name;
 	gs_free char *dump1 = NULL, *dump2 = NULL;
-	json_t *value, *property;
+	json_t *value;
 	json_error_t jerror;
 	const char *key;
 	gboolean ret;
@@ -4386,33 +5379,8 @@ _nm_utils_team_config_equal (const char *conf1,
 	 * configuration.  Add them with the default value if necessary, depending
 	 * on the configuration type.
 	 */
-	for (i = 0, json = json1; i < 2; i++, json = json2) {
-		if  (port_config) {
-			property = json_object_get (json, "link_watch");
-			if (!property) {
-				property = json_object ();
-				json_object_set_new (property, "name", json_string ("ethtool"));
-				json_object_set_new (json, "link_watch", property);
-			}
-		} else {
-			property = json_object_get (json, "runner");
-			if (!property) {
-				property = json_object ();
-				json_object_set_new (property, "name", json_string ("roundrobin"));
-				json_object_set_new (json, "runner", property);
-			} else if (   (name = json_object_get (property, "name"))
-			           && NM_IN_STRSET (json_string_value (name), "lacp", "loadbalance")) {
-				/* Add default tx_hash when missing */
-				if (!json_object_get (property, "tx_hash")) {
-					array = json_array ();
-					json_array_append_new (array, json_string ("eth"));
-					json_array_append_new (array, json_string ("ipv4"));
-					json_array_append_new (array, json_string ("ipv6"));
-					json_object_set_new (property, "tx_hash", array);
-				}
-			}
-		}
-	}
+	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 */
@@ -4437,6 +5405,239 @@ out:
 	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;
+
+	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 ((GDestroyNotify) 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)));
+			}
+			if (data->len) {
+				g_value_init (value, G_TYPE_STRV);
+				g_value_take_boxed (value, _nm_utils_ptrarray_to_strv (data));
+			}
+			g_ptr_array_free (data, TRUE);
+		} 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)
+{
+	json_t *json, *json_element, *json_link, *json_value = NULL;
+	json_error_t jerror;
+	gboolean updated = FALSE;
+	char **strv;
+	GPtrArray *array;
+	const char *iter_key = key;
+	int i;
+	NMTeamLinkWatcher *watcher;
+
+	g_return_val_if_fail (key, FALSE);
+
+	json = json_loads (*conf?: "{}", JSON_REJECT_DUPLICATES, &jerror);
+	if (!json)
+		return FALSE;
+
+	/* no new value? delete element */
+	if (!value) {
+		updated = _json_del_object (json, key, key2, key3);
+		goto done;
+	}
+
+	/* insert new value */
+	updated = TRUE;
+	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")) {
+			array = g_value_get_boxed (value);
+			if (!array || !array->len) {
+				updated = FALSE;
+				goto done;
+			}
+
+			/*
+			 * json_value:   will hold the final link_watcher json (array) object
+			 * json_element: is the next link_watcher to append to json_value
+			 * json_link:    used to transit the json_value from a single link_watcher
+			 *               object to an array of link watcher objects
+			 */
+			json_value = NULL;
+			for (i = 0; i < array->len; i++) {
+				watcher = array->pdata[i];
+				json_element = _nm_utils_team_link_watcher_to_json (watcher);
+				if (!json_element)
+					continue;
+				if (!json_value) {
+					json_value = json_element;
+					continue;
+				}
+				if (!json_is_array (json_value)) {
+					json_link = json_value;
+					json_value = json_array ();
+					json_array_append_new (json_value, json_link);
+				}
+				json_array_append_new (json_value, json_element);
+			}
+		} else if (   nm_streq (key, "runner")
+		           && nm_streq0 (key2, "tx_hash")) {
+			strv = g_value_get_boxed (value);
+			if (!strv) {
+				updated = FALSE;
+				goto done;
+			}
+			json_value = json_array ();
+			for (i = 0; strv[i]; i++)
+				json_array_append_new (json_value, json_string (strv[i]));
+		} else {
+			updated = FALSE;
+			goto done;
+		}
+	} else {  /* G_VALUE_HOLDS_? */
+		g_assert_not_reached ();
+		updated = FALSE;
+		goto done;
+	}
+
+	/* 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, json_value);
+
+done:
+	if (updated) {
+		_json_team_normalize_defaults (json, (   nm_streq0 (key, "runner")
+		                                      && nm_streq0 (key2, "name")));
+		g_free (*conf);
+		*conf = json_dumps (json, JSON_PRESERVE_ORDER);
+		/* Don't save an empty config */
+		if (nm_streq0 (*conf, "{}")) {
+			g_free (*conf);
+			*conf = NULL;
+		}
+	}
+	json_decref (json);
+	return updated;
+}
+
 #else /* WITH_JANSSON */
 
 gboolean
@@ -4491,8 +5692,224 @@ _nm_utils_team_config_equal (const char *conf1,
 {
 	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 */
+		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;
+		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, "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_ping (val1, val2, val3,
+				                                             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 *
 attribute_escape (const char *src, char c1, char c2)
 {
@@ -4574,8 +5991,10 @@ nm_utils_parse_variant_attributes (const char *string,
 		if (*ptr == '\\') {
 			ptr++;
 			if (!*ptr) {
-				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
-				             _("unterminated escape sequence"));
+				g_set_error_literal (error,
+				                     NM_CONNECTION_ERROR,
+				                     NM_CONNECTION_ERROR_FAILED,
+				                     _("unterminated escape sequence"));
 				return NULL;
 			}
 			goto next;
@@ -4592,8 +6011,10 @@ nm_utils_parse_variant_attributes (const char *string,
 				if (*sep == '\\') {
 					sep++;
 					if (!*sep) {
-						g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
-						             _("unterminated escape sequence"));
+						g_set_error_literal (error,
+						                     NM_CONNECTION_ERROR,
+						                     NM_CONNECTION_ERROR_FAILED,
+						                     _("unterminated escape sequence"));
 						return NULL;
 					}
 				}
@@ -4601,18 +6022,16 @@ nm_utils_parse_variant_attributes (const char *string,
 					break;
 			}
 
-			if (*sep != key_value_separator) {
-				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
-				             _("missing key-value separator '%c'"), key_value_separator);
-				return NULL;
-			}
-
 			name = attribute_unescape (start, sep);
-			value = attribute_unescape (sep + 1, ptr);
 
 			for (s = spec; *s; s++) {
+				if (g_hash_table_contains (ht, (*s)->name))
+					continue;
 				if (nm_streq (name, (*s)->name))
 					break;
+				if (   (*s)->no_value
+				    && g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_STRING))
+					break;
 			}
 
 			if (!*s) {
@@ -4625,12 +6044,33 @@ nm_utils_parse_variant_attributes (const char *string,
 				}
 			}
 
+			if ((*s)->no_value) {
+				if ((*s)->consumes_rest) {
+					value = g_strdup (start);
+					ptr = strchr (start, '\0');
+				} else {
+					value = g_steal_pointer (&name);
+				}
+			} else {
+				if (*sep != key_value_separator) {
+					g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
+					             _("missing key-value separator '%c' after '%s'"), key_value_separator, name);
+					return NULL;
+				}
+
+				/* The attribute and key/value separators are the same. Look for the next one. */
+				if (ptr == sep)
+					goto next;
+
+				value = attribute_unescape (sep + 1, ptr);
+			}
+
 			if (g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_UINT32)) {
 				gint64 num = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1);
 
 				if (num == -1) {
 					g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
-					             _("invalid uint32 value '%s' for attribute '%s'"), value, name);
+					             _("invalid uint32 value '%s' for attribute '%s'"), value, (*s)->name);
 					return NULL;
 				}
 				variant = g_variant_new_uint32 (num);
@@ -4639,33 +6079,32 @@ nm_utils_parse_variant_attributes (const char *string,
 
 				if (num == -1) {
 					g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
-					             _("invalid uint8 value '%s' for attribute '%s'"), value, name);
+					             _("invalid uint8 value '%s' for attribute '%s'"), value, (*s)->name);
 					return NULL;
 				}
 				variant = g_variant_new_byte ((guchar) num);
 			} else if (g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_BOOLEAN)) {
-				gboolean b;
+				int b;
 
-				if (nm_streq (value, "true"))
-					b = TRUE;
-				else if (nm_streq (value, "false"))
-					b = FALSE;
-				else {
+				b = (*s)->no_value ? TRUE :_nm_utils_ascii_str_to_bool (value, -1);
+				if (b == -1) {
 					g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
-					             _("invalid boolean value '%s' for attribute '%s'"), value, name);
+					             _("invalid boolean value '%s' for attribute '%s'"), value, (*s)->name);
 					return NULL;
 				}
 				variant = g_variant_new_boolean (b);
 			} else if (g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_STRING)) {
 				variant = g_variant_new_take_string (g_steal_pointer (&value));
+			} else if (g_variant_type_equal ((*s)->type, G_VARIANT_TYPE_BYTESTRING)) {
+				variant = g_variant_new_bytestring (value);
 			} else {
 				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED,
-				             _("unsupported attribute '%s' of type '%s'"), name,
+				             _("unsupported attribute '%s' of type '%s'"), (*s)->name,
 				             (char *) (*s)->type);
 				return NULL;
 			}
 
-			g_hash_table_insert (ht, g_steal_pointer (&name), variant);
+			g_hash_table_insert (ht, g_strdup ((*s)->name), variant);
 			start = NULL;
 		}
 next:
@@ -4726,6 +6165,8 @@ nm_utils_format_variant_attributes (GHashTable *attributes,
 			value = g_variant_get_boolean (variant) ? "true" : "false";
 		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING))
 			value = g_variant_get_string (variant, NULL);
+		else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BYTESTRING))
+			value = g_variant_get_bytestring (variant);
 		else
 			continue;
 
diff --git a/libnm-core/nm-utils.h b/libnm-core/nm-utils.h
index 77fe18a9..df9284b3 100644
--- a/libnm-core/nm-utils.h
+++ b/libnm-core/nm-utils.h
@@ -15,7 +15,7 @@
  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  * Boston, MA 02110-1301 USA.
  *
- * Copyright 2005 - 2014 Red Hat, Inc.
+ * Copyright 2005 - 2017 Red Hat, Inc.
  */
 
 #ifndef __NM_UTILS_H__
@@ -35,6 +35,7 @@
 
 #include "nm-core-enum-types.h"
 #include "nm-setting-wireless-security.h"
+#include "nm-setting-tc-config.h"
 
 G_BEGIN_DECLS
 
@@ -230,6 +231,23 @@ char * nm_utils_format_variant_attributes (GHashTable *attributes,
                                            char attr_separator,
                                            char key_value_separator);
 
+/*****************************************************************************/
+
+NM_AVAILABLE_IN_1_10_2
+NMTCQdisc *nm_utils_tc_qdisc_from_str      (const char *str, GError **error);
+NM_AVAILABLE_IN_1_10_2
+char *nm_utils_tc_qdisc_to_str             (NMTCQdisc *qdisc, GError **error);
+
+NM_AVAILABLE_IN_1_10_2
+NMTCAction *nm_utils_tc_action_from_str    (const char *str, GError **error);
+NM_AVAILABLE_IN_1_10_2
+char *nm_utils_tc_action_to_str            (NMTCAction *action, GError **error);
+
+NM_AVAILABLE_IN_1_10_2
+NMTCTfilter *nm_utils_tc_tfilter_from_str  (const char *str, GError **error);
+NM_AVAILABLE_IN_1_10_2
+char *nm_utils_tc_tfilter_to_str           (NMTCTfilter *tfilter, GError **error);
+
 G_END_DECLS
 
 #endif /* __NM_UTILS_H__ */
diff --git a/libnm-core/nm-version.h b/libnm-core/nm-version.h
index f6bfaf72..61bfa5b1 100644
--- a/libnm-core/nm-version.h
+++ b/libnm-core/nm-version.h
@@ -146,4 +146,10 @@
 # define NM_AVAILABLE_IN_1_10
 #endif
 
+#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_10_2
+# define NM_AVAILABLE_IN_1_10_2          G_UNAVAILABLE(1,10.2)
+#else
+# define NM_AVAILABLE_IN_1_10_2
+#endif
+
 #endif  /* NM_VERSION_H */
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index 188cb28e..d743fd22 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -330,7 +330,7 @@ test_c_list_sort (void)
 
 				if (headless) {
 					lst = head.next;
-					c_list_unlink (&head);
+					c_list_unlink_stale (&head);
 					lst = c_list_sort_headless (lst, _c_list_sort_cmp, NULL);
 					g_assert (lst);
 					g_assert (lst->next);
diff --git a/libnm-core/tests/test-keyfile.c b/libnm-core/tests/test-keyfile.c
index 2dc929af..313932f0 100644
--- a/libnm-core/tests/test-keyfile.c
+++ b/libnm-core/tests/test-keyfile.c
@@ -99,6 +99,11 @@ test_encode_key (void)
 	do_test_encode_key_bijection (kf, "   ", "\\20 \\20");
 	do_test_encode_key_decode_surjection (kf, "f\\20c", "f c");
 	do_test_encode_key_decode_surjection (kf, "\\20\\20\\20", "\\20 \\20");
+
+	do_test_encode_key_bijection (kf, "\t", "\\09");
+	do_test_encode_key_bijection (kf, "\t=x", "\\09\\3Dx");
+	do_test_encode_key_bijection (kf, "(nm-openvpn-auth-dialog:10283): GdkPixbuf-DEBUG: \tCopy pixels == false",
+	                                  "(nm-openvpn-auth-dialog:10283): GdkPixbuf-DEBUG: \\09Copy pixels \\3D\\3D false");
 }
 
 /*****************************************************************************/
diff --git a/libnm-core/tests/test-setting-8021x.c b/libnm-core/tests/test-setting-8021x.c
deleted file mode 100644
index 268a9f52..00000000
--- a/libnm-core/tests/test-setting-8021x.c
+++ /dev/null
@@ -1,404 +0,0 @@
-/* -*- 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
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2008 - 2011 Red Hat, Inc.
- *
- */
-
-#include "nm-default.h"
-
-#include <string.h>
-
-#include "nm-utils.h"
-#include "nm-setting-connection.h"
-#include "nm-setting-8021x.h"
-
-#include "nm-utils/nm-test-utils.h"
-
-static void
-compare_blob_data (const char *test,
-                   const char *key_path,
-                   GBytes *key)
-{
-	char *contents = NULL;
-	gsize len = 0;
-	GError *error = NULL;
-	gboolean success;
-
-	g_assert (key && g_bytes_get_size (key) > 0);
-
-	success = g_file_get_contents (key_path, &contents, &len, &error);
-	nmtst_assert_success (success, error);
-
-	g_assert_cmpmem (contents, len, g_bytes_get_data (key, NULL), g_bytes_get_size (key));
-
-	g_free (contents);
-}
-
-static void
-check_scheme_path (GBytes *value, const char *path)
-{
-	const guint8 *p;
-
-	g_assert (value);
-
-	p = g_bytes_get_data (value, NULL);
-	g_assert (memcmp (p, NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)) == 0);
-	p += strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH);
-	g_assert (memcmp (p, path, strlen (path)) == 0);
-	p += strlen (path);
-	g_assert (*p == '\0');
-}
-
-static void
-test_private_key_import (const char *path,
-                         const char *password,
-                         NMSetting8021xCKScheme scheme)
-{
-	NMSetting8021x *s_8021x;
-	gboolean success;
-	NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
-	NMSetting8021xCKFormat tmp_fmt;
-	GError *error = NULL;
-	GBytes *tmp_key = NULL, *client_cert = NULL;
-	const char *pw;
-
-	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
-	g_assert (s_8021x);
-
-	success = nm_setting_802_1x_set_private_key (s_8021x,
-	                                             path,
-	                                             password,
-	                                             scheme,
-	                                             &format,
-	                                             &error);
-	nmtst_assert_success (success, error);
-	g_assert (format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
-	tmp_fmt = nm_setting_802_1x_get_private_key_format (s_8021x);
-	g_assert (tmp_fmt == format);
-
-	/* Make sure the password is what we expect */
-	pw = nm_setting_802_1x_get_private_key_password (s_8021x);
-	g_assert (pw != NULL);
-	g_assert_cmpstr (pw, ==, password);
-
-	if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
-		tmp_key = nm_setting_802_1x_get_private_key_blob (s_8021x);
-		compare_blob_data ("private-key-import", path, tmp_key);
-	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
-		g_object_get (s_8021x, NM_SETTING_802_1X_PRIVATE_KEY, &tmp_key, NULL);
-		check_scheme_path (tmp_key, path);
-		g_bytes_unref (tmp_key);
-	} else
-		g_assert_not_reached ();
-
-	/* If it's PKCS#12 ensure the client cert is the same value */
-	if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
-		g_object_get (s_8021x, NM_SETTING_802_1X_PRIVATE_KEY, &tmp_key, NULL);
-		g_assert (tmp_key);
-
-		g_object_get (s_8021x, NM_SETTING_802_1X_CLIENT_CERT, &client_cert, NULL);
-		g_assert (client_cert);
-
-		/* make sure they are the same */
-		g_assert (g_bytes_equal (tmp_key, client_cert));
-
-		g_bytes_unref (tmp_key);
-		g_bytes_unref (client_cert);
-	}
-
-	g_object_unref (s_8021x);
-}
-
-static void
-test_phase2_private_key_import (const char *path,
-                                const char *password,
-                                NMSetting8021xCKScheme scheme)
-{
-	NMSetting8021x *s_8021x;
-	gboolean success;
-	NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
-	NMSetting8021xCKFormat tmp_fmt;
-	GError *error = NULL;
-	GBytes *tmp_key = NULL, *client_cert = NULL;
-	const char *pw;
-
-	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
-	g_assert (s_8021x);
-
-	success = nm_setting_802_1x_set_phase2_private_key (s_8021x,
-	                                                    path,
-	                                                    password,
-	                                                    scheme,
-	                                                    &format,
-	                                                    &error);
-	nmtst_assert_success (success, error);
-	g_assert (format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
-	tmp_fmt = nm_setting_802_1x_get_phase2_private_key_format (s_8021x);
-	g_assert (tmp_fmt == format);
-
-	/* Make sure the password is what we expect */
-	pw = nm_setting_802_1x_get_phase2_private_key_password (s_8021x);
-	g_assert (pw);
-	g_assert_cmpstr (pw, ==, password);
-
-	if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
-		tmp_key = nm_setting_802_1x_get_phase2_private_key_blob (s_8021x);
-		compare_blob_data ("phase2-private-key-import", path, tmp_key);
-	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
-		g_object_get (s_8021x, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, &tmp_key, NULL);
-		check_scheme_path (tmp_key, path);
-		g_bytes_unref (tmp_key);
-	} else
-		g_assert_not_reached ();
-
-	/* If it's PKCS#12 ensure the client cert is the same value */
-	if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
-		g_object_get (s_8021x, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, &tmp_key, NULL);
-		g_assert (tmp_key);
-
-		g_object_get (s_8021x, NM_SETTING_802_1X_PHASE2_CLIENT_CERT, &client_cert, NULL);
-		g_assert (client_cert);
-
-		/* make sure they are the same */
-		g_assert (g_bytes_equal (tmp_key, client_cert));
-
-		g_bytes_unref (tmp_key);
-		g_bytes_unref (client_cert);
-	}
-
-	g_object_unref (s_8021x);
-}
-
-static void
-test_wrong_password_keeps_data (const char *path, const char *password)
-{
-	NMSetting8021x *s_8021x;
-	gboolean success;
-	NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
-	GError *error = NULL;
-	const char *pw;
-
-	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
-	g_assert (s_8021x);
-
-	success = nm_setting_802_1x_set_private_key (s_8021x,
-	                                             path,
-	                                             password,
-	                                             NM_SETTING_802_1X_CK_SCHEME_BLOB,
-	                                             &format,
-	                                             &error);
-	nmtst_assert_success (success, error);
-	g_assert (format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
-
-	/* Now try to set it to something that's not a certificate */
-	format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
-	success = nm_setting_802_1x_set_private_key (s_8021x,
-	                                             "Makefile.am",
-	                                             password,
-	                                             NM_SETTING_802_1X_CK_SCHEME_BLOB,
-	                                             &format,
-	                                             &error);
-	nmtst_assert_no_success (success, error);
-	g_assert (format == NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
-	g_clear_error (&error);
-
-	/* Make sure the password hasn't changed */
-	pw = nm_setting_802_1x_get_private_key_password (s_8021x);
-	g_assert (pw);
-	g_assert_cmpstr (pw, ==, password);
-
-	g_object_unref (s_8021x);
-}
-
-static void
-test_clear_private_key (const char *path, const char *password)
-{
-	NMSetting8021x *s_8021x;
-	gboolean success;
-	NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
-	GError *error = NULL;
-	const char *pw;
-
-	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
-	g_assert (s_8021x);
-
-	success = nm_setting_802_1x_set_private_key (s_8021x,
-	                                             path,
-	                                             password,
-	                                             NM_SETTING_802_1X_CK_SCHEME_BLOB,
-	                                             &format,
-	                                             &error);
-	nmtst_assert_success (success, error);
-	g_assert (format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
-
-	/* Make sure the password is what we expect */
-	pw = nm_setting_802_1x_get_private_key_password (s_8021x);
-	g_assert (pw);
-	g_assert_cmpstr (pw, ==, password);
-
-	/* Now clear it */
-	success = nm_setting_802_1x_set_private_key (s_8021x,
-	                                             NULL,
-	                                             NULL,
-	                                             NM_SETTING_802_1X_CK_SCHEME_BLOB,
-	                                             NULL,
-	                                             &error);
-	nmtst_assert_success (success, error);
-
-	/* Ensure the password is also now clear */
-	g_assert (!nm_setting_802_1x_get_private_key_password (s_8021x));
-
-	g_object_unref (s_8021x);
-}
-
-static void
-test_wrong_phase2_password_keeps_data (const char *path, const char *password)
-{
-	NMSetting8021x *s_8021x;
-	gboolean success;
-	NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
-	GError *error = NULL;
-	const char *pw;
-
-	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
-	g_assert (s_8021x);
-
-	success = nm_setting_802_1x_set_phase2_private_key (s_8021x,
-	                                                    path,
-	                                                    password,
-	                                                    NM_SETTING_802_1X_CK_SCHEME_BLOB,
-	                                                    &format,
-	                                                    &error);
-	nmtst_assert_success (success, error);
-	g_assert (format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
-
-	/* Now try to set it to something that's not a certificate */
-	format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
-	success = nm_setting_802_1x_set_phase2_private_key (s_8021x,
-	                                                    "Makefile.am",
-	                                                    password,
-	                                                    NM_SETTING_802_1X_CK_SCHEME_BLOB,
-	                                                    &format,
-	                                                    &error);
-	nmtst_assert_no_success (success, error);
-	g_assert (format == NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
-	g_clear_error (&error);
-
-	/* Make sure the password hasn't changed */
-	pw = nm_setting_802_1x_get_phase2_private_key_password (s_8021x);
-	g_assert (pw);
-	g_assert_cmpstr (pw, ==, password);
-
-	g_object_unref (s_8021x);
-}
-
-static void
-test_clear_phase2_private_key (const char *path, const char *password)
-{
-	NMSetting8021x *s_8021x;
-	gboolean success;
-	NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
-	GError *error = NULL;
-	const char *pw;
-
-	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
-	g_assert (s_8021x);
-
-	success = nm_setting_802_1x_set_phase2_private_key (s_8021x,
-	                                                    path,
-	                                                    password,
-	                                                    NM_SETTING_802_1X_CK_SCHEME_BLOB,
-	                                                    &format,
-	                                                    &error);
-	nmtst_assert_success (success, error);
-	g_assert (format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
-
-	/* Make sure the password is what we expect */
-	pw = nm_setting_802_1x_get_phase2_private_key_password (s_8021x);
-	g_assert (pw);
-	g_assert_cmpstr (pw, ==, password);
-
-	/* Now clear it */
-	success = nm_setting_802_1x_set_phase2_private_key (s_8021x,
-	                                                    NULL,
-	                                                    NULL,
-	                                                    NM_SETTING_802_1X_CK_SCHEME_BLOB,
-	                                                    NULL,
-	                                                    &error);
-	nmtst_assert_success (success, error);
-
-	/* Ensure the password is also now clear */
-	g_assert (!nm_setting_802_1x_get_phase2_private_key_password (s_8021x));
-
-	g_object_unref (s_8021x);
-}
-
-static void
-do_8021x_test (gconstpointer test_data)
-{
-	char **parts, *path, *password;
-
-	parts = g_strsplit ((const char *) test_data, ", ", -1);
-	g_assert_cmpint (g_strv_length (parts), ==, 2);
-
-	path = g_build_filename (TEST_CERT_DIR, parts[0], NULL);
-	password = parts[1];
-
-	/* Test phase1 and phase2 path scheme */
-	test_private_key_import (path, password, NM_SETTING_802_1X_CK_SCHEME_PATH);
-	test_phase2_private_key_import (path, password, NM_SETTING_802_1X_CK_SCHEME_PATH);
-
-	/* Test phase1 and phase2 blob scheme */
-	test_private_key_import (path, password, NM_SETTING_802_1X_CK_SCHEME_BLOB);
-	test_phase2_private_key_import (path, password, NM_SETTING_802_1X_CK_SCHEME_BLOB);
-
-	/* Test that using a wrong password does not change existing data */
-	test_wrong_password_keeps_data (path, password);
-	test_wrong_phase2_password_keeps_data (path, password);
-
-	/* Test clearing the private key */
-	test_clear_private_key (path, password);
-	test_clear_phase2_private_key (path, password);
-
-	g_free (path);
-	g_strfreev (parts);
-}
-
-NMTST_DEFINE ();
-
-int
-main (int argc, char **argv)
-{
-	nmtst_init (&argc, &argv, TRUE);
-
-	g_test_add_data_func ("/libnm/setting-8021x/key-and-cert",
-	                      "test_key_and_cert.pem, test",
-	                      do_8021x_test);
-	g_test_add_data_func ("/libnm/setting-8021x/key-only",
-	                      "test-key-only.pem, test",
-	                      do_8021x_test);
-	g_test_add_data_func ("/libnm/setting-8021x/pkcs8-enc-key",
-	                      "pkcs8-enc-key.pem, 1234567890",
-	                      do_8021x_test);
-	g_test_add_data_func ("/libnm/setting-8021x/pkcs12",
-	                      "test-cert.p12, test",
-	                      do_8021x_test);
-
-	return g_test_run ();
-}
-
diff --git a/libnm-core/tests/test-setting-bond.c b/libnm-core/tests/test-setting-bond.c
deleted file mode 100644
index e6a65bba..00000000
--- a/libnm-core/tests/test-setting-bond.c
+++ /dev/null
@@ -1,247 +0,0 @@
-/* -*- 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
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2016 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-utils.h"
-#include "nm-setting-bond.h"
-#include "nm-connection.h"
-#include "nm-simple-connection.h"
-#include "nm-setting-connection.h"
-#include "nm-errors.h"
-
-#include "nm-utils/nm-test-utils.h"
-
-static void
-create_bond_connection (NMConnection **con, NMSettingBond **s_bond)
-{
-	NMSettingConnection *s_con;
-
-	g_assert (con);
-	g_assert (s_bond);
-
-	*con = nmtst_create_minimal_connection ("bond",
-	                                        NULL,
-	                                        NM_SETTING_BOND_SETTING_NAME,
-	                                        &s_con);
-	g_assert (*con);
-	g_assert (s_con);
-
-	g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "bond0", NULL);
-
-	*s_bond = (NMSettingBond *) nm_setting_bond_new ();
-	g_assert (*s_bond);
-
-	nm_connection_add_setting (*con, NM_SETTING (*s_bond));
-}
-
-#define test_verify_options(exp, ...) \
-	G_STMT_START { \
-		const char *__opts[] = { __VA_ARGS__ , NULL }; \
-		\
-		_test_verify_options (__opts, exp); \
-	} G_STMT_END
-
-static void
-_test_verify_options (const char **options, gboolean expected_result)
-{
-	gs_unref_object NMConnection *con = NULL;
-	NMSettingBond *s_bond;
-	GError *error = NULL;
-	gboolean success;
-	const char **option;
-
-	create_bond_connection (&con, &s_bond);
-
-	for (option = options; option[0] && option[1]; option += 2)
-		g_assert (nm_setting_bond_add_option (s_bond, option[0], option[1]));
-
-	if (expected_result) {
-		nmtst_assert_connection_verifies_and_normalizable (con);
-		nmtst_connection_normalize (con);
-		success = nm_setting_verify ((NMSetting *) s_bond, con, &error);
-		nmtst_assert_success (success, error);
-	} else {
-		nmtst_assert_connection_unnormalizable (con,
-		                                        NM_CONNECTION_ERROR,
-		                                        NM_CONNECTION_ERROR_INVALID_PROPERTY);
-	}
-}
-
-static void
-test_verify (void)
-{
-	test_verify_options (TRUE,
-	                     "mode", "3",
-	                     "arp_interval", "0");
-	test_verify_options (FALSE,
-	                     /* arp_interval not supported in balance-alb mode */
-	                     "mode", "balance-alb",
-	                     "arp_interval", "1",
-	                     "arp_ip_target", "1.2.3.4");
-	test_verify_options (FALSE,
-	                     /* arp_ip_target requires arp_interval */
-	                     "mode", "balance-rr",
-	                     "arp_ip_target", "1.2.3.4");
-	test_verify_options (TRUE,
-	                     "mode", "balance-rr",
-	                     "arp_interval", "1",
-	                     "arp_ip_target", "1.2.3.4");
-	test_verify_options (FALSE,
-	                     /* num_grat_arp, num_unsol_na cannot be different */
-	                     "mode", "balance-rr",
-	                     "num_grat_arp", "3",
-	                     "num_unsol_na", "4");
-	test_verify_options (TRUE,
-	                     "mode", "balance-rr",
-	                     "num_grat_arp", "5",
-	                     "num_unsol_na", "5");
-	test_verify_options (TRUE,
-	                     "mode", "active-backup",
-	                     "primary", "eth0");
-	test_verify_options (FALSE,
-	                     /* primary requires mode=active-backup */
-	                     "mode", "802.3ad",
-	                     "primary", "eth0");
-	test_verify_options (TRUE,
-	                     "mode", "802.3ad",
-	                     "lacp_rate", "fast");
-	test_verify_options (FALSE,
-	                     /* lacp_rate=fast requires mode=802.3ad */
-	                     "mode", "balance-rr",
-	                     "lacp_rate", "fast");
-	test_verify_options (TRUE,
-	                     "mode", "802.3ad",
-	                     "ad_actor_system", "ae:00:11:33:44:55");
-}
-
-static void
-test_compare_options (gboolean exp_res, const char **opts1, const char **opts2)
-{
-	gs_unref_object NMSettingBond *s_bond1 = NULL, *s_bond2 = NULL;
-	const char **p;
-
-	s_bond1 = (NMSettingBond *) nm_setting_bond_new ();
-	g_assert (s_bond1);
-	s_bond2 = (NMSettingBond *) nm_setting_bond_new ();
-	g_assert (s_bond2);
-
-	for (p = opts1; p[0] && p[1]; p += 2)
-		g_assert (nm_setting_bond_add_option (s_bond1, p[0], p[1]));
-
-	for (p = opts2; p[0] && p[1]; p += 2)
-		g_assert (nm_setting_bond_add_option (s_bond2, p[0], p[1]));
-
-	g_assert_cmpint (nm_setting_compare ((NMSetting *) s_bond1,
-	                                     (NMSetting *) s_bond2,
-	                                     NM_SETTING_COMPARE_FLAG_EXACT),
-	                 ==,
-	                 exp_res);
-}
-
-static void
-test_compare (void)
-{
-	test_compare_options (TRUE,
-	                      ((const char *[]){ "mode", "balance-rr", "miimon", "1", NULL }),
-	                      ((const char *[]){ "mode", "balance-rr", "miimon", "1", NULL }));
-	test_compare_options (FALSE,
-	                      ((const char *[]){ "mode", "balance-rr", "miimon", "1", NULL }),
-	                      ((const char *[]){ "mode", "balance-rr", "miimon", "2", NULL }));
-
-	/* ignore default values */
-	test_compare_options (TRUE,
-	                      ((const char *[]){ "miimon", "1", NULL }),
-	                      ((const char *[]){ "miimon", "1", "updelay", "0", NULL }));
-
-	/* special handling of num_grat_arp, num_unsol_na */
-	test_compare_options (FALSE,
-	                      ((const char *[]){ "num_grat_arp", "2", NULL }),
-	                      ((const char *[]){ "num_grat_arp", "1", NULL }));
-	test_compare_options (TRUE,
-	                      ((const char *[]){ "num_grat_arp", "3", NULL }),
-	                      ((const char *[]){ "num_unsol_na", "3", NULL }));
-	test_compare_options (TRUE,
-	                      ((const char *[]){ "num_grat_arp", "4", NULL }),
-	                      ((const char *[]){ "num_unsol_na", "4", "num_grat_arp", "4", NULL }));
-}
-
-static void
-test_normalize_options (const char **opts1, const char **opts2)
-{
-	gs_unref_object NMConnection *con = NULL;
-	NMSettingBond *s_bond;
-	GError *error = NULL;
-	gboolean success;
-	const char **p;
-	int num = 0;
-
-	create_bond_connection (&con, &s_bond);
-
-	for (p = opts1; p[0] && p[1]; p += 2)
-		g_assert (nm_setting_bond_add_option (s_bond, p[0], p[1]));
-
-	nmtst_assert_connection_verifies_and_normalizable (con);
-	nmtst_connection_normalize (con);
-	success = nm_setting_verify ((NMSetting *) s_bond, con, &error);
-	nmtst_assert_success (success, error);
-
-	for (p = opts2; p[0] && p[1]; p += 2) {
-		g_assert_cmpstr (nm_setting_bond_get_option_by_name (s_bond, p[0]), ==, p[1]);
-		num++;
-	}
-
-	g_assert_cmpint (num, ==, nm_setting_bond_get_num_options (s_bond));
-}
-
-static void
-test_normalize (void)
-{
-	test_normalize_options (
-		((const char *[]){ "mode", "802.3ad", "ad_actor_system", "00:02:03:04:05:06", NULL }),
-		((const char *[]){ "mode", "802.3ad", "ad_actor_system", "00:02:03:04:05:06", NULL }));
-	test_normalize_options (
-		((const char *[]){ "mode", "1", "miimon", "1", NULL }),
-		((const char *[]){ "mode", "active-backup", "miimon", "1", NULL }));
-	test_normalize_options (
-		((const char *[]){ "mode", "balance-alb", "tlb_dynamic_lb", "1", NULL }),
-		((const char *[]){ "mode", "balance-alb", NULL }));
-	test_normalize_options (
-		((const char *[]){ "mode", "balance-tlb", "tlb_dynamic_lb", "1", NULL }),
-		((const char *[]){ "mode", "balance-tlb", "tlb_dynamic_lb", "1", NULL }));
-	test_normalize_options (
-		((const char *[]){ "mode", "balance-rr", "ad_actor_sys_prio", "4", "packets_per_slave", "3", NULL }),
-		((const char *[]){ "mode", "balance-rr", "packets_per_slave", "3", NULL }));
-}
-
-#define TPATH "/libnm/settings/bond/"
-
-NMTST_DEFINE ();
-
-int
-main (int argc, char **argv)
-{
-	nmtst_init (&argc, &argv, TRUE);
-
-	g_test_add_func (TPATH "verify", test_verify);
-	g_test_add_func (TPATH "compare", test_compare);
-	g_test_add_func (TPATH "normalize", test_normalize);
-
-	return g_test_run ();
-}
diff --git a/libnm-core/tests/test-setting-dcb.c b/libnm-core/tests/test-setting-dcb.c
deleted file mode 100644
index 55c1c516..00000000
--- a/libnm-core/tests/test-setting-dcb.c
+++ /dev/null
@@ -1,321 +0,0 @@
-/* -*- 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
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2013 Red Hat, Inc.
- *
- */
-
-#include "nm-default.h"
-
-#include <string.h>
-
-#include "nm-utils.h"
-#include "nm-setting-dcb.h"
-#include "nm-connection.h"
-#include "nm-errors.h"
-
-#include "nm-utils/nm-test-utils.h"
-
-#define DCB_FLAGS_ALL (NM_SETTING_DCB_FLAG_ENABLE | \
-                       NM_SETTING_DCB_FLAG_ADVERTISE | \
-                       NM_SETTING_DCB_FLAG_WILLING)
-
-static void
-test_dcb_flags_valid (void)
-{
-	gs_unref_object NMSettingDcb *s_dcb = NULL;
-	GError *error = NULL;
-	gboolean success;
-	guint i;
-
-	s_dcb = (NMSettingDcb *) nm_setting_dcb_new ();
-	g_assert (s_dcb);
-
-	g_assert_cmpint (nm_setting_dcb_get_app_fcoe_flags (s_dcb), ==, 0);
-	g_assert_cmpint (nm_setting_dcb_get_app_iscsi_flags (s_dcb), ==, 0);
-	g_assert_cmpint (nm_setting_dcb_get_app_fip_flags (s_dcb), ==, 0);
-	g_assert_cmpint (nm_setting_dcb_get_priority_flow_control_flags (s_dcb), ==, 0);
-	g_assert_cmpint (nm_setting_dcb_get_priority_group_flags (s_dcb), ==, 0);
-
-	g_object_set (G_OBJECT (s_dcb),
-	              NM_SETTING_DCB_APP_FCOE_FLAGS, DCB_FLAGS_ALL,
-	              NM_SETTING_DCB_APP_ISCSI_FLAGS, DCB_FLAGS_ALL,
-	              NM_SETTING_DCB_APP_FIP_FLAGS, DCB_FLAGS_ALL,
-	              NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, DCB_FLAGS_ALL,
-	              NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, DCB_FLAGS_ALL,
-	              NULL);
-	/* Priority Group Bandwidth must total 100% */
-	for (i = 0; i < 7; i++)
-		nm_setting_dcb_set_priority_group_bandwidth (s_dcb, i, 12);
-	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 7, 16);
-
-	success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error);
-	g_assert_no_error (error);
-	g_assert (success);
-
-	g_assert_cmpint (nm_setting_dcb_get_app_fcoe_flags (s_dcb), ==, DCB_FLAGS_ALL);
-	g_assert_cmpint (nm_setting_dcb_get_app_iscsi_flags (s_dcb), ==, DCB_FLAGS_ALL);
-	g_assert_cmpint (nm_setting_dcb_get_app_fip_flags (s_dcb), ==, DCB_FLAGS_ALL);
-	g_assert_cmpint (nm_setting_dcb_get_priority_flow_control_flags (s_dcb), ==, DCB_FLAGS_ALL);
-	g_assert_cmpint (nm_setting_dcb_get_priority_group_flags (s_dcb), ==, DCB_FLAGS_ALL);
-}
-
-#define TEST_FLAG(p, f, v) \
-{ \
-	/* GObject property min/max should ensure the property does not get set to \
-	 * the invalid value, so we ensure the value we just tried to set is 0 and \
-	 * that verify is successful since the property never got set. \
-	 */ \
-	g_object_set (G_OBJECT (s_dcb), p, v, NULL); \
-	g_assert_cmpint (f (s_dcb), ==, 0); \
-	success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error); \
-	g_assert_no_error (error); \
-	g_assert (success); \
-}
-
-static void
-test_dcb_flags_invalid (void)
-{
-	gs_unref_object NMSettingDcb *s_dcb = NULL;
-	GError *error = NULL;
-	gboolean success;
-
-	s_dcb = (NMSettingDcb *) nm_setting_dcb_new ();
-	g_assert (s_dcb);
-
-	g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*");
-	TEST_FLAG (NM_SETTING_DCB_APP_FCOE_FLAGS, nm_setting_dcb_get_app_fcoe_flags, 0x332523);
-	g_test_assert_expected_messages ();
-
-	g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*");
-	TEST_FLAG (NM_SETTING_DCB_APP_ISCSI_FLAGS, nm_setting_dcb_get_app_iscsi_flags, 0xFF);
-	g_test_assert_expected_messages ();
-
-	g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*");
-	TEST_FLAG (NM_SETTING_DCB_APP_FIP_FLAGS, nm_setting_dcb_get_app_fip_flags, 0x1111);
-	g_test_assert_expected_messages ();
-
-	g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*");
-	TEST_FLAG (NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, nm_setting_dcb_get_priority_flow_control_flags, G_MAXUINT32);
-	g_test_assert_expected_messages ();
-
-	g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*");
-	TEST_FLAG (NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, nm_setting_dcb_get_priority_group_flags,
-	           (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE | NM_SETTING_DCB_FLAG_WILLING) + 1);
-	g_test_assert_expected_messages ();
-}
-
-#define TEST_APP_PRIORITY(lcprop, ucprop, v) \
-{ \
-	g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_APP_##ucprop##_FLAGS, NM_SETTING_DCB_FLAG_NONE, NULL); \
- \
-	g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_APP_##ucprop##_PRIORITY, v, NULL); \
-	g_assert_cmpint (nm_setting_dcb_get_app_##lcprop##_priority (s_dcb), ==, v); \
- \
-	/* Assert that the setting is invalid while the app is disabled unless v is default */ \
-	success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error); \
-	if (v >= 0) { \
-		g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); \
-		g_assert (success == FALSE); \
-	} else { \
-		g_assert_no_error (error); \
-		g_assert (success); \
-	} \
-	g_clear_error (&error); \
- \
-	/* Set the enable flag and re-verify, this time it should be valid */ \
-	g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_APP_##ucprop##_FLAGS, NM_SETTING_DCB_FLAG_ENABLE, NULL); \
-	success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error); \
-	g_assert_no_error (error); \
-	g_assert (success); \
- \
-	g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_APP_##ucprop##_PRIORITY, 0, NULL); \
-}
-
-static void
-test_dcb_app_priorities (void)
-{
-	gs_unref_object NMSettingDcb *s_dcb = NULL;
-	GError *error = NULL;
-	gboolean success;
-
-	s_dcb = (NMSettingDcb *) nm_setting_dcb_new ();
-	g_assert (s_dcb);
-
-	/* Defaults */
-	g_assert_cmpint (nm_setting_dcb_get_app_fcoe_priority (s_dcb), ==, -1);
-	g_assert_cmpint (nm_setting_dcb_get_app_iscsi_priority (s_dcb), ==, -1);
-	g_assert_cmpint (nm_setting_dcb_get_app_fip_priority (s_dcb), ==, -1);
-
-	TEST_APP_PRIORITY (fcoe, FCOE, 6);
-	TEST_APP_PRIORITY (iscsi, ISCSI, 5);
-	TEST_APP_PRIORITY (fip, FIP, 4);
-
-	TEST_APP_PRIORITY (fcoe, FCOE, -1);
-	TEST_APP_PRIORITY (iscsi, ISCSI, -1);
-	TEST_APP_PRIORITY (fip, FIP, -1);
-}
-
-#define TEST_PRIORITY_VALID(fn, id, val, flagsprop, verify) \
-{ \
-	/* Assert that setting the value gets the same value back out */ \
-	nm_setting_dcb_set_priority_##fn (s_dcb, id, val); \
-	g_assert_cmpint (nm_setting_dcb_get_priority_##fn (s_dcb, id), ==, val); \
- \
-	if (verify) { \
-		if (val != 0) { \
-			/* Assert that verify fails because the flags do not include 'enabled' \
-			 * and a value has been set. \
-			 */ \
-			success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error); \
-			g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); \
-			g_assert (success == FALSE); \
-			g_clear_error (&error); \
-		} \
- \
-		/* Assert that adding the 'enabled' flag verifies the setting */ \
-		g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_PRIORITY_##flagsprop##_FLAGS, NM_SETTING_DCB_FLAG_ENABLE, NULL); \
-		success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error); \
-		g_assert_no_error (error); \
-		g_assert (success); \
-	} \
- \
-	/* Reset everything */ \
-	g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_PRIORITY_##flagsprop##_FLAGS, NM_SETTING_DCB_FLAG_NONE, NULL); \
-	nm_setting_dcb_set_priority_##fn (s_dcb, id, 0); \
-}
-
-/* If Priority Groups are enabled, PG bandwidth must equal 100% */
-#define SET_VALID_PRIORITY_GROUP_BANDWIDTH \
-{ \
-	guint x; \
-	for (x = 0; x < 7; x++) \
-		nm_setting_dcb_set_priority_group_bandwidth (s_dcb, x, 12); \
-	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 7, 16); \
-}
-
-static void
-test_dcb_priorities_valid (void)
-{
-	gs_unref_object NMSettingDcb *s_dcb = NULL;
-	GError *error = NULL;
-	gboolean success;
-	guint i;
-
-	s_dcb = (NMSettingDcb *) nm_setting_dcb_new ();
-	g_assert (s_dcb);
-
-	for (i = 0; i < 8; i++)
-		TEST_PRIORITY_VALID (flow_control, i, TRUE, FLOW_CONTROL, TRUE);
-
-	SET_VALID_PRIORITY_GROUP_BANDWIDTH
-	for (i = 0; i < 8; i++) {
-		TEST_PRIORITY_VALID (group_id, i, i, GROUP, TRUE);
-		TEST_PRIORITY_VALID (group_id, i, 7 - i, GROUP, TRUE);
-	}
-
-	/* Clear PG bandwidth from earlier tests */
-	for (i = 0; i < 8; i++)
-		nm_setting_dcb_set_priority_group_bandwidth (s_dcb, i, 0);
-
-	/* Priority Group Bandwidth must add up to 100% if enabled, which requires
-	 * some dancing for verifying individual values here.
-	 */
-	for (i = 0; i < 8; i++) {
-		guint other = 7 - (i % 8);
-
-		/* Set another priority group to the remaining bandwidth */
-		nm_setting_dcb_set_priority_group_bandwidth (s_dcb, other, 100 - i);
-		TEST_PRIORITY_VALID (group_bandwidth, i, i, GROUP, TRUE);
-
-		/* Set another priority group to the remaining bandwidth */
-		nm_setting_dcb_set_priority_group_bandwidth (s_dcb, other, 100 - (7 - i));
-		TEST_PRIORITY_VALID (group_bandwidth, i, 7 - i, GROUP, TRUE);
-
-		/* Clear remaining bandwidth */
-		nm_setting_dcb_set_priority_group_bandwidth (s_dcb, other, 0);
-	}
-
-	SET_VALID_PRIORITY_GROUP_BANDWIDTH
-	for (i = 0; i < 8; i++) {
-		TEST_PRIORITY_VALID (bandwidth, i, i, GROUP, TRUE);
-		TEST_PRIORITY_VALID (bandwidth, i, 7 - i, GROUP, TRUE);
-	}
-
-	SET_VALID_PRIORITY_GROUP_BANDWIDTH
-	for (i = 0; i < 8; i++)
-		TEST_PRIORITY_VALID (strict_bandwidth, i, TRUE, GROUP, TRUE);
-
-	SET_VALID_PRIORITY_GROUP_BANDWIDTH
-	for (i = 0; i < 8; i++) {
-		TEST_PRIORITY_VALID (traffic_class, i, i, GROUP, TRUE);
-		TEST_PRIORITY_VALID (traffic_class, i, 7 - i, GROUP, TRUE);
-	}
-}
-
-static void
-test_dcb_bandwidth_sums (void)
-{
-	gs_unref_object NMSettingDcb *s_dcb = NULL;
-	GError *error = NULL;
-	gboolean success;
-
-	s_dcb = (NMSettingDcb *) nm_setting_dcb_new ();
-	g_assert (s_dcb);
-
-	/* Assert that setting the value gets the same value back out */
-	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 0, 9);
-	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 1, 10);
-	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 2, 11);
-	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 3, 12);
-	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 4, 13);
-	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 5, 14);
-	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 6, 15);
-	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 7, 16);
-
-	/* Assert verify success when sums total 100% */
-	g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, NM_SETTING_DCB_FLAG_ENABLE, NULL);
-	success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error);
-	g_assert_no_error (error);
-	g_assert (success);
-
-	/* Assert verify fails when sums do not total 100% */
-	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 4, 20);
-	success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error);
-	g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY);
-	g_assert (success == FALSE);
-	g_clear_error (&error);
-}
-
-#define TPATH "/libnm/settings/dcb/"
-
-NMTST_DEFINE ();
-
-int
-main (int argc, char **argv)
-{
-	nmtst_init (&argc, &argv, TRUE);
-
-	g_test_add_func (TPATH "flags-valid", test_dcb_flags_valid);
-	g_test_add_func (TPATH "flags-invalid", test_dcb_flags_invalid);
-	g_test_add_func (TPATH "app-priorities", test_dcb_app_priorities);
-	g_test_add_func (TPATH "priorities", test_dcb_priorities_valid);
-	g_test_add_func (TPATH "bandwidth-sums", test_dcb_bandwidth_sums);
-
-	return g_test_run ();
-}
-
diff --git a/libnm-core/tests/test-setting.c b/libnm-core/tests/test-setting.c
new file mode 100644
index 00000000..efdcee25
--- /dev/null
+++ b/libnm-core/tests/test-setting.c
@@ -0,0 +1,1632 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2008 - 2017 Red Hat, Inc.
+ *
+ */
+
+#include "nm-default.h"
+
+#include <linux/pkt_sched.h>
+#include <string.h>
+
+#include "nm-utils.h"
+#include "nm-setting-8021x.h"
+#include "nm-setting-bond.h"
+#include "nm-setting-dcb.h"
+#include "nm-setting-team.h"
+#include "nm-setting-team-port.h"
+#include "nm-setting-tc-config.h"
+#include "nm-setting-dummy.h"
+#include "nm-connection.h"
+#include "nm-simple-connection.h"
+#include "nm-setting-connection.h"
+#include "nm-errors.h"
+
+#include "nm-utils/nm-test-utils.h"
+
+/*****************************************************************************/
+
+static void
+compare_blob_data (const char *test,
+                   const char *key_path,
+                   GBytes *key)
+{
+	char *contents = NULL;
+	gsize len = 0;
+	GError *error = NULL;
+	gboolean success;
+
+	g_assert (key && g_bytes_get_size (key) > 0);
+
+	success = g_file_get_contents (key_path, &contents, &len, &error);
+	nmtst_assert_success (success, error);
+
+	g_assert_cmpmem (contents, len, g_bytes_get_data (key, NULL), g_bytes_get_size (key));
+
+	g_free (contents);
+}
+
+static void
+check_scheme_path (GBytes *value, const char *path)
+{
+	const guint8 *p;
+
+	g_assert (value);
+
+	p = g_bytes_get_data (value, NULL);
+	g_assert (memcmp (p, NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)) == 0);
+	p += strlen (NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH);
+	g_assert (memcmp (p, path, strlen (path)) == 0);
+	p += strlen (path);
+	g_assert (*p == '\0');
+}
+
+static void
+test_private_key_import (const char *path,
+                         const char *password,
+                         NMSetting8021xCKScheme scheme)
+{
+	NMSetting8021x *s_8021x;
+	gboolean success;
+	NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+	NMSetting8021xCKFormat tmp_fmt;
+	GError *error = NULL;
+	GBytes *tmp_key = NULL, *client_cert = NULL;
+	const char *pw;
+
+	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
+	g_assert (s_8021x);
+
+	success = nm_setting_802_1x_set_private_key (s_8021x,
+	                                             path,
+	                                             password,
+	                                             scheme,
+	                                             &format,
+	                                             &error);
+	nmtst_assert_success (success, error);
+	g_assert (format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
+	tmp_fmt = nm_setting_802_1x_get_private_key_format (s_8021x);
+	g_assert (tmp_fmt == format);
+
+	/* Make sure the password is what we expect */
+	pw = nm_setting_802_1x_get_private_key_password (s_8021x);
+	g_assert (pw != NULL);
+	g_assert_cmpstr (pw, ==, password);
+
+	if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+		tmp_key = nm_setting_802_1x_get_private_key_blob (s_8021x);
+		compare_blob_data ("private-key-import", path, tmp_key);
+	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+		g_object_get (s_8021x, NM_SETTING_802_1X_PRIVATE_KEY, &tmp_key, NULL);
+		check_scheme_path (tmp_key, path);
+		g_bytes_unref (tmp_key);
+	} else
+		g_assert_not_reached ();
+
+	/* If it's PKCS#12 ensure the client cert is the same value */
+	if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
+		g_object_get (s_8021x, NM_SETTING_802_1X_PRIVATE_KEY, &tmp_key, NULL);
+		g_assert (tmp_key);
+
+		g_object_get (s_8021x, NM_SETTING_802_1X_CLIENT_CERT, &client_cert, NULL);
+		g_assert (client_cert);
+
+		/* make sure they are the same */
+		g_assert (g_bytes_equal (tmp_key, client_cert));
+
+		g_bytes_unref (tmp_key);
+		g_bytes_unref (client_cert);
+	}
+
+	g_object_unref (s_8021x);
+}
+
+static void
+test_phase2_private_key_import (const char *path,
+                                const char *password,
+                                NMSetting8021xCKScheme scheme)
+{
+	NMSetting8021x *s_8021x;
+	gboolean success;
+	NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+	NMSetting8021xCKFormat tmp_fmt;
+	GError *error = NULL;
+	GBytes *tmp_key = NULL, *client_cert = NULL;
+	const char *pw;
+
+	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
+	g_assert (s_8021x);
+
+	success = nm_setting_802_1x_set_phase2_private_key (s_8021x,
+	                                                    path,
+	                                                    password,
+	                                                    scheme,
+	                                                    &format,
+	                                                    &error);
+	nmtst_assert_success (success, error);
+	g_assert (format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
+	tmp_fmt = nm_setting_802_1x_get_phase2_private_key_format (s_8021x);
+	g_assert (tmp_fmt == format);
+
+	/* Make sure the password is what we expect */
+	pw = nm_setting_802_1x_get_phase2_private_key_password (s_8021x);
+	g_assert (pw);
+	g_assert_cmpstr (pw, ==, password);
+
+	if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
+		tmp_key = nm_setting_802_1x_get_phase2_private_key_blob (s_8021x);
+		compare_blob_data ("phase2-private-key-import", path, tmp_key);
+	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
+		g_object_get (s_8021x, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, &tmp_key, NULL);
+		check_scheme_path (tmp_key, path);
+		g_bytes_unref (tmp_key);
+	} else
+		g_assert_not_reached ();
+
+	/* If it's PKCS#12 ensure the client cert is the same value */
+	if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
+		g_object_get (s_8021x, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, &tmp_key, NULL);
+		g_assert (tmp_key);
+
+		g_object_get (s_8021x, NM_SETTING_802_1X_PHASE2_CLIENT_CERT, &client_cert, NULL);
+		g_assert (client_cert);
+
+		/* make sure they are the same */
+		g_assert (g_bytes_equal (tmp_key, client_cert));
+
+		g_bytes_unref (tmp_key);
+		g_bytes_unref (client_cert);
+	}
+
+	g_object_unref (s_8021x);
+}
+
+static void
+test_wrong_password_keeps_data (const char *path, const char *password)
+{
+	NMSetting8021x *s_8021x;
+	gboolean success;
+	NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+	GError *error = NULL;
+	const char *pw;
+
+	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
+	g_assert (s_8021x);
+
+	success = nm_setting_802_1x_set_private_key (s_8021x,
+	                                             path,
+	                                             password,
+	                                             NM_SETTING_802_1X_CK_SCHEME_BLOB,
+	                                             &format,
+	                                             &error);
+	nmtst_assert_success (success, error);
+	g_assert (format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
+
+	/* Now try to set it to something that's not a certificate */
+	format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+	success = nm_setting_802_1x_set_private_key (s_8021x,
+	                                             "Makefile.am",
+	                                             password,
+	                                             NM_SETTING_802_1X_CK_SCHEME_BLOB,
+	                                             &format,
+	                                             &error);
+	nmtst_assert_no_success (success, error);
+	g_assert (format == NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
+	g_clear_error (&error);
+
+	/* Make sure the password hasn't changed */
+	pw = nm_setting_802_1x_get_private_key_password (s_8021x);
+	g_assert (pw);
+	g_assert_cmpstr (pw, ==, password);
+
+	g_object_unref (s_8021x);
+}
+
+static void
+test_clear_private_key (const char *path, const char *password)
+{
+	NMSetting8021x *s_8021x;
+	gboolean success;
+	NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+	GError *error = NULL;
+	const char *pw;
+
+	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
+	g_assert (s_8021x);
+
+	success = nm_setting_802_1x_set_private_key (s_8021x,
+	                                             path,
+	                                             password,
+	                                             NM_SETTING_802_1X_CK_SCHEME_BLOB,
+	                                             &format,
+	                                             &error);
+	nmtst_assert_success (success, error);
+	g_assert (format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
+
+	/* Make sure the password is what we expect */
+	pw = nm_setting_802_1x_get_private_key_password (s_8021x);
+	g_assert (pw);
+	g_assert_cmpstr (pw, ==, password);
+
+	/* Now clear it */
+	success = nm_setting_802_1x_set_private_key (s_8021x,
+	                                             NULL,
+	                                             NULL,
+	                                             NM_SETTING_802_1X_CK_SCHEME_BLOB,
+	                                             NULL,
+	                                             &error);
+	nmtst_assert_success (success, error);
+
+	/* Ensure the password is also now clear */
+	g_assert (!nm_setting_802_1x_get_private_key_password (s_8021x));
+
+	g_object_unref (s_8021x);
+}
+
+static void
+test_wrong_phase2_password_keeps_data (const char *path, const char *password)
+{
+	NMSetting8021x *s_8021x;
+	gboolean success;
+	NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+	GError *error = NULL;
+	const char *pw;
+
+	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
+	g_assert (s_8021x);
+
+	success = nm_setting_802_1x_set_phase2_private_key (s_8021x,
+	                                                    path,
+	                                                    password,
+	                                                    NM_SETTING_802_1X_CK_SCHEME_BLOB,
+	                                                    &format,
+	                                                    &error);
+	nmtst_assert_success (success, error);
+	g_assert (format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
+
+	/* Now try to set it to something that's not a certificate */
+	format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+	success = nm_setting_802_1x_set_phase2_private_key (s_8021x,
+	                                                    "Makefile.am",
+	                                                    password,
+	                                                    NM_SETTING_802_1X_CK_SCHEME_BLOB,
+	                                                    &format,
+	                                                    &error);
+	nmtst_assert_no_success (success, error);
+	g_assert (format == NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
+	g_clear_error (&error);
+
+	/* Make sure the password hasn't changed */
+	pw = nm_setting_802_1x_get_phase2_private_key_password (s_8021x);
+	g_assert (pw);
+	g_assert_cmpstr (pw, ==, password);
+
+	g_object_unref (s_8021x);
+}
+
+static void
+test_clear_phase2_private_key (const char *path, const char *password)
+{
+	NMSetting8021x *s_8021x;
+	gboolean success;
+	NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
+	GError *error = NULL;
+	const char *pw;
+
+	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
+	g_assert (s_8021x);
+
+	success = nm_setting_802_1x_set_phase2_private_key (s_8021x,
+	                                                    path,
+	                                                    password,
+	                                                    NM_SETTING_802_1X_CK_SCHEME_BLOB,
+	                                                    &format,
+	                                                    &error);
+	nmtst_assert_success (success, error);
+	g_assert (format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN);
+
+	/* Make sure the password is what we expect */
+	pw = nm_setting_802_1x_get_phase2_private_key_password (s_8021x);
+	g_assert (pw);
+	g_assert_cmpstr (pw, ==, password);
+
+	/* Now clear it */
+	success = nm_setting_802_1x_set_phase2_private_key (s_8021x,
+	                                                    NULL,
+	                                                    NULL,
+	                                                    NM_SETTING_802_1X_CK_SCHEME_BLOB,
+	                                                    NULL,
+	                                                    &error);
+	nmtst_assert_success (success, error);
+
+	/* Ensure the password is also now clear */
+	g_assert (!nm_setting_802_1x_get_phase2_private_key_password (s_8021x));
+
+	g_object_unref (s_8021x);
+}
+
+static void
+test_8021x (gconstpointer test_data)
+{
+	char **parts, *path, *password;
+
+	parts = g_strsplit ((const char *) test_data, ", ", -1);
+	g_assert_cmpint (g_strv_length (parts), ==, 2);
+
+	path = g_build_filename (TEST_CERT_DIR, parts[0], NULL);
+	password = parts[1];
+
+	/* Test phase1 and phase2 path scheme */
+	test_private_key_import (path, password, NM_SETTING_802_1X_CK_SCHEME_PATH);
+	test_phase2_private_key_import (path, password, NM_SETTING_802_1X_CK_SCHEME_PATH);
+
+	/* Test phase1 and phase2 blob scheme */
+	test_private_key_import (path, password, NM_SETTING_802_1X_CK_SCHEME_BLOB);
+	test_phase2_private_key_import (path, password, NM_SETTING_802_1X_CK_SCHEME_BLOB);
+
+	/* Test that using a wrong password does not change existing data */
+	test_wrong_password_keeps_data (path, password);
+	test_wrong_phase2_password_keeps_data (path, password);
+
+	/* Test clearing the private key */
+	test_clear_private_key (path, password);
+	test_clear_phase2_private_key (path, password);
+
+	g_free (path);
+	g_strfreev (parts);
+}
+
+/*****************************************************************************/
+
+static void
+create_bond_connection (NMConnection **con, NMSettingBond **s_bond)
+{
+	NMSettingConnection *s_con;
+
+	g_assert (con);
+	g_assert (s_bond);
+
+	*con = nmtst_create_minimal_connection ("bond",
+	                                        NULL,
+	                                        NM_SETTING_BOND_SETTING_NAME,
+	                                        &s_con);
+	g_assert (*con);
+	g_assert (s_con);
+
+	g_object_set (s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "bond0", NULL);
+
+	*s_bond = (NMSettingBond *) nm_setting_bond_new ();
+	g_assert (*s_bond);
+
+	nm_connection_add_setting (*con, NM_SETTING (*s_bond));
+}
+
+#define test_verify_options(exp, ...) \
+	G_STMT_START { \
+		const char *__opts[] = { __VA_ARGS__ , NULL }; \
+		\
+		_test_verify_options (__opts, exp); \
+	} G_STMT_END
+
+static void
+_test_verify_options (const char **options, gboolean expected_result)
+{
+	gs_unref_object NMConnection *con = NULL;
+	NMSettingBond *s_bond;
+	GError *error = NULL;
+	gboolean success;
+	const char **option;
+
+	create_bond_connection (&con, &s_bond);
+
+	for (option = options; option[0] && option[1]; option += 2)
+		g_assert (nm_setting_bond_add_option (s_bond, option[0], option[1]));
+
+	if (expected_result) {
+		nmtst_assert_connection_verifies_and_normalizable (con);
+		nmtst_connection_normalize (con);
+		success = nm_setting_verify ((NMSetting *) s_bond, con, &error);
+		nmtst_assert_success (success, error);
+	} else {
+		nmtst_assert_connection_unnormalizable (con,
+		                                        NM_CONNECTION_ERROR,
+		                                        NM_CONNECTION_ERROR_INVALID_PROPERTY);
+	}
+}
+
+static void
+test_bond_verify (void)
+{
+	test_verify_options (TRUE,
+	                     "mode", "3",
+	                     "arp_interval", "0");
+	test_verify_options (FALSE,
+	                     /* arp_interval not supported in balance-alb mode */
+	                     "mode", "balance-alb",
+	                     "arp_interval", "1",
+	                     "arp_ip_target", "1.2.3.4");
+	test_verify_options (FALSE,
+	                     /* arp_ip_target requires arp_interval */
+	                     "mode", "balance-rr",
+	                     "arp_ip_target", "1.2.3.4");
+	test_verify_options (TRUE,
+	                     "mode", "balance-rr",
+	                     "arp_interval", "1",
+	                     "arp_ip_target", "1.2.3.4");
+	test_verify_options (FALSE,
+	                     /* num_grat_arp, num_unsol_na cannot be different */
+	                     "mode", "balance-rr",
+	                     "num_grat_arp", "3",
+	                     "num_unsol_na", "4");
+	test_verify_options (TRUE,
+	                     "mode", "balance-rr",
+	                     "num_grat_arp", "5",
+	                     "num_unsol_na", "5");
+	test_verify_options (TRUE,
+	                     "mode", "active-backup",
+	                     "primary", "eth0");
+	test_verify_options (FALSE,
+	                     /* primary requires mode=active-backup */
+	                     "mode", "802.3ad",
+	                     "primary", "eth0");
+	test_verify_options (TRUE,
+	                     "mode", "802.3ad",
+	                     "lacp_rate", "fast");
+	test_verify_options (FALSE,
+	                     /* lacp_rate=fast requires mode=802.3ad */
+	                     "mode", "balance-rr",
+	                     "lacp_rate", "fast");
+	test_verify_options (TRUE,
+	                     "mode", "802.3ad",
+	                     "ad_actor_system", "ae:00:11:33:44:55");
+}
+
+static void
+test_bond_compare_options (gboolean exp_res, const char **opts1, const char **opts2)
+{
+	gs_unref_object NMSettingBond *s_bond1 = NULL, *s_bond2 = NULL;
+	const char **p;
+
+	s_bond1 = (NMSettingBond *) nm_setting_bond_new ();
+	g_assert (s_bond1);
+	s_bond2 = (NMSettingBond *) nm_setting_bond_new ();
+	g_assert (s_bond2);
+
+	for (p = opts1; p[0] && p[1]; p += 2)
+		g_assert (nm_setting_bond_add_option (s_bond1, p[0], p[1]));
+
+	for (p = opts2; p[0] && p[1]; p += 2)
+		g_assert (nm_setting_bond_add_option (s_bond2, p[0], p[1]));
+
+	g_assert_cmpint (nm_setting_compare ((NMSetting *) s_bond1,
+	                                     (NMSetting *) s_bond2,
+	                                     NM_SETTING_COMPARE_FLAG_EXACT),
+	                 ==,
+	                 exp_res);
+}
+
+static void
+test_bond_compare (void)
+{
+	test_bond_compare_options (TRUE,
+	                           ((const char *[]){ "mode", "balance-rr", "miimon", "1", NULL }),
+	                           ((const char *[]){ "mode", "balance-rr", "miimon", "1", NULL }));
+	test_bond_compare_options (FALSE,
+	                           ((const char *[]){ "mode", "balance-rr", "miimon", "1", NULL }),
+	                           ((const char *[]){ "mode", "balance-rr", "miimon", "2", NULL }));
+
+	/* ignore default values */
+	test_bond_compare_options (TRUE,
+	                           ((const char *[]){ "miimon", "1", NULL }),
+	                           ((const char *[]){ "miimon", "1", "updelay", "0", NULL }));
+
+	/* special handling of num_grat_arp, num_unsol_na */
+	test_bond_compare_options (FALSE,
+	                           ((const char *[]){ "num_grat_arp", "2", NULL }),
+	                           ((const char *[]){ "num_grat_arp", "1", NULL }));
+	test_bond_compare_options (TRUE,
+	                           ((const char *[]){ "num_grat_arp", "3", NULL }),
+	                           ((const char *[]){ "num_unsol_na", "3", NULL }));
+	test_bond_compare_options (TRUE,
+	                           ((const char *[]){ "num_grat_arp", "4", NULL }),
+	                           ((const char *[]){ "num_unsol_na", "4", "num_grat_arp", "4", NULL }));
+}
+
+static void
+test_bond_normalize_options (const char **opts1, const char **opts2)
+{
+	gs_unref_object NMConnection *con = NULL;
+	NMSettingBond *s_bond;
+	GError *error = NULL;
+	gboolean success;
+	const char **p;
+	int num = 0;
+
+	create_bond_connection (&con, &s_bond);
+
+	for (p = opts1; p[0] && p[1]; p += 2)
+		g_assert (nm_setting_bond_add_option (s_bond, p[0], p[1]));
+
+	nmtst_assert_connection_verifies_and_normalizable (con);
+	nmtst_connection_normalize (con);
+	success = nm_setting_verify ((NMSetting *) s_bond, con, &error);
+	nmtst_assert_success (success, error);
+
+	for (p = opts2; p[0] && p[1]; p += 2) {
+		g_assert_cmpstr (nm_setting_bond_get_option_by_name (s_bond, p[0]), ==, p[1]);
+		num++;
+	}
+
+	g_assert_cmpint (num, ==, nm_setting_bond_get_num_options (s_bond));
+}
+
+static void
+test_bond_normalize (void)
+{
+	test_bond_normalize_options (
+		((const char *[]){ "mode", "802.3ad", "ad_actor_system", "00:02:03:04:05:06", NULL }),
+		((const char *[]){ "mode", "802.3ad", "ad_actor_system", "00:02:03:04:05:06", NULL }));
+	test_bond_normalize_options (
+		((const char *[]){ "mode", "1", "miimon", "1", NULL }),
+		((const char *[]){ "mode", "active-backup", "miimon", "1", NULL }));
+	test_bond_normalize_options (
+		((const char *[]){ "mode", "balance-alb", "tlb_dynamic_lb", "1", NULL }),
+		((const char *[]){ "mode", "balance-alb", NULL }));
+	test_bond_normalize_options (
+		((const char *[]){ "mode", "balance-tlb", "tlb_dynamic_lb", "1", NULL }),
+		((const char *[]){ "mode", "balance-tlb", "tlb_dynamic_lb", "1", NULL }));
+	test_bond_normalize_options (
+		((const char *[]){ "mode", "balance-rr", "ad_actor_sys_prio", "4", "packets_per_slave", "3", NULL }),
+		((const char *[]){ "mode", "balance-rr", "packets_per_slave", "3", NULL }));
+}
+
+/*****************************************************************************/
+
+#define DCB_FLAGS_ALL (NM_SETTING_DCB_FLAG_ENABLE | \
+                       NM_SETTING_DCB_FLAG_ADVERTISE | \
+                       NM_SETTING_DCB_FLAG_WILLING)
+
+
+static void
+test_dcb_flags_valid (void)
+{
+	gs_unref_object NMSettingDcb *s_dcb = NULL;
+	GError *error = NULL;
+	gboolean success;
+	guint i;
+
+	s_dcb = (NMSettingDcb *) nm_setting_dcb_new ();
+	g_assert (s_dcb);
+
+	g_assert_cmpint (nm_setting_dcb_get_app_fcoe_flags (s_dcb), ==, 0);
+	g_assert_cmpint (nm_setting_dcb_get_app_iscsi_flags (s_dcb), ==, 0);
+	g_assert_cmpint (nm_setting_dcb_get_app_fip_flags (s_dcb), ==, 0);
+	g_assert_cmpint (nm_setting_dcb_get_priority_flow_control_flags (s_dcb), ==, 0);
+	g_assert_cmpint (nm_setting_dcb_get_priority_group_flags (s_dcb), ==, 0);
+
+	g_object_set (G_OBJECT (s_dcb),
+	              NM_SETTING_DCB_APP_FCOE_FLAGS, DCB_FLAGS_ALL,
+	              NM_SETTING_DCB_APP_ISCSI_FLAGS, DCB_FLAGS_ALL,
+	              NM_SETTING_DCB_APP_FIP_FLAGS, DCB_FLAGS_ALL,
+	              NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, DCB_FLAGS_ALL,
+	              NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, DCB_FLAGS_ALL,
+	              NULL);
+	/* Priority Group Bandwidth must total 100% */
+	for (i = 0; i < 7; i++)
+		nm_setting_dcb_set_priority_group_bandwidth (s_dcb, i, 12);
+	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 7, 16);
+
+	success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	g_assert_cmpint (nm_setting_dcb_get_app_fcoe_flags (s_dcb), ==, DCB_FLAGS_ALL);
+	g_assert_cmpint (nm_setting_dcb_get_app_iscsi_flags (s_dcb), ==, DCB_FLAGS_ALL);
+	g_assert_cmpint (nm_setting_dcb_get_app_fip_flags (s_dcb), ==, DCB_FLAGS_ALL);
+	g_assert_cmpint (nm_setting_dcb_get_priority_flow_control_flags (s_dcb), ==, DCB_FLAGS_ALL);
+	g_assert_cmpint (nm_setting_dcb_get_priority_group_flags (s_dcb), ==, DCB_FLAGS_ALL);
+}
+
+#define TEST_FLAG(p, f, v) \
+{ \
+	/* GObject property min/max should ensure the property does not get set to \
+	 * the invalid value, so we ensure the value we just tried to set is 0 and \
+	 * that verify is successful since the property never got set. \
+	 */ \
+	g_object_set (G_OBJECT (s_dcb), p, v, NULL); \
+	g_assert_cmpint (f (s_dcb), ==, 0); \
+	success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error); \
+	g_assert_no_error (error); \
+	g_assert (success); \
+}
+
+static void
+test_dcb_flags_invalid (void)
+{
+	gs_unref_object NMSettingDcb *s_dcb = NULL;
+	GError *error = NULL;
+	gboolean success;
+
+	s_dcb = (NMSettingDcb *) nm_setting_dcb_new ();
+	g_assert (s_dcb);
+
+	g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*");
+	TEST_FLAG (NM_SETTING_DCB_APP_FCOE_FLAGS, nm_setting_dcb_get_app_fcoe_flags, 0x332523);
+	g_test_assert_expected_messages ();
+
+	g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*");
+	TEST_FLAG (NM_SETTING_DCB_APP_ISCSI_FLAGS, nm_setting_dcb_get_app_iscsi_flags, 0xFF);
+	g_test_assert_expected_messages ();
+
+	g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*");
+	TEST_FLAG (NM_SETTING_DCB_APP_FIP_FLAGS, nm_setting_dcb_get_app_fip_flags, 0x1111);
+	g_test_assert_expected_messages ();
+
+	g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*");
+	TEST_FLAG (NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, nm_setting_dcb_get_priority_flow_control_flags, G_MAXUINT32);
+	g_test_assert_expected_messages ();
+
+	g_test_expect_message ("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*");
+	TEST_FLAG (NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, nm_setting_dcb_get_priority_group_flags,
+	           (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE | NM_SETTING_DCB_FLAG_WILLING) + 1);
+	g_test_assert_expected_messages ();
+}
+
+#define TEST_APP_PRIORITY(lcprop, ucprop, v) \
+{ \
+	g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_APP_##ucprop##_FLAGS, NM_SETTING_DCB_FLAG_NONE, NULL); \
+ \
+	g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_APP_##ucprop##_PRIORITY, v, NULL); \
+	g_assert_cmpint (nm_setting_dcb_get_app_##lcprop##_priority (s_dcb), ==, v); \
+ \
+	/* Assert that the setting is invalid while the app is disabled unless v is default */ \
+	success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error); \
+	if (v >= 0) { \
+		g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); \
+		g_assert (success == FALSE); \
+	} else { \
+		g_assert_no_error (error); \
+		g_assert (success); \
+	} \
+	g_clear_error (&error); \
+ \
+	/* Set the enable flag and re-verify, this time it should be valid */ \
+	g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_APP_##ucprop##_FLAGS, NM_SETTING_DCB_FLAG_ENABLE, NULL); \
+	success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error); \
+	g_assert_no_error (error); \
+	g_assert (success); \
+ \
+	g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_APP_##ucprop##_PRIORITY, 0, NULL); \
+}
+
+static void
+test_dcb_app_priorities (void)
+{
+	gs_unref_object NMSettingDcb *s_dcb = NULL;
+	GError *error = NULL;
+	gboolean success;
+
+	s_dcb = (NMSettingDcb *) nm_setting_dcb_new ();
+	g_assert (s_dcb);
+
+	/* Defaults */
+	g_assert_cmpint (nm_setting_dcb_get_app_fcoe_priority (s_dcb), ==, -1);
+	g_assert_cmpint (nm_setting_dcb_get_app_iscsi_priority (s_dcb), ==, -1);
+	g_assert_cmpint (nm_setting_dcb_get_app_fip_priority (s_dcb), ==, -1);
+
+	TEST_APP_PRIORITY (fcoe, FCOE, 6);
+	TEST_APP_PRIORITY (iscsi, ISCSI, 5);
+	TEST_APP_PRIORITY (fip, FIP, 4);
+
+	TEST_APP_PRIORITY (fcoe, FCOE, -1);
+	TEST_APP_PRIORITY (iscsi, ISCSI, -1);
+	TEST_APP_PRIORITY (fip, FIP, -1);
+}
+
+#define TEST_PRIORITY_VALID(fn, id, val, flagsprop, verify) \
+{ \
+	/* Assert that setting the value gets the same value back out */ \
+	nm_setting_dcb_set_priority_##fn (s_dcb, id, val); \
+	g_assert_cmpint (nm_setting_dcb_get_priority_##fn (s_dcb, id), ==, val); \
+ \
+	if (verify) { \
+		if (val != 0) { \
+			/* Assert that verify fails because the flags do not include 'enabled' \
+			 * and a value has been set. \
+			 */ \
+			success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error); \
+			g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); \
+			g_assert (success == FALSE); \
+			g_clear_error (&error); \
+		} \
+ \
+		/* Assert that adding the 'enabled' flag verifies the setting */ \
+		g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_PRIORITY_##flagsprop##_FLAGS, NM_SETTING_DCB_FLAG_ENABLE, NULL); \
+		success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error); \
+		g_assert_no_error (error); \
+		g_assert (success); \
+	} \
+ \
+	/* Reset everything */ \
+	g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_PRIORITY_##flagsprop##_FLAGS, NM_SETTING_DCB_FLAG_NONE, NULL); \
+	nm_setting_dcb_set_priority_##fn (s_dcb, id, 0); \
+}
+
+/* If Priority Groups are enabled, PG bandwidth must equal 100% */
+#define SET_VALID_PRIORITY_GROUP_BANDWIDTH \
+{ \
+	guint x; \
+	for (x = 0; x < 7; x++) \
+		nm_setting_dcb_set_priority_group_bandwidth (s_dcb, x, 12); \
+	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 7, 16); \
+}
+
+static void
+test_dcb_priorities_valid (void)
+{
+	gs_unref_object NMSettingDcb *s_dcb = NULL;
+	GError *error = NULL;
+	gboolean success;
+	guint i;
+
+	s_dcb = (NMSettingDcb *) nm_setting_dcb_new ();
+	g_assert (s_dcb);
+
+	for (i = 0; i < 8; i++)
+		TEST_PRIORITY_VALID (flow_control, i, TRUE, FLOW_CONTROL, TRUE);
+
+	SET_VALID_PRIORITY_GROUP_BANDWIDTH
+	for (i = 0; i < 8; i++) {
+		TEST_PRIORITY_VALID (group_id, i, i, GROUP, TRUE);
+		TEST_PRIORITY_VALID (group_id, i, 7 - i, GROUP, TRUE);
+	}
+
+	/* Clear PG bandwidth from earlier tests */
+	for (i = 0; i < 8; i++)
+		nm_setting_dcb_set_priority_group_bandwidth (s_dcb, i, 0);
+
+	/* Priority Group Bandwidth must add up to 100% if enabled, which requires
+	 * some dancing for verifying individual values here.
+	 */
+	for (i = 0; i < 8; i++) {
+		guint other = 7 - (i % 8);
+
+		/* Set another priority group to the remaining bandwidth */
+		nm_setting_dcb_set_priority_group_bandwidth (s_dcb, other, 100 - i);
+		TEST_PRIORITY_VALID (group_bandwidth, i, i, GROUP, TRUE);
+
+		/* Set another priority group to the remaining bandwidth */
+		nm_setting_dcb_set_priority_group_bandwidth (s_dcb, other, 100 - (7 - i));
+		TEST_PRIORITY_VALID (group_bandwidth, i, 7 - i, GROUP, TRUE);
+
+		/* Clear remaining bandwidth */
+		nm_setting_dcb_set_priority_group_bandwidth (s_dcb, other, 0);
+	}
+
+	SET_VALID_PRIORITY_GROUP_BANDWIDTH
+	for (i = 0; i < 8; i++) {
+		TEST_PRIORITY_VALID (bandwidth, i, i, GROUP, TRUE);
+		TEST_PRIORITY_VALID (bandwidth, i, 7 - i, GROUP, TRUE);
+	}
+
+	SET_VALID_PRIORITY_GROUP_BANDWIDTH
+	for (i = 0; i < 8; i++)
+		TEST_PRIORITY_VALID (strict_bandwidth, i, TRUE, GROUP, TRUE);
+
+	SET_VALID_PRIORITY_GROUP_BANDWIDTH
+	for (i = 0; i < 8; i++) {
+		TEST_PRIORITY_VALID (traffic_class, i, i, GROUP, TRUE);
+		TEST_PRIORITY_VALID (traffic_class, i, 7 - i, GROUP, TRUE);
+	}
+}
+
+static void
+test_dcb_bandwidth_sums (void)
+{
+	gs_unref_object NMSettingDcb *s_dcb = NULL;
+	GError *error = NULL;
+	gboolean success;
+
+	s_dcb = (NMSettingDcb *) nm_setting_dcb_new ();
+	g_assert (s_dcb);
+
+	/* Assert that setting the value gets the same value back out */
+	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 0, 9);
+	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 1, 10);
+	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 2, 11);
+	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 3, 12);
+	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 4, 13);
+	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 5, 14);
+	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 6, 15);
+	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 7, 16);
+
+	/* Assert verify success when sums total 100% */
+	g_object_set (G_OBJECT (s_dcb), NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, NM_SETTING_DCB_FLAG_ENABLE, NULL);
+	success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	/* Assert verify fails when sums do not total 100% */
+	nm_setting_dcb_set_priority_group_bandwidth (s_dcb, 4, 20);
+	success = nm_setting_verify (NM_SETTING (s_dcb), NULL, &error);
+	g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY);
+	g_assert (success == FALSE);
+	g_clear_error (&error);
+}
+
+/*****************************************************************************/
+
+static void
+_test_team_config_sync (const char *team_config,
+                        int notify_peer_count,
+                        int notify_peers_interval,
+                        int mcast_rejoin_count,
+                        int mcast_rejoin_interval,
+                        char *runner,
+                        char *runner_hwaddr_policy,      /* activebackup */
+                        GPtrArray *runner_tx_hash,       /* lacp, loadbalance */
+                        char *runner_tx_balancer,        /* lacp, loadbalance */
+                        int runner_tx_balancer_interval, /* lacp, loadbalance */
+                        gboolean runner_active,          /* lacp */
+                        gboolean runner_fast_rate,       /* lacp */
+                        int runner_sys_prio,             /* lacp */
+                        int runner_min_ports,            /* lacp */
+                        char *runner_agg_select_policy,  /* lacp */
+                        GPtrArray *link_watchers)
+{
+	gs_unref_object NMSettingTeam *s_team = NULL;
+	guint i, j;
+	gboolean found;
+
+	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));
+
+	if (runner_tx_hash) {
+		g_assert (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++) {
+				if (nm_streq0 (nm_setting_team_get_runner_tx_hash (s_team, j),
+				               runner_tx_hash->pdata[i])) {
+					found = TRUE;
+					break;
+				}
+			}
+			g_assert (found);
+		}
+	}
+
+	if (link_watchers) {
+		g_assert (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++) {
+				if (nm_team_link_watcher_equal (link_watchers->pdata[i],
+				                                nm_setting_team_get_link_watcher (s_team, j))) {
+					found = TRUE;
+					break;
+				}
+			}
+			g_assert (found);
+		}
+	}
+
+	g_assert (nm_setting_verify ((NMSetting *) s_team, NULL, NULL));
+}
+
+
+static void
+test_runner_roundrobin_sync_from_config (void)
+{
+	_test_team_config_sync ("",
+	                        0, 0, 0, 0,
+	                        NM_SETTING_TEAM_RUNNER_ROUNDROBIN,
+	                        NULL,
+	                        NULL, NULL, -1,
+	                        FALSE, FALSE, -1, -1, NULL,
+	                        NULL);
+}
+
+static void
+test_runner_broadcast_sync_from_config (void)
+{
+	_test_team_config_sync ("{\"runner\": {\"name\": \"broadcast\"}}",
+	                        0, 0, 0, 0,
+	                        NM_SETTING_TEAM_RUNNER_BROADCAST,
+	                        NULL,
+	                        NULL, NULL, -1,
+	                        FALSE, FALSE, -1, -1, NULL,
+	                        NULL);
+}
+
+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,
+	                        NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP,
+	                        NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_DEFAULT,
+	                        NULL, NULL, -1,
+	                        FALSE, FALSE, -1, -1, NULL,
+	                        NULL);
+}
+
+static void
+test_runner_loadbalance_sync_from_config (void)
+{
+	gs_unref_ptrarray GPtrArray *tx_hash = NULL;
+
+	tx_hash = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
+	g_ptr_array_add (tx_hash, g_strdup ("eth"));
+	g_ptr_array_add (tx_hash, g_strdup ("ipv4"));
+	g_ptr_array_add (tx_hash, g_strdup ("ipv6"));
+
+	_test_team_config_sync ("{\"runner\": {\"name\": \"loadbalance\"}}",
+	                        0, 0, 0, 0,
+	                        NM_SETTING_TEAM_RUNNER_LOADBALANCE,
+	                        NULL,
+	                        tx_hash, NULL, NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL_DEFAULT,
+	                        FALSE, FALSE, -1, -1, NULL,
+	                        NULL);
+
+	_test_team_config_sync ("{\"runner\": {\"name\": \"loadbalance\", "
+	                        "\"tx_hash\": [\"eth\", \"ipv4\", \"ipv6\"]}}",
+	                        0, 0, 0, 0,
+	                        NM_SETTING_TEAM_RUNNER_LOADBALANCE,
+	                        NULL,
+	                        tx_hash, NULL, NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL_DEFAULT,
+	                        FALSE, 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,
+	                        NM_SETTING_TEAM_RUNNER_LOADBALANCE,
+	                        NULL,
+	                        tx_hash, "basic", 30,
+	                        FALSE, FALSE, -1, -1, NULL,
+	                        NULL);
+}
+
+static void
+test_runner_lacp_sync_from_config (void)
+{
+	gs_unref_ptrarray GPtrArray *tx_hash = NULL;
+
+	tx_hash = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
+	g_ptr_array_add (tx_hash, g_strdup ("eth"));
+	g_ptr_array_add (tx_hash, g_strdup ("ipv4"));
+	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,
+	                        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,
+	                        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,
+	                        NM_SETTING_TEAM_RUNNER_LACP,
+	                        NULL,
+	                        tx_hash, NULL, NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL_DEFAULT,
+	                        FALSE, TRUE, 10, 5, "port_config",
+	                        NULL);
+}
+
+static void
+test_watcher_ethtool_sync_from_config (void)
+{
+	gs_unref_ptrarray GPtrArray *link_watchers = NULL;
+
+	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",
+	                        NULL,
+	                        NULL, NULL, -1,
+	                        FALSE, FALSE, -1, -1, NULL,
+	                        link_watchers);
+}
+
+static void
+test_watcher_nsna_ping_sync_from_config (void)
+{
+	gs_unref_ptrarray GPtrArray *link_watchers = NULL;
+
+	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",
+	                        NULL,
+	                        NULL, NULL, -1,
+	                        FALSE, FALSE, -1, -1, NULL,
+	                        link_watchers);
+}
+
+static void
+test_watcher_arp_ping_sync_from_config (void)
+{
+	gs_unref_ptrarray GPtrArray *link_watchers = NULL;
+
+	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_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",
+	                        NULL,
+	                        NULL, NULL, -1,
+	                        FALSE, FALSE, -1, -1, NULL,
+	                        link_watchers);
+}
+
+static void
+test_multiple_watchers_sync_from_config (void)
+{
+	gs_unref_ptrarray GPtrArray *link_watchers = NULL;
+
+	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 (2, 4, NULL));
+	g_ptr_array_add (link_watchers, nm_team_link_watcher_new_nsna_ping (3, 6, 9, "target.host", NULL));
+	g_ptr_array_add (link_watchers,
+	                 nm_team_link_watcher_new_arp_ping (5, 10, 15, "target.host", "source.host",
+	                                                      NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE
+	                                                    | NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE
+	                                                    | NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS,
+	                                                    NULL));
+	_test_team_config_sync ("{\"link_watch\": ["
+	                        "{\"name\": \"ethtool\", \"delay_up\": 2, \"delay_down\": 4}, "
+	                        "{\"name\": \"arp_ping\", \"init_wait\": 5, \"interval\": 10, \"missed_max\": 15, "
+	                        "\"target_host\": \"target.host\", \"source_host\": \"source.host\", "
+	                        "\"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",
+	                        NULL,
+	                        NULL, NULL, -1,
+	                        FALSE, FALSE, -1, -1, NULL,
+	                        link_watchers);
+}
+
+/*****************************************************************************/
+
+static void
+_test_team_port_config_sync (const char *team_port_config,
+                             int queue_id,
+                             int prio,
+                             gboolean sticky,
+                             int lacp_prio,
+                             int lacp_key,
+                             GPtrArray *link_watchers)
+{
+	gs_unref_object NMSettingTeamPort *s_team_port = NULL;
+	guint i, j;
+	gboolean found;
+
+	s_team_port = (NMSettingTeamPort *) nm_setting_team_port_new ();
+	g_assert (s_team_port);
+
+	g_object_set (s_team_port, NM_SETTING_TEAM_CONFIG, team_port_config, NULL);
+	g_assert (nm_setting_team_port_get_queue_id (s_team_port) == queue_id);
+	g_assert (nm_setting_team_port_get_prio (s_team_port) == prio);
+	g_assert (nm_setting_team_port_get_sticky (s_team_port) == sticky);
+	g_assert (nm_setting_team_port_get_lacp_prio (s_team_port) == lacp_prio);
+	g_assert (nm_setting_team_port_get_lacp_key (s_team_port) == lacp_key);
+
+	if (link_watchers) {
+		g_assert (link_watchers->len == nm_setting_team_port_get_num_link_watchers (s_team_port));
+		for (i = 0; i < link_watchers->len; i++) {
+			found = FALSE;
+			for (j = 0; j < nm_setting_team_port_get_num_link_watchers (s_team_port); j++) {
+				if (nm_team_link_watcher_equal (link_watchers->pdata[i],
+				                                nm_setting_team_port_get_link_watcher (s_team_port,
+				                                                                       j))) {
+					found = TRUE;
+					break;
+				}
+			}
+			g_assert (found);
+		}
+	}
+
+	g_assert (nm_setting_verify ((NMSetting *) s_team_port, NULL, NULL));
+}
+
+
+static void
+test_team_port_default (void)
+{
+	_test_team_port_config_sync ("", -1, 0, FALSE, 255, 0, NULL);
+}
+
+static void
+test_team_port_queue_id (void)
+{
+	_test_team_port_config_sync ("{\"queue_id\": 3}",
+	                             3, 0, FALSE, 255, 0, NULL);
+	_test_team_port_config_sync ("{\"queue_id\": 0}",
+	                             0, 0, FALSE, 255, 0, NULL);
+}
+
+static void
+test_team_port_prio (void)
+{
+	_test_team_port_config_sync ("{\"prio\": 6}",
+	                             -1, 6, FALSE, 255, 0, NULL);
+	_test_team_port_config_sync ("{\"prio\": 0}",
+	                             -1, 0, FALSE, 255, 0, NULL);
+}
+
+static void
+test_team_port_sticky (void)
+{
+	_test_team_port_config_sync ("{\"sticky\": true}",
+	                             -1, 0, TRUE, 255, 0, NULL);
+	_test_team_port_config_sync ("{\"sticky\": false}",
+	                             -1, 0, FALSE, 255, 0, NULL);
+}
+
+static void
+test_team_port_lacp_prio (void)
+{
+	_test_team_port_config_sync ("{\"lacp_prio\": 9}",
+	                             -1, 0, FALSE, 9, 0, NULL);
+	_test_team_port_config_sync ("{\"lacp_prio\": 0}",
+	                             -1, 0, FALSE, 0, 0, NULL);
+}
+
+static void
+test_team_port_lacp_key (void)
+{
+	_test_team_port_config_sync ("{\"lacp_key\": 12}",
+	                             -1, 0, FALSE, 255, 12, NULL);
+	_test_team_port_config_sync ("{\"lacp_key\": 0}",
+	                             -1, 0, FALSE, 255, 0, NULL);
+}
+
+static void
+test_team_port_full_config (void)
+{
+	gs_unref_ptrarray GPtrArray *link_watchers = NULL;
+
+	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_arp_ping (0, 3, 3, "1.2.3.2", "1.2.3.1",
+	                                                    NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE,
+	                                                    NULL));
+	g_ptr_array_add (link_watchers,
+	                 nm_team_link_watcher_new_arp_ping (1, 1, 0, "1.2.3.4", "1.2.3.1",
+	                                                     NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS,
+	                                                     NULL));
+
+	_test_team_port_config_sync ("{\"queue_id\": 10, \"prio\": 20, \"sticky\": true, \"lacp_prio\": 30, "
+	                             "\"lacp_key\": 40, \"link_watch\": ["
+	                             "{\"name\": \"arp_ping\", \"interval\": 3, \"target_host\": \"1.2.3.2\", "
+	                             "\"source_host\": \"1.2.3.1\", \"validate_inactive\": true}, "
+	                             "{\"name\": \"arp_ping\", \"init_wait\": 1, \"interval\": 1, "
+	                             "\"target_host\": \"1.2.3.4\", \"source_host\": \"1.2.3.1\", "
+	                             "\"send_always\": true}]}",
+	                             10, 20, true, 30, 40, NULL);
+}
+
+/*****************************************************************************/
+
+static void
+test_tc_config_qdisc (void)
+{
+	NMTCQdisc *qdisc1, *qdisc2;
+	char *str;
+	GError *error = NULL;
+
+	qdisc1 = nm_tc_qdisc_new ("fq_codel", TC_H_ROOT, &error);
+	nmtst_assert_success (qdisc1, error);
+
+	qdisc2 = nm_tc_qdisc_new ("fq_codel", TC_H_ROOT, &error);
+	nmtst_assert_success (qdisc2, error);
+
+	g_assert (nm_tc_qdisc_equal (qdisc1, qdisc2));
+
+	nm_tc_qdisc_unref (qdisc2);
+	qdisc2 = nm_tc_qdisc_dup (qdisc1);
+
+	g_assert (nm_tc_qdisc_equal (qdisc1, qdisc2));
+
+	g_assert_cmpstr (nm_tc_qdisc_get_kind (qdisc1), ==, "fq_codel");
+	g_assert (nm_tc_qdisc_get_handle (qdisc1) == TC_H_UNSPEC);
+	g_assert (nm_tc_qdisc_get_parent (qdisc1) == TC_H_ROOT);
+
+	str = nm_utils_tc_qdisc_to_str (qdisc1, &error);
+	nmtst_assert_success (str, error);
+	g_assert_cmpstr (str, ==, "root fq_codel");
+	g_free (str);
+
+	nm_tc_qdisc_unref (qdisc1);
+	qdisc1 = nm_tc_qdisc_new ("ingress", TC_H_INGRESS, &error);
+	nmtst_assert_success (qdisc1, error);
+
+	g_assert (!nm_tc_qdisc_equal (qdisc1, qdisc2));
+
+	str = nm_utils_tc_qdisc_to_str (qdisc1, &error);
+	nmtst_assert_success (str, error);
+	g_assert_cmpstr (str, ==, "ingress");
+	g_free (str);
+
+	nm_tc_qdisc_unref (qdisc1);
+	qdisc1 = nm_utils_tc_qdisc_from_str ("narodil sa kristus pan",  &error);
+	nmtst_assert_no_success (qdisc1, error);
+	g_clear_error (&error);
+
+	qdisc1 = nm_utils_tc_qdisc_from_str ("handle 1234 parent fff1:1 pfifo_fast",  &error);
+	nmtst_assert_success (qdisc1, error);
+
+	g_assert_cmpstr (nm_tc_qdisc_get_kind (qdisc1), ==, "pfifo_fast");
+	g_assert (nm_tc_qdisc_get_handle (qdisc1) == TC_H_MAKE (0x1234 << 16, 0x0000));
+	g_assert (nm_tc_qdisc_get_parent (qdisc1) == TC_H_MAKE (0xfff1 << 16, 0x0001));
+
+	str = nm_utils_tc_qdisc_to_str (qdisc1, &error);
+	nmtst_assert_success (str, error);
+	g_assert_cmpstr (str, ==, "parent fff1:1 handle 1234: pfifo_fast");
+	g_free (str);
+
+	nm_tc_qdisc_unref (qdisc2);
+	str = nm_utils_tc_qdisc_to_str (qdisc1, &error);
+	nmtst_assert_success (str, error);
+	qdisc2 = nm_utils_tc_qdisc_from_str (str, &error);
+	nmtst_assert_success (qdisc2, error);
+	g_free (str);
+
+	g_assert (nm_tc_qdisc_equal (qdisc1, qdisc2));
+
+	nm_tc_qdisc_unref (qdisc1);
+	nm_tc_qdisc_unref (qdisc2);
+}
+
+static void
+test_tc_config_action (void)
+{
+	NMTCAction *action1, *action2;
+	char *str;
+	GError *error = NULL;
+
+	action1 = nm_tc_action_new ("drop", &error);
+	nmtst_assert_success (action1, error);
+	action2 = nm_tc_action_new ("drop", &error);
+	nmtst_assert_success (action2, error);
+
+	g_assert (nm_tc_action_equal (action1, action2));
+	g_assert_cmpstr (nm_tc_action_get_kind (action1), ==, "drop");
+
+	nm_tc_action_unref (action1);
+	action1 = nm_tc_action_new ("simple", &error);
+	nmtst_assert_success (action1, error);
+	nm_tc_action_set_attribute (action1, "sdata", g_variant_new_bytestring ("Hello"));
+
+	g_assert (!nm_tc_action_equal (action1, action2));
+
+	str = nm_utils_tc_action_to_str (action1, &error);
+	nmtst_assert_success (str, error);
+	g_assert_cmpstr (str, ==, "simple sdata Hello");
+	g_free (str);
+
+	str = nm_utils_tc_action_to_str (action2, &error);
+	nmtst_assert_success (str, error);
+	g_assert_cmpstr (str, ==, "drop");
+	g_free (str);
+
+	nm_tc_action_unref (action2);
+	action2 = nm_tc_action_dup (action1);
+
+	g_assert (nm_tc_action_equal (action1, action2));
+
+	nm_tc_action_unref (action1);
+	action1 = nm_utils_tc_action_from_str ("narodil sa kristus pan",  &error);
+	nmtst_assert_no_success (action1, error);
+	g_clear_error (&error);
+
+	action1 = nm_utils_tc_action_from_str ("simple sdata Hello",  &error);
+	nmtst_assert_success (action1, error);
+
+	g_assert_cmpstr (nm_tc_action_get_kind (action1), ==, "simple");
+	g_assert_cmpstr (g_variant_get_bytestring (nm_tc_action_get_attribute (action1, "sdata")), ==, "Hello");
+
+	nm_tc_action_unref (action1);
+	nm_tc_action_unref (action2);
+}
+
+static void
+test_tc_config_tfilter (void)
+{
+	NMTCAction *action1;
+	NMTCTfilter *tfilter1, *tfilter2;
+	char *str;
+	GError *error = NULL;
+
+	tfilter1 = nm_tc_tfilter_new ("matchall",
+	                              TC_H_MAKE (0x1234 << 16, 0x0000),
+	                              &error);
+	nmtst_assert_success (tfilter1, error);
+
+	tfilter2 = nm_tc_tfilter_new ("matchall",
+	                              TC_H_MAKE (0x1234 << 16, 0x0000),
+	                              &error);
+	nmtst_assert_success (tfilter2, error);
+
+	g_assert (nm_tc_tfilter_equal (tfilter1, tfilter2));
+
+	action1 = nm_tc_action_new ("simple", &error);
+	nmtst_assert_success (action1, error);
+	nm_tc_action_set_attribute (action1, "sdata", g_variant_new_bytestring ("Hello"));
+	nm_tc_tfilter_set_action (tfilter1, action1);
+	nm_tc_action_unref (action1);
+
+	g_assert (!nm_tc_tfilter_equal (tfilter1, tfilter2));
+
+	str = nm_utils_tc_tfilter_to_str (tfilter1, &error);
+	nmtst_assert_success (str, error);
+	g_assert_cmpstr (str, ==, "parent 1234: matchall action simple sdata Hello");
+	g_free (str);
+
+	nm_tc_tfilter_unref (tfilter2);
+	tfilter2 = nm_tc_tfilter_dup (tfilter1);
+
+	g_assert (nm_tc_tfilter_equal (tfilter1, tfilter2));
+
+	nm_tc_tfilter_unref (tfilter1);
+	tfilter1 = nm_utils_tc_tfilter_from_str ("narodil sa kristus pan",  &error);
+	nmtst_assert_no_success (tfilter1, error);
+	g_clear_error (&error);
+
+	str = nm_utils_tc_tfilter_to_str (tfilter2, &error);
+	nmtst_assert_success (str, error);
+	tfilter1 = nm_utils_tc_tfilter_from_str (str, &error);
+	nmtst_assert_success (tfilter1, error);
+	g_free (str);
+
+	g_assert (nm_tc_tfilter_equal (tfilter1, tfilter2));
+
+	nm_tc_tfilter_unref (tfilter1);
+	nm_tc_tfilter_unref (tfilter2);
+}
+
+static void
+test_tc_config_setting (void)
+{
+	gs_unref_object NMSettingTCConfig *s_tc = NULL;
+	NMTCQdisc *qdisc1, *qdisc2;
+	GError *error = NULL;
+
+	s_tc = (NMSettingTCConfig *) nm_setting_tc_config_new ();
+
+	qdisc1 = nm_tc_qdisc_new ("fq_codel", TC_H_ROOT, &error);
+	nmtst_assert_success (qdisc1, error);
+
+	qdisc2 = nm_tc_qdisc_new ("pfifo_fast",
+	                          TC_H_MAKE (0xfff1 << 16, 0x0001),
+	                          &error);
+	nmtst_assert_success (qdisc2, error);
+	nm_tc_qdisc_set_handle (qdisc2, TC_H_MAKE (0x1234 << 16, 0x0000));
+
+	g_assert (nm_setting_tc_config_get_num_qdiscs (s_tc) == 0);
+	g_assert (nm_setting_tc_config_add_qdisc (s_tc, qdisc1) == TRUE);
+	g_assert (nm_setting_tc_config_get_num_qdiscs (s_tc) == 1);
+	g_assert (nm_setting_tc_config_get_qdisc (s_tc, 0) != NULL);
+	g_assert (nm_setting_tc_config_remove_qdisc_by_value (s_tc, qdisc2) == FALSE);
+	g_assert (nm_setting_tc_config_add_qdisc (s_tc, qdisc2) == TRUE);
+	g_assert (nm_setting_tc_config_get_num_qdiscs (s_tc) == 2);
+	g_assert (nm_setting_tc_config_remove_qdisc_by_value (s_tc, qdisc1) == TRUE);
+	g_assert (nm_setting_tc_config_get_num_qdiscs (s_tc) == 1);
+	nm_setting_tc_config_clear_qdiscs (s_tc);
+	g_assert (nm_setting_tc_config_get_num_qdiscs (s_tc) == 0);
+
+	nm_tc_qdisc_unref (qdisc1);
+	nm_tc_qdisc_unref (qdisc2);
+}
+
+static void
+test_tc_config_dbus (void)
+{
+	NMConnection *connection1, *connection2;
+	NMSetting *s_tc;
+	NMTCQdisc *qdisc1, *qdisc2;
+	NMTCTfilter *tfilter1, *tfilter2;
+	NMTCAction *action;
+	GVariant *dbus, *tc_dbus, *var1, *var2;
+	GError *error = NULL;
+	gboolean success;
+
+	connection1 = nmtst_create_minimal_connection ("dummy",
+	                                               NULL,
+	                                               NM_SETTING_DUMMY_SETTING_NAME,
+	                                               NULL);
+
+	s_tc = nm_setting_tc_config_new ();
+
+	qdisc1 = nm_tc_qdisc_new ("fq_codel", TC_H_ROOT, &error);
+	nmtst_assert_success (qdisc1, error);
+	nm_tc_qdisc_set_handle (qdisc1, TC_H_MAKE (0x1234 << 16, 0x0000));
+	nm_setting_tc_config_add_qdisc (NM_SETTING_TC_CONFIG (s_tc), qdisc1);
+
+	qdisc2 = nm_tc_qdisc_new ("ingress", TC_H_INGRESS, &error);
+	nmtst_assert_success (qdisc2, error);
+	nm_tc_qdisc_set_handle (qdisc2, TC_H_MAKE (TC_H_INGRESS, 0));
+	nm_setting_tc_config_add_qdisc (NM_SETTING_TC_CONFIG (s_tc), qdisc2);
+
+	tfilter1 = nm_tc_tfilter_new ("matchall",
+	                              TC_H_MAKE (0x1234 << 16, 0x0000),
+	                              &error);
+	nmtst_assert_success (tfilter1, error);
+	action = nm_tc_action_new ("drop", &error);
+	nmtst_assert_success (action, error);
+	nm_tc_tfilter_set_action (tfilter1, action);
+	nm_tc_action_unref (action);
+	nm_setting_tc_config_add_tfilter (NM_SETTING_TC_CONFIG (s_tc), tfilter1);
+	nm_tc_tfilter_unref (tfilter1);
+
+	tfilter2 = nm_tc_tfilter_new ("matchall",
+	                              TC_H_MAKE (TC_H_INGRESS, 0),
+	                              &error);
+	nmtst_assert_success (tfilter2, error);
+	action = nm_tc_action_new ("simple", &error);
+	nmtst_assert_success (action, error);
+	nm_tc_action_set_attribute (action, "sdata", g_variant_new_bytestring ("Hello"));
+	nm_tc_tfilter_set_action (tfilter2, action);
+	nm_tc_action_unref (action);
+	nm_setting_tc_config_add_tfilter (NM_SETTING_TC_CONFIG (s_tc), tfilter2);
+	nm_tc_tfilter_unref (tfilter2);
+
+	nm_connection_add_setting (connection1, s_tc);
+
+	dbus = nm_connection_to_dbus (connection1, NM_CONNECTION_SERIALIZE_ALL);
+
+	tc_dbus = g_variant_lookup_value (dbus, "tc", G_VARIANT_TYPE_VARDICT);
+	g_assert (tc_dbus);
+
+	var1 = g_variant_lookup_value (tc_dbus, "qdiscs", G_VARIANT_TYPE ("aa{sv}"));
+	var2 = g_variant_new_parsed ("[{'kind':   <'fq_codel'>,"
+	                             "  'handle': <uint32 0x12340000>,"
+	                             "  'parent': <uint32 0xffffffff>},"
+	                             " {'kind':   <'ingress'>,"
+	                             "  'handle': <uint32 0xffff0000>,"
+	                             "  'parent': <uint32 0xfffffff1>}]");
+	g_assert (g_variant_equal (var1, var2));
+	g_variant_unref (var1);
+	g_variant_unref (var2);
+
+	var1 = g_variant_lookup_value (tc_dbus, "tfilters", G_VARIANT_TYPE ("aa{sv}"));
+	var2 = g_variant_new_parsed ("[{'kind':   <'matchall'>,"
+	                             "  'handle': <uint32 0>,"
+	                             "  'parent': <uint32 0x12340000>,"
+	                             "  'action': <{'kind': <'drop'>}>},"
+	                             " {'kind':   <'matchall'>,"
+	                             "  'handle': <uint32 0>,"
+	                             "  'parent': <uint32 0xffff0000>,"
+	                             "  'action': <{'kind':  <'simple'>,"
+	                             "              'sdata': <b'Hello'>}>}]");
+	g_variant_unref (var1);
+	g_variant_unref (var2);
+
+	g_variant_unref (tc_dbus);
+
+	connection2 = nm_simple_connection_new ();
+	success = nm_connection_replace_settings (connection2, dbus, &error);
+	nmtst_assert_success (success, error);
+
+	g_assert (nm_connection_diff (connection1, connection2, NM_SETTING_COMPARE_FLAG_EXACT, NULL));
+
+	g_variant_unref (dbus);
+
+	nm_tc_qdisc_unref (qdisc1);
+	nm_tc_qdisc_unref (qdisc2);
+
+	g_object_unref (connection1);
+	g_object_unref (connection2);
+}
+
+/*****************************************************************************/
+
+NMTST_DEFINE ();
+
+int
+main (int argc, char **argv)
+{
+	nmtst_init (&argc, &argv, TRUE);
+
+	g_test_add_data_func ("/libnm/setting-8021x/key-and-cert",
+	                      "test_key_and_cert.pem, test",
+	                      test_8021x);
+	g_test_add_data_func ("/libnm/setting-8021x/key-only",
+	                      "test-key-only.pem, test",
+	                      test_8021x);
+	g_test_add_data_func ("/libnm/setting-8021x/pkcs8-enc-key",
+	                      "pkcs8-enc-key.pem, 1234567890",
+	                      test_8021x);
+	g_test_add_data_func ("/libnm/setting-8021x/pkcs12",
+	                      "test-cert.p12, test",
+	                      test_8021x);
+
+	g_test_add_func ("/libnm/settings/bond/verify", test_bond_verify);
+	g_test_add_func ("/libnm/settings/bond/compare", test_bond_compare);
+	g_test_add_func ("/libnm/settings/bond/normalize", test_bond_normalize);
+
+	g_test_add_func ("/libnm/settings/dcb/flags-valid", test_dcb_flags_valid);
+	g_test_add_func ("/libnm/settings/dcb/flags-invalid", test_dcb_flags_invalid);
+	g_test_add_func ("/libnm/settings/dcb/app-priorities", test_dcb_app_priorities);
+	g_test_add_func ("/libnm/settings/dcb/priorities", test_dcb_priorities_valid);
+	g_test_add_func ("/libnm/settings/dcb/bandwidth-sums", test_dcb_bandwidth_sums);
+
+	g_test_add_func ("/libnm/settings/tc_config/qdisc", test_tc_config_qdisc);
+	g_test_add_func ("/libnm/settings/tc_config/action", test_tc_config_action);
+	g_test_add_func ("/libnm/settings/tc_config/tfilter", test_tc_config_tfilter);
+	g_test_add_func ("/libnm/settings/tc_config/setting", test_tc_config_setting);
+	g_test_add_func ("/libnm/settings/tc_config/dbus", test_tc_config_dbus);
+
+#if WITH_JANSSON
+	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",
+	                 test_runner_broadcast_sync_from_config);
+	g_test_add_func ("/libnm/settings/team/sync_runner_from_config_activebackup",
+	                 test_runner_activebackup_sync_from_config);
+	g_test_add_func ("/libnm/settings/team/sync_runner_from_config_loadbalance",
+	                 test_runner_loadbalance_sync_from_config);
+	g_test_add_func ("/libnm/settings/team/sync_runner_from_config_lacp",
+	                 test_runner_lacp_sync_from_config);
+	g_test_add_func ("/libnm/settings/team/sync_watcher_from_config_ethtool",
+	                 test_watcher_ethtool_sync_from_config);
+	g_test_add_func ("/libnm/settings/team/sync_watcher_from_config_nsna_ping",
+	                 test_watcher_nsna_ping_sync_from_config);
+	g_test_add_func ("/libnm/settings/team/sync_watcher_from_config_arp_ping",
+	                 test_watcher_arp_ping_sync_from_config);
+	g_test_add_func ("/libnm/settings/team/sync_watcher_from_config_all",
+	                 test_multiple_watchers_sync_from_config);
+
+	g_test_add_func ("/libnm/settings/team-port/sync_from_config_defaults", test_team_port_default);
+	g_test_add_func ("/libnm/settings/team-port/sync_from_config_queue_id", test_team_port_queue_id);
+	g_test_add_func ("/libnm/settings/team-port/sync_from_config_prio", test_team_port_prio);
+	g_test_add_func ("/libnm/settings/team-port/sync_from_config_sticky", test_team_port_sticky);
+	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
+
+	return g_test_run ();
+}