about summary refs log tree commit diff
path: root/src/platform
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 /src/platform
parent417f6015c3dc8c47cf27daa59f64e0e36c521b9c (diff)
New upstream version 1.10.2 upstream/1.10.2
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/nm-fake-platform.c28
-rw-r--r--src/platform/nm-linux-platform.c460
-rw-r--r--src/platform/nm-platform.c383
-rw-r--r--src/platform/nm-platform.h114
-rw-r--r--src/platform/nmp-netns.c5
-rw-r--r--src/platform/nmp-object.c103
-rw-r--r--src/platform/nmp-object.h85
-rw-r--r--src/platform/tests/test-common.c54
-rw-r--r--src/platform/tests/test-common.h20
-rw-r--r--src/platform/tests/test-link.c15
-rw-r--r--src/platform/tests/test-nmp-object.c91
-rw-r--r--src/platform/tests/test-route.c208
12 files changed, 1294 insertions, 272 deletions
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index c199c5ed..be430152 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -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 (C) 2012–2013 Red Hat, Inc.
+ * Copyright (C) 2012–2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -1056,11 +1056,11 @@ ipx_address_delete (NMPlatform *platform,
 	peer_addr_i = peer_addr ? *((guint32 *) peer_addr) : 0;
 
 	nmp_cache_iter_for_each (&iter,
-	                         nm_platform_lookup_addrroute (platform,
-	                                                       addr_family == AF_INET
-	                                                         ? NMP_OBJECT_TYPE_IP4_ADDRESS
-	                                                         : NMP_OBJECT_TYPE_IP6_ADDRESS,
-	                                                       0),
+	                         nm_platform_lookup_object (platform,
+	                                                    addr_family == AF_INET
+	                                                      ? NMP_OBJECT_TYPE_IP4_ADDRESS
+	                                                      : NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                                                    0),
 	                         &o) {
 		const NMPObject *obj_old = NULL;
 
@@ -1144,9 +1144,9 @@ ipx_route_delete (NMPlatform *platform,
 	}
 
 	nmp_cache_iter_for_each (&iter,
-	                         nm_platform_lookup_addrroute (platform,
-	                                                       obj_type,
-	                                                       ifindex),
+	                         nm_platform_lookup_object (platform,
+	                                                    obj_type,
+	                                                    ifindex),
 	                         &o) {
 		const NMPObject *obj_old = NULL;
 
@@ -1190,7 +1190,7 @@ ipx_route_delete (NMPlatform *platform,
 }
 
 static gboolean
-ip_route_delete (NMPlatform *platform, const NMPObject *obj)
+object_delete (NMPlatform *platform, const NMPObject *obj)
 {
 	g_assert (NM_IS_FAKE_PLATFORM (platform));
 	g_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
@@ -1254,9 +1254,9 @@ ip_route_add (NMPlatform *platform,
 		gboolean has_route_to_gw = FALSE;
 
 		nmp_cache_iter_for_each (&iter,
-		                         nm_platform_lookup_addrroute (platform,
-		                                                       NMP_OBJECT_GET_TYPE (obj),
-		                                                       0),
+		                         nm_platform_lookup_object (platform,
+		                                                    NMP_OBJECT_GET_TYPE (obj),
+		                                                    0),
 		                         &o) {
 			if (addr_family == AF_INET) {
 				const NMPlatformIP4Route *item = NMP_OBJECT_CAST_IP4_ROUTE (o);
@@ -1462,5 +1462,5 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 	platform_class->ip6_address_delete = ip6_address_delete;
 
 	platform_class->ip_route_add = ip_route_add;
-	platform_class->ip_route_delete = ip_route_delete;
+	platform_class->object_delete = object_delete;
 }
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index c4c93ed3..10d1a6ef 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -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 (C) 2012-2015 Red Hat, Inc.
+ * Copyright (C) 2012 - 2017 Red Hat, Inc.
  */
 #include "nm-default.h"
 
@@ -54,6 +54,29 @@
 #include "nm-utils/unaligned.h"
 #include "nm-utils/nm-udev-utils.h"
 
+/*****************************************************************************/
+
+/* re-implement <linux/tc_act/tc_defact.h> to build against kernel
+ * headers that lack this. */
+
+#include <linux/pkt_cls.h>
+
+struct tc_defact {
+	tc_gen;
+};
+
+enum {
+	TCA_DEF_UNSPEC,
+	TCA_DEF_TM,
+	TCA_DEF_PARMS,
+	TCA_DEF_DATA,
+	TCA_DEF_PAD,
+	__TCA_DEF_MAX
+};
+#define TCA_DEF_MAX (__TCA_DEF_MAX - 1)
+
+/*****************************************************************************/
+
 #define VLAN_FLAG_MVRP 0x8
 
 /* nm-internal error codes for libnl. Make sure they don't overlap. */
@@ -227,27 +250,33 @@ enum {
 	DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ADDRESSES,
 	DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ROUTES,
 	DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ROUTES,
+	DELAYED_ACTION_IDX_REFRESH_ALL_QDISCS,
+	DELAYED_ACTION_IDX_REFRESH_ALL_TFILTERS,
 	_DELAYED_ACTION_IDX_REFRESH_ALL_NUM,
 };
 
 typedef enum {
 	DELAYED_ACTION_TYPE_NONE                        = 0,
-	DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS           = (1LL << DELAYED_ACTION_IDX_REFRESH_ALL_LINKS),
-	DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES   = (1LL << DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ADDRESSES),
-	DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES   = (1LL << DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ADDRESSES),
-	DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES      = (1LL << DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ROUTES),
-	DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES      = (1LL << DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ROUTES),
-	DELAYED_ACTION_TYPE_REFRESH_LINK                = (1LL << 5),
-	DELAYED_ACTION_TYPE_MASTER_CONNECTED            = (1LL << 6),
-	DELAYED_ACTION_TYPE_READ_NETLINK                = (1LL << 7),
-	DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE        = (1LL << 8),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS           = (1LL << /* 0 */ DELAYED_ACTION_IDX_REFRESH_ALL_LINKS),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES   = (1LL << /* 1 */ DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ADDRESSES),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES   = (1LL << /* 2 */ DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ADDRESSES),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES      = (1LL << /* 3 */ DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ROUTES),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES      = (1LL << /* 4 */ DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ROUTES),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS          = (1LL << /* 5 */ DELAYED_ACTION_IDX_REFRESH_ALL_QDISCS),
+	DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS        = (1LL << /* 6 */ DELAYED_ACTION_IDX_REFRESH_ALL_TFILTERS),
+	DELAYED_ACTION_TYPE_REFRESH_LINK                = (1LL <<    7),
+	DELAYED_ACTION_TYPE_MASTER_CONNECTED            = (1LL <<   11),
+	DELAYED_ACTION_TYPE_READ_NETLINK                = (1LL <<   12),
+	DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE        = (1LL <<   13),
 	__DELAYED_ACTION_TYPE_MAX,
 
 	DELAYED_ACTION_TYPE_REFRESH_ALL                 = DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
 	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
 	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
 	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
-	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES |
+	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS |
+	                                                  DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
 
 	DELAYED_ACTION_TYPE_MAX                         = __DELAYED_ACTION_TYPE_MAX -1,
 } DelayedActionType;
@@ -941,17 +970,25 @@ _nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len)
 	b = buf;
 
 	switch (hdr->nlmsg_type) {
-	case RTM_NEWLINK:  s = "NEWLINK";  break;
-	case RTM_DELLINK:  s = "DELLINK";  break;
-	case RTM_NEWADDR:  s = "NEWADDR";  break;
-	case RTM_DELADDR:  s = "DELADDR";  break;
-	case RTM_NEWROUTE: s = "NEWROUTE"; break;
-	case RTM_DELROUTE: s = "DELROUTE"; break;
-	default:           s = NULL;       break;
+	case RTM_NEWLINK:    s = "RTM_NEWLINK";  break;
+	case RTM_DELLINK:    s = "RTM_DELLINK";  break;
+	case RTM_NEWADDR:    s = "RTM_NEWADDR";  break;
+	case RTM_DELADDR:    s = "RTM_DELADDR";  break;
+	case RTM_NEWROUTE:   s = "RTM_NEWROUTE"; break;
+	case RTM_DELROUTE:   s = "RTM_DELROUTE"; break;
+	case RTM_NEWQDISC:   s = "RTM_NEWQDISC"; break;
+	case RTM_DELQDISC:   s = "RTM_DELQDISC"; break;
+	case RTM_NEWTFILTER: s = "RTM_NEWTFILTER"; break;
+	case RTM_DELTFILTER: s = "RTM_DELTFILTER"; break;
+	case NLMSG_NOOP:     s = "NLMSG_NOOP"; break;
+	case NLMSG_ERROR:    s = "NLMSG_ERROR"; break;
+	case NLMSG_DONE:     s = "NLMSG_DONE"; break;
+	case NLMSG_OVERRUN:  s = "NLMSG_OVERRUN"; break;
+	default:             s = NULL;       break;
 	}
 
 	if (s)
-		nm_utils_strbuf_append (&buf, &len, "RTM_%s", s);
+		nm_utils_strbuf_append_str (&buf, &len, s);
 	else
 		nm_utils_strbuf_append (&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type);
 
@@ -989,6 +1026,8 @@ _nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len)
 	case RTM_NEWLINK:
 	case RTM_NEWADDR:
 	case RTM_NEWROUTE:
+	case RTM_NEWQDISC:
+	case RTM_NEWTFILTER:
 		_F (NLM_F_REPLACE, "replace");
 		_F (NLM_F_EXCL, "excl");
 		_F (NLM_F_CREATE, "create");
@@ -997,6 +1036,8 @@ _nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len)
 	case RTM_GETLINK:
 	case RTM_GETADDR:
 	case RTM_GETROUTE:
+	case RTM_DELQDISC:
+	case RTM_DELTFILTER:
 		_F (NLM_F_DUMP, "dump");
 		_F (NLM_F_ROOT, "root");
 		_F (NLM_F_MATCH, "match");
@@ -2291,17 +2332,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 			obj->ip6_route.rt_pref = nla_get_u8 (tb[RTA_PREF]);
 	}
 
-	if (NM_FLAGS_HAS (rtm->rtm_flags, RTM_F_CLONED)) {
-		/* we must not straight way reject cloned routes, because we might have cached
-		 * a non-cloned route. If we now receive an update of the route with the route
-		 * being cloned, we must still return the object, so that we can remove the old
-		 * one from the cache.
-		 *
-		 * This happens, because this route is not nmp_object_is_alive().
-		 * */
-		obj->ip_route.rt_cloned = TRUE;
-	}
-
+	obj->ip_route.r_rtm_flags = rtm->rtm_flags;
 	obj->ip_route.rt_source = nmp_utils_ip_config_source_from_rtprot (rtm->rtm_protocol);
 
 	obj_result = obj;
@@ -2310,6 +2341,74 @@ errout:
 	return obj_result;
 }
 
+static NMPObject *
+_new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
+{
+	NMPObject *obj = NULL;
+	const struct tcmsg *tcm;
+	struct nlattr *tb[TCA_MAX + 1];
+	int err;
+	static const struct nla_policy policy[TCA_MAX + 1] = {
+		[TCA_KIND] = { .type = NLA_STRING },
+	};
+
+	if (!nlmsg_valid_hdr (nlh, sizeof (*tcm)))
+		return NULL;
+	tcm = nlmsg_data (nlh);
+
+	err = nlmsg_parse (nlh, sizeof (*tcm), tb, TCA_MAX, policy);
+	if (err < 0)
+		return NULL;
+
+	if (!tb[TCA_KIND])
+		return NULL;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_QDISC, NULL);
+
+	obj->qdisc.kind = g_intern_string (nla_get_string (tb[TCA_KIND]));
+	obj->qdisc.ifindex = tcm->tcm_ifindex;
+	obj->qdisc.addr_family = tcm->tcm_family;
+	obj->qdisc.handle = tcm->tcm_handle;
+	obj->qdisc.parent = tcm->tcm_parent;
+	obj->qdisc.info = tcm->tcm_info;
+
+	return obj;
+}
+
+static NMPObject *
+_new_from_nl_tfilter (struct nlmsghdr *nlh, gboolean id_only)
+{
+	NMPObject *obj = NULL;
+	const struct tcmsg *tcm;
+	struct nlattr *tb[TCA_MAX + 1];
+	int err;
+	static const struct nla_policy policy[TCA_MAX + 1] = {
+		[TCA_KIND] = { .type = NLA_STRING },
+	};
+
+	if (!nlmsg_valid_hdr (nlh, sizeof (*tcm)))
+		return NULL;
+	tcm = nlmsg_data (nlh);
+
+	err = nlmsg_parse (nlh, sizeof (*tcm), tb, TCA_MAX, policy);
+	if (err < 0)
+		return NULL;
+
+	if (!tb[TCA_KIND])
+		return NULL;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_TFILTER, NULL);
+
+	obj->tfilter.kind = g_intern_string (nla_get_string (tb[TCA_KIND]));
+	obj->tfilter.ifindex = tcm->tcm_ifindex;
+	obj->tfilter.addr_family = tcm->tcm_family;
+	obj->tfilter.handle = tcm->tcm_handle;
+	obj->tfilter.parent = tcm->tcm_parent;
+	obj->tfilter.info = tcm->tcm_info;
+
+	return obj;
+}
+
 /**
  * nmp_object_new_from_nl:
  * @platform: (allow-none): for creating certain objects, the constructor wants to check
@@ -2346,6 +2445,14 @@ nmp_object_new_from_nl (NMPlatform *platform, const NMPCache *cache, struct nl_m
 	case RTM_DELROUTE:
 	case RTM_GETROUTE:
 		return _new_from_nl_route (msghdr, id_only);
+	case RTM_NEWQDISC:
+	case RTM_DELQDISC:
+	case RTM_GETQDISC:
+		return _new_from_nl_qdisc (msghdr, id_only);
+	case RTM_NEWTFILTER:
+	case RTM_DELTFILTER:
+	case RTM_GETTFILTER:
+		return _new_from_nl_tfilter (msghdr, id_only);
 	default:
 		return NULL;
 	}
@@ -2700,7 +2807,9 @@ _nl_msg_new_route (int nlmsg_type,
 		             ? nm_platform_route_scope_inv (obj->ip4_route.scope_inv)
 		             : RT_SCOPE_NOWHERE,
 		.rtm_type = RTN_UNICAST,
-		.rtm_flags = 0,
+		.rtm_flags = obj->ip_route.r_rtm_flags & (is_v4
+		                                          ? (unsigned) (RTNH_F_ONLINK)
+		                                          : (unsigned) 0),
 		.rtm_dst_len = obj->ip_route.plen,
 		.rtm_src_len = is_v4
 		               ? 0
@@ -2797,6 +2906,124 @@ nla_put_failure:
 	g_return_val_if_reached (NULL);
 }
 
+static struct nl_msg *
+_nl_msg_new_qdisc (int nlmsg_type,
+                   int nlmsg_flags,
+                   const NMPlatformQdisc *qdisc)
+{
+	struct nl_msg *msg;
+	struct tcmsg tcm = {
+		.tcm_family = qdisc->addr_family,
+		.tcm_ifindex = qdisc->ifindex,
+		.tcm_handle = qdisc->handle,
+		.tcm_parent = qdisc->parent,
+		.tcm_info = qdisc->info,
+	};
+
+	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	if (!msg)
+		return NULL;
+
+	if (nlmsg_append (msg, &tcm, sizeof (tcm), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	NLA_PUT_STRING (msg, TCA_KIND, qdisc->kind);
+
+	return msg;
+nla_put_failure:
+	nlmsg_free (msg);
+	g_return_val_if_reached (NULL);
+}
+
+static gboolean
+_add_action_simple (struct nl_msg *msg,
+                    const NMPlatformActionSimple *simple)
+{
+	struct nlattr *act_options;
+	struct tc_defact sel = { 0, };
+
+	if (!(act_options = nla_nest_start (msg, TCA_ACT_OPTIONS)))
+		goto nla_put_failure;
+
+	NLA_PUT (msg, TCA_DEF_PARMS, sizeof (sel), &sel);
+	NLA_PUT (msg, TCA_DEF_DATA, sizeof (simple->sdata), simple->sdata);
+
+	nla_nest_end (msg, act_options);
+
+	return TRUE;
+
+nla_put_failure:
+	return FALSE;
+}
+
+static gboolean
+_add_action (struct nl_msg *msg,
+             const NMPlatformAction *action)
+{
+	struct nlattr *prio;
+
+	nm_assert (action || action->kind);
+
+	if (!(prio = nla_nest_start (msg, 1 /* priority */)))
+		goto nla_put_failure;
+
+	NLA_PUT_STRING (msg, TCA_ACT_KIND, action->kind);
+
+	if (nm_streq (action->kind, NM_PLATFORM_ACTION_KIND_SIMPLE))
+		_add_action_simple (msg, &action->simple);
+
+	nla_nest_end (msg, prio);
+
+	return TRUE;
+
+nla_put_failure:
+	return FALSE;
+}
+
+static struct nl_msg *
+_nl_msg_new_tfilter (int nlmsg_type,
+                     int nlmsg_flags,
+                     const NMPlatformTfilter *tfilter)
+{
+	struct nl_msg *msg;
+	struct nlattr *tc_options;
+	struct nlattr *act_tab;
+	struct tcmsg tcm = {
+		.tcm_family = tfilter->addr_family,
+		.tcm_ifindex = tfilter->ifindex,
+		.tcm_handle = tfilter->handle,
+		.tcm_parent = tfilter->parent,
+		.tcm_info = tfilter->info,
+	};
+
+	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	if (!msg)
+		return NULL;
+
+	if (nlmsg_append (msg, &tcm, sizeof (tcm), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	NLA_PUT_STRING (msg, TCA_KIND, tfilter->kind);
+
+	if (!(tc_options = nla_nest_start (msg, TCA_OPTIONS)))
+		goto nla_put_failure;
+
+	if (!(act_tab = nla_nest_start (msg, TCA_OPTIONS))) // 3 TCA_ACT_KIND TCA_ACT_KIND
+		goto nla_put_failure;
+
+	if (tfilter->action.kind)
+		_add_action (msg, &tfilter->action);
+
+	nla_nest_end (msg, tc_options);
+
+	nla_nest_end (msg, act_tab);
+
+	return msg;
+nla_put_failure:
+	nlmsg_free (msg);
+	g_return_val_if_reached (NULL);
+}
+
 /******************************************************************
  * NMPlatform types and functions
  ******************************************************************/
@@ -2867,7 +3094,7 @@ struct _NMLinuxPlatformClass {
 
 G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
 
-#define NM_LINUX_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE_VOID(self, NMLinuxPlatform, NM_IS_LINUX_PLATFORM)
+#define NM_LINUX_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMLinuxPlatform, NM_IS_LINUX_PLATFORM, NMPlatform)
 
 NMPlatform *
 nm_linux_platform_new (gboolean log_with_ptr, gboolean netns_support)
@@ -3198,6 +3425,8 @@ _NM_UTILS_LOOKUP_DEFINE (static, delayed_action_refresh_from_object_type, NMPObj
 	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_IP6_ADDRESS, DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES),
 	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_IP4_ROUTE,   DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES),
 	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_IP6_ROUTE,   DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES),
+	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_QDISC,       DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS),
+	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_TFILTER,     DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS),
 	NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER (),
 );
 
@@ -3208,6 +3437,8 @@ _NM_UTILS_LOOKUP_DEFINE (static, delayed_action_refresh_to_object_type, DelayedA
 	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES, NMP_OBJECT_TYPE_IP6_ADDRESS),
 	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES,    NMP_OBJECT_TYPE_IP4_ROUTE),
 	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,    NMP_OBJECT_TYPE_IP6_ROUTE),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS,        NMP_OBJECT_TYPE_QDISC),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,      NMP_OBJECT_TYPE_TFILTER),
 	NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER (),
 );
 
@@ -3218,6 +3449,8 @@ _NM_UTILS_LOOKUP_DEFINE (static, delayed_action_refresh_all_to_idx, DelayedActio
 	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES, DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ADDRESSES),
 	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES,    DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ROUTES),
 	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,    DELAYED_ACTION_IDX_REFRESH_ALL_IP6_ROUTES),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS,        DELAYED_ACTION_IDX_REFRESH_ALL_QDISCS),
+	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,      DELAYED_ACTION_IDX_REFRESH_ALL_TFILTERS),
 	NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER (),
 );
 
@@ -3228,6 +3461,8 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (delayed_action_to_string, DelayedActionType,
 	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES, "refresh-all-ip6-addresses"),
 	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES,    "refresh-all-ip4-routes"),
 	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,    "refresh-all-ip6-routes"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS,        "refresh-all-qdiscs"),
+	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,      "refresh-all-tfilters"),
 	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_LINK,              "refresh-link"),
 	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_MASTER_CONNECTED,          "master-connected"),
 	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_READ_NETLINK,              "read-netlink"),
@@ -3665,7 +3900,7 @@ cache_on_change (NMPlatform *platform,
 		{
 			int ifindex = 0;
 
-			/* if we remove a link (from netlink), we must refresh the addresses and routes */
+			/* if we remove a link (from netlink), we must refresh the addresses, routes, qdiscs and tfilters */
 			if (   cache_op == NMP_CACHE_OPS_REMOVED
 			    && obj_old /* <-- nonsensical, make coverity happy */)
 				ifindex = obj_old->link.ifindex;
@@ -3680,7 +3915,9 @@ cache_on_change (NMPlatform *platform,
 				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
 				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
 				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
-				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+				                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES |
+				                         DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS |
+				                         DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
 				                         NULL);
 			}
 		}
@@ -4003,9 +4240,6 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 		NMPObjectType obj_type = delayed_action_refresh_to_object_type (iflags);
 		const NMPClass *klass = nmp_class_from_type (obj_type);
 		nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-		struct rtgenmsg gmsg = {
-			.rtgen_family = klass->addr_family,
-		};
 		int nle;
 		gint *out_refresh_all_in_progess;
 
@@ -4032,7 +4266,18 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 		if (!nlmsg)
 			continue;
 
-		nle = nlmsg_append (nlmsg, &gmsg, sizeof (gmsg), NLMSG_ALIGNTO);
+		if (   klass->obj_type == NMP_OBJECT_TYPE_QDISC
+		    || klass->obj_type == NMP_OBJECT_TYPE_TFILTER) {
+			struct tcmsg tcmsg = {
+				.tcm_family = AF_UNSPEC,
+			};
+			nle = nlmsg_append (nlmsg, &tcmsg, sizeof (tcmsg), NLMSG_ALIGNTO);
+		} else {
+			struct rtgenmsg gmsg = {
+				.rtgen_family = klass->addr_family,
+			};
+			nle = nlmsg_append (nlmsg, &gmsg, sizeof (gmsg), NLMSG_ALIGNTO);
+		}
 		if (nle < 0)
 			continue;
 
@@ -4154,6 +4399,8 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 	case RTM_NEWADDR:
 	case RTM_NEWLINK:
 	case RTM_NEWROUTE:
+	case RTM_NEWQDISC:
+	case RTM_NEWTFILTER:
 		is_dump = delayed_action_refresh_all_in_progress (platform,
 		                                                  delayed_action_refresh_from_object_type (NMP_OBJECT_GET_TYPE (obj)));
 		break;
@@ -4177,6 +4424,8 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 		case RTM_NEWLINK:
 		case RTM_NEWADDR:
 		case RTM_GETLINK:
+		case RTM_NEWQDISC:
+		case RTM_NEWTFILTER:
 			cache_op = nmp_cache_update_netlink (cache, obj, is_dump, &obj_old, &obj_new);
 			if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
 				cache_on_change (platform, cache_op, obj_old, obj_new);
@@ -4189,7 +4438,7 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 			gboolean resync_required = FALSE;
 			gboolean only_dirty = FALSE;
 
-			if (obj->ip_route.rt_cloned) {
+			if (NM_FLAGS_HAS (obj->ip_route.r_rtm_flags, RTM_F_CLONED)) {
 				/* a cloned route might be a response for RTM_GETROUTE. Check, whether it is. */
 				nm_assert (!nmp_object_is_alive (obj));
 				priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
@@ -4270,13 +4519,14 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 		case RTM_DELLINK:
 		case RTM_DELADDR:
 		case RTM_DELROUTE:
+		case RTM_DELQDISC:
+		case RTM_DELTFILTER:
 			cache_op = nmp_cache_remove_netlink (cache, obj, &obj_old, &obj_new);
 			if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
 				cache_on_change (platform, cache_op, obj_old, obj_new);
 				nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
 			}
 			break;
-
 		default:
 			break;
 		}
@@ -5652,7 +5902,7 @@ tun_add (NMPlatform *platform, const char *name, gboolean tap,
 	if (out_link)
 		*out_link = obj ? &obj->link : NULL;
 
-	close (fd);
+	nm_close (fd);
 	return !!obj;
 }
 
@@ -5886,15 +6136,15 @@ link_can_assume (NMPlatform *platform, int ifindex)
 	if (link->link.master > 0)
 		return TRUE;
 
-	nmp_lookup_init_addrroute (&lookup,
-	                           NMP_OBJECT_TYPE_IP4_ADDRESS,
-	                           ifindex);
+	nmp_lookup_init_object (&lookup,
+	                        NMP_OBJECT_TYPE_IP4_ADDRESS,
+	                        ifindex);
 	if (nmp_cache_lookup (cache, &lookup))
 		return TRUE;
 
-	nmp_lookup_init_addrroute (&lookup,
-	                           NMP_OBJECT_TYPE_IP6_ADDRESS,
-	                           ifindex);
+	nmp_lookup_init_object (&lookup,
+	                        NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                        ifindex);
 	nmp_cache_iter_for_each (&iter,
 	                         nmp_cache_lookup (cache, &lookup),
 	                         &o) {
@@ -6120,19 +6370,30 @@ ip_route_add (NMPlatform *platform,
 }
 
 static gboolean
-ip_route_delete (NMPlatform *platform,
-                 const NMPObject *obj)
+object_delete (NMPlatform *platform,
+               const NMPObject *obj)
 {
 	nm_auto_nmpobj const NMPObject *obj_keep_alive = NULL;
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
-	                                                 NMP_OBJECT_TYPE_IP6_ROUTE));
-
 	if (!NMP_OBJECT_IS_STACKINIT (obj))
 		obj_keep_alive = nmp_object_ref (obj);
 
-	nlmsg = _nl_msg_new_route (RTM_DELROUTE, 0, obj);
+	switch (NMP_OBJECT_GET_TYPE (obj)) {
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+	case NMP_OBJECT_TYPE_IP6_ROUTE:
+		nlmsg = _nl_msg_new_route (RTM_DELROUTE, 0, obj);
+		break;
+	case NMP_OBJECT_TYPE_QDISC:
+		nlmsg = _nl_msg_new_qdisc (RTM_DELQDISC, 0, NMP_OBJECT_CAST_QDISC (obj));
+		break;
+	case NMP_OBJECT_TYPE_TFILTER:
+		nlmsg = _nl_msg_new_tfilter (RTM_DELTFILTER, 0, NMP_OBJECT_CAST_TFILTER (obj));
+		break;
+	default:
+		break;
+	}
+
 	if (!nlmsg)
 		g_return_val_if_reached (FALSE);
 	return do_delete_object (platform, obj, nlmsg);
@@ -6219,6 +6480,84 @@ ip_route_get (NMPlatform *platform,
 
 /*****************************************************************************/
 
+static NMPlatformError
+qdisc_add (NMPlatform *platform,
+           NMPNlmFlags flags,
+           const NMPlatformQdisc *qdisc)
+{
+	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	int nle;
+	char s_buf[256];
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+
+	msg = _nl_msg_new_qdisc (RTM_NEWQDISC, flags, qdisc);
+
+	event_handler_read_netlink (platform, FALSE);
+
+	nle = _nl_send_nlmsg (platform, msg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	if (nle < 0) {
+		_LOGE ("do-add-qdisc: failed sending netlink request \"%s\" (%d)",
+		      nl_geterror (nle), -nle);
+		return NM_PLATFORM_ERROR_NETLINK;
+	}
+
+	delayed_action_handle_all (platform, FALSE);
+
+	nm_assert (seq_result);
+
+	_NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
+	            ? LOGL_DEBUG
+	            : LOGL_WARN,
+	        "do-add-qdisc: %s",
+	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
+
+	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
+		return NM_PLATFORM_ERROR_SUCCESS;
+
+	return NM_PLATFORM_ERROR_UNSPECIFIED;
+}
+
+/*****************************************************************************/
+
+static NMPlatformError
+tfilter_add (NMPlatform *platform,
+             NMPNlmFlags flags,
+             const NMPlatformTfilter *tfilter)
+{
+	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	int nle;
+	char s_buf[256];
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+
+	msg = _nl_msg_new_tfilter (RTM_NEWTFILTER, flags, tfilter);
+
+	event_handler_read_netlink (platform, FALSE);
+
+	nle = _nl_send_nlmsg (platform, msg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+	if (nle < 0) {
+		_LOGE ("do-add-tfilter: failed sending netlink request \"%s\" (%d)",
+		      nl_geterror (nle), -nle);
+		return NM_PLATFORM_ERROR_NETLINK;
+	}
+
+	delayed_action_handle_all (platform, FALSE);
+
+	nm_assert (seq_result);
+
+	_NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
+	            ? LOGL_DEBUG
+	            : LOGL_WARN,
+	        "do-add-tfilter: %s",
+	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
+
+	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
+		return NM_PLATFORM_ERROR_SUCCESS;
+
+	return NM_PLATFORM_ERROR_UNSPECIFIED;
+}
+
+/*****************************************************************************/
+
 #define EVENT_CONDITIONS      ((GIOCondition) (G_IO_IN | G_IO_PRI))
 #define ERROR_CONDITIONS      ((GIOCondition) (G_IO_ERR | G_IO_NVAL))
 #define DISCONNECT_CONDITIONS ((GIOCondition) (G_IO_HUP))
@@ -6500,7 +6839,9 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
 					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
-					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES |
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS |
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
 					                         NULL);
 					break;
 				default:
@@ -6760,13 +7101,13 @@ constructed (GObject *_object)
 	                                 RTNLGRP_LINK,
 	                                 RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR,
 	                                 RTNLGRP_IPV4_ROUTE,  RTNLGRP_IPV6_ROUTE,
+	                                 RTNLGRP_TC,
 	                                 0);
 	g_assert (!nle);
 	_LOGD ("Netlink socket for events established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh), nl_socket_get_fd (priv->nlh));
 
 	priv->event_channel = g_io_channel_unix_new (nl_socket_get_fd (priv->nlh));
 	g_io_channel_set_encoding (priv->event_channel, NULL, NULL);
-	g_io_channel_set_close_on_unref (priv->event_channel, TRUE);
 
 	channel_flags = g_io_channel_get_flags (priv->event_channel);
 	status = g_io_channel_set_flags (priv->event_channel,
@@ -6785,7 +7126,9 @@ constructed (GObject *_object)
 	                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
 	                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
 	                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
-	                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+	                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES |
+	                         DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS |
+	                         DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
 	                         NULL);
 
 	delayed_action_handle_all (platform, FALSE);
@@ -6942,15 +7285,18 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_ipip_add = link_ipip_add;
 	platform_class->link_sit_add = link_sit_add;
 
+	platform_class->object_delete = object_delete;
 	platform_class->ip4_address_add = ip4_address_add;
 	platform_class->ip6_address_add = ip6_address_add;
 	platform_class->ip4_address_delete = ip4_address_delete;
 	platform_class->ip6_address_delete = ip6_address_delete;
 
 	platform_class->ip_route_add = ip_route_add;
-	platform_class->ip_route_delete = ip_route_delete;
 	platform_class->ip_route_get = ip_route_get;
 
+	platform_class->qdisc_add = qdisc_add;
+	platform_class->tfilter_add = tfilter_add;
+
 	platform_class->check_kernel_support = check_kernel_support;
 
 	platform_class->process_events = process_events;
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index ffc4b395..2ca379e4 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -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 (C) 2012 Red Hat, Inc.
+ * Copyright (C) 2012 - 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -508,6 +508,7 @@ nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *pathid, int di
 {
 	char *value = NULL;
 	gint32 ret;
+	int errsv;
 
 	_CHECK_SELF (self, klass, fallback);
 
@@ -522,7 +523,9 @@ nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *pathid, int di
 	}
 
 	ret = _nm_utils_ascii_str_to_int64 (value, base, min, max, fallback);
+	errsv = errno;
 	g_free (value);
+	errno = errsv;
 	return ret;
 }
 
@@ -3343,9 +3346,9 @@ delete_and_next:
 	}
 
 	plat_addresses = nm_platform_lookup_clone (self,
-	                                           nmp_lookup_init_addrroute (&lookup,
-	                                                                      NMP_OBJECT_TYPE_IP4_ADDRESS,
-	                                                                      ifindex),
+	                                           nmp_lookup_init_object (&lookup,
+	                                                                   NMP_OBJECT_TYPE_IP4_ADDRESS,
+	                                                                   ifindex),
 	                                           NULL, NULL);
 	if (plat_addresses)
 		plat_subnets = ip4_addr_subnets_build_index (plat_addresses, TRUE, TRUE);
@@ -3485,9 +3488,9 @@ nm_platform_ip6_address_sync (NMPlatform *self,
 
 	/* Delete unknown addresses */
 	plat_addresses = nm_platform_lookup_clone (self,
-	                                           nmp_lookup_init_addrroute (&lookup,
-	                                                                      NMP_OBJECT_TYPE_IP6_ADDRESS,
-	                                                                      ifindex),
+	                                           nmp_lookup_init_object (&lookup,
+	                                                                   NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                                                                   ifindex),
 	                                           NULL, NULL);
 	if (plat_addresses) {
 		for (i = 0; i < plat_addresses->len; i++) {
@@ -3608,11 +3611,11 @@ nm_platform_ip_route_get_prune_list (NMPlatform *self,
 	                                        NM_IP_ROUTE_TABLE_SYNC_MODE_FULL,
 	                                        NM_IP_ROUTE_TABLE_SYNC_MODE_ALL));
 
-	nmp_lookup_init_addrroute (&lookup,
-	                           addr_family == AF_INET
-	                             ? NMP_OBJECT_TYPE_IP4_ROUTE
-	                             : NMP_OBJECT_TYPE_IP6_ROUTE,
-	                           ifindex);
+	nmp_lookup_init_object (&lookup,
+	                        addr_family == AF_INET
+	                          ? NMP_OBJECT_TYPE_IP4_ROUTE
+	                          : NMP_OBJECT_TYPE_IP6_ROUTE,
+	                        ifindex);
 	head_entry = nm_platform_lookup (self, &lookup);
 	if (!head_entry)
 		return NULL;
@@ -3729,7 +3732,7 @@ nm_platform_ip_route_sync (NMPlatform *self,
 
 				/* we need to replace the existing route with a (slightly) differnt
 				 * one. Delete it first. */
-				if (!nm_platform_ip_route_delete (self, plat_o)) {
+				if (!nm_platform_object_delete (self, plat_o)) {
 					/* ignore error. */
 				}
 			}
@@ -3810,7 +3813,7 @@ nm_platform_ip_route_sync (NMPlatform *self,
 			                               prune_o))
 				continue;
 
-			if (!nm_platform_ip_route_delete (self, prune_o)) {
+			if (!nm_platform_object_delete (self, prune_o)) {
 				/* ignore error... */
 			}
 		}
@@ -3988,20 +3991,22 @@ nm_platform_ip6_route_add (NMPlatform *self,
 }
 
 gboolean
-nm_platform_ip_route_delete (NMPlatform *self,
-                             const NMPObject *obj)
+nm_platform_object_delete (NMPlatform *self,
+                           const NMPObject *obj)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	if (!NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
-	                                           NMP_OBJECT_TYPE_IP6_ROUTE))
+	                                           NMP_OBJECT_TYPE_IP6_ROUTE,
+	                                           NMP_OBJECT_TYPE_QDISC,
+	                                           NMP_OBJECT_TYPE_TFILTER))
 		g_return_val_if_reached (FALSE);
 
-	_LOGD ("route: delete     IPv%c route %s",
-	       NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_IP4_ROUTE ? '4' : '6',
+	_LOGD ("%s: delete %s",
+	       NMP_OBJECT_GET_CLASS (obj)->obj_type_name,
 	       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
 
-	return klass->ip_route_delete (self, obj);
+	return klass->object_delete (self, obj);
 }
 
 /*****************************************************************************/
@@ -4112,7 +4117,7 @@ again:
 
 		_LOGT ("ip4-dev-route: delete %s",
 		       nmp_object_to_string (p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-		nm_platform_ip_route_delete (self, p_obj);
+		nm_platform_object_delete (self, p_obj);
 		goto again;
 	}
 
@@ -4329,6 +4334,138 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
 
 /*****************************************************************************/
 
+NMPlatformError
+nm_platform_qdisc_add (NMPlatform *self,
+                       NMPNlmFlags flags,
+                       const NMPlatformQdisc *qdisc)
+{
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	_LOGD ("adding or updating a qdisc: %s", nm_platform_qdisc_to_string (qdisc, NULL, 0));
+	return klass->qdisc_add (self, flags, qdisc);
+}
+
+gboolean
+nm_platform_qdisc_sync (NMPlatform *self,
+                        int ifindex,
+                        GPtrArray *known_qdiscs)
+{
+	gs_unref_ptrarray GPtrArray *plat_qdiscs = NULL;
+	NMPLookup lookup;
+	guint i;
+	gboolean success = TRUE;
+	gs_unref_hashtable GHashTable *known_qdiscs_idx = NULL;
+
+	nm_assert (NM_IS_PLATFORM (self));
+	nm_assert (ifindex > 0);
+
+	known_qdiscs_idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash,
+	                                     (GEqualFunc) nmp_object_id_equal);
+
+	if (known_qdiscs) {
+		for (i = 0; i < known_qdiscs->len; i++) {
+			const NMPObject *q = g_ptr_array_index (known_qdiscs, i);
+
+			g_hash_table_insert (known_qdiscs_idx, (gpointer) q, (gpointer) q);
+		}
+	}
+
+	plat_qdiscs = nm_platform_lookup_clone (self,
+	                                        nmp_lookup_init_object (&lookup,
+	                                                                NMP_OBJECT_TYPE_QDISC,
+	                                                                ifindex),
+	                                        NULL, NULL);
+
+
+	if (plat_qdiscs) {
+		for (i = 0; i < plat_qdiscs->len; i++) {
+			const NMPObject *q = g_ptr_array_index (plat_qdiscs, i);
+
+			if (!g_hash_table_lookup (known_qdiscs_idx, q))
+				success &= nm_platform_object_delete (self, q);
+		}
+	}
+
+	if (known_qdiscs) {
+		for (i = 0; i < known_qdiscs->len; i++) {
+			const NMPObject *q = g_ptr_array_index (known_qdiscs, i);
+
+			success &= (nm_platform_qdisc_add (self, NMP_NLM_FLAG_ADD,
+			                                   NMP_OBJECT_CAST_QDISC (q)) == NM_PLATFORM_ERROR_SUCCESS);
+		}
+	}
+
+	return success;
+}
+
+/*****************************************************************************/
+
+NMPlatformError
+nm_platform_tfilter_add (NMPlatform *self,
+                         NMPNlmFlags flags,
+                         const NMPlatformTfilter *tfilter)
+{
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	_LOGD ("adding or updating a tfilter: %s", nm_platform_tfilter_to_string (tfilter, NULL, 0));
+	return klass->tfilter_add (self, flags, tfilter);
+}
+
+gboolean
+nm_platform_tfilter_sync (NMPlatform *self,
+                          int ifindex,
+                          GPtrArray *known_tfilters)
+{
+	gs_unref_ptrarray GPtrArray *plat_tfilters = NULL;
+	NMPLookup lookup;
+	guint i;
+	gboolean success = TRUE;
+	gs_unref_hashtable GHashTable *known_tfilters_idx = NULL;
+
+	nm_assert (NM_IS_PLATFORM (self));
+	nm_assert (ifindex > 0);
+
+	known_tfilters_idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash,
+	                                       (GEqualFunc) nmp_object_id_equal);
+
+	if (known_tfilters) {
+		for (i = 0; i < known_tfilters->len; i++) {
+			const NMPObject *q = g_ptr_array_index (known_tfilters, i);
+
+			g_hash_table_insert (known_tfilters_idx, (gpointer) q, (gpointer) q);
+		}
+	}
+
+	plat_tfilters = nm_platform_lookup_clone (self,
+	                                          nmp_lookup_init_object (&lookup,
+	                                                                  NMP_OBJECT_TYPE_TFILTER,
+	                                                                  ifindex),
+	                                          NULL, NULL);
+
+
+	if (plat_tfilters) {
+		for (i = 0; i < plat_tfilters->len; i++) {
+			const NMPObject *q = g_ptr_array_index (plat_tfilters, i);
+
+			if (!g_hash_table_lookup (known_tfilters_idx, q))
+				success &= nm_platform_object_delete (self, q);
+		}
+	}
+
+	if (known_tfilters) {
+		for (i = 0; i < known_tfilters->len; i++) {
+			const NMPObject *q = g_ptr_array_index (known_tfilters, i);
+
+			success &= (nm_platform_tfilter_add (self, NMP_NLM_FLAG_ADD,
+			                                     NMP_OBJECT_CAST_TFILTER (q)) == NM_PLATFORM_ERROR_SUCCESS);
+		}
+	}
+
+	return success;
+}
+
+/*****************************************************************************/
+
 const char *
 nm_platform_vlan_qos_mapping_to_string (const char *name,
                                         const NMVlanQosMapping *map,
@@ -4976,6 +5113,40 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *bu
 	return buf;
 }
 
+NM_UTILS_FLAGS2STR_DEFINE_STATIC (_rtm_flags_to_string, unsigned,
+	NM_UTILS_FLAGS2STR (RTNH_F_DEAD,                   "dead"),
+	NM_UTILS_FLAGS2STR (RTNH_F_PERVASIVE,              "pervasive"),
+	NM_UTILS_FLAGS2STR (RTNH_F_ONLINK,                 "onlink"),
+	NM_UTILS_FLAGS2STR (8  /*RTNH_F_OFFLOAD*/,         "offload"),
+	NM_UTILS_FLAGS2STR (16 /*RTNH_F_LINKDOWN*/,        "linkdown"),
+	NM_UTILS_FLAGS2STR (32 /*RTNH_F_UNRESOLVED*/,      "unresolved"),
+
+	NM_UTILS_FLAGS2STR (RTM_F_NOTIFY,                  "notify"),
+	NM_UTILS_FLAGS2STR (RTM_F_CLONED,                  "cloned"),
+	NM_UTILS_FLAGS2STR (RTM_F_EQUALIZE,                "equalize"),
+	NM_UTILS_FLAGS2STR (RTM_F_PREFIX,                  "prefix"),
+	NM_UTILS_FLAGS2STR (0x1000 /*RTM_F_LOOKUP_TABLE*/, "lookup-table"),
+	NM_UTILS_FLAGS2STR (0x2000 /*RTM_F_FIB_MATCH*/,    "fib-match"),
+);
+
+#define _RTM_FLAGS_TO_STRING_MAXLEN 200
+
+static const char *
+_rtm_flags_to_string_full (char *buf, gsize buf_size, unsigned rtm_flags)
+{
+	const char *buf0 = buf;
+
+	nm_assert (buf_size >= _RTM_FLAGS_TO_STRING_MAXLEN);
+
+	if (!rtm_flags)
+		return "";
+
+	nm_utils_strbuf_append_str (&buf, &buf_size, " rtm_flags ");
+	_rtm_flags_to_string (rtm_flags, buf, buf_size);
+	nm_assert (strlen (buf) < buf_size);
+	return buf0;
+}
+
 /**
  * nm_platform_ip4_route_to_string:
  * @route: pointer to NMPlatformIP4Route route structure
@@ -4997,6 +5168,7 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	char str_table[30];
 	char str_scope[30], s_source[50];
 	char str_tos[32], str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32];
+	char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN];
 
 	if (!nm_utils_to_string_buffer_init_null (route, &buf, &len))
 		return buf;
@@ -5015,7 +5187,7 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	            " metric %"G_GUINT32_FORMAT
 	            " mss %"G_GUINT32_FORMAT
 	            " rt-src %s" /* protocol */
-	            "%s" /* cloned */
+	            "%s" /* rtm_flags */
 	            "%s%s" /* scope */
 	            "%s%s" /* pref-src */
 	            "%s" /* tos */
@@ -5033,7 +5205,7 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	            route->metric,
 	            route->mss,
 	            nmp_utils_ip_config_source_to_string (route->rt_source, s_source, sizeof (s_source)),
-	            route->rt_cloned ? " cloned" : "",
+	            _rtm_flags_to_string_full (str_rtm_flags, sizeof (str_rtm_flags), route->r_rtm_flags),
 	            route->scope_inv ? " scope " : "",
 	            route->scope_inv ? (nm_platform_route_scope2str (nm_platform_route_scope_inv (route->scope_inv), str_scope, sizeof (str_scope))) : "",
 	            route->pref_src ? " pref-src " : "",
@@ -5069,6 +5241,7 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	char str_pref2[30];
 	char str_dev[TO_STRING_DEV_BUF_SIZE], s_source[50];
 	char str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32];
+	char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN];
 
 	if (!nm_utils_to_string_buffer_init_null (route, &buf, &len))
 		return buf;
@@ -5092,7 +5265,7 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	            " mss %"G_GUINT32_FORMAT
 	            " rt-src %s" /* protocol */
 	            "%s" /* source */
-	            "%s" /* cloned */
+	            "%s" /* rtm_flags */
 	            "%s%s" /* pref-src */
 	            "%s" /* window */
 	            "%s" /* cwnd */
@@ -5112,7 +5285,7 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	            route->src_plen || !IN6_IS_ADDR_UNSPECIFIED (&route->src)
 	              ? nm_sprintf_buf (s_src_all, " src %s/%u", nm_utils_inet6_ntop (&route->src, s_src), (unsigned) route->src_plen)
 	              : "",
-	            route->rt_cloned ? " cloned" : "",
+	            _rtm_flags_to_string_full (str_rtm_flags, sizeof (str_rtm_flags), route->r_rtm_flags),
 	            s_pref_src[0] ? " pref-src " : "",
 	            s_pref_src[0] ? s_pref_src : "",
 	            route->window   || route->lock_window   ? nm_sprintf_buf (str_window,   " window %s%"G_GUINT32_FORMAT,   route->lock_window   ? "lock " : "", route->window)   : "",
@@ -5125,6 +5298,129 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	return buf;
 }
 
+const char *
+nm_platform_qdisc_to_string (const NMPlatformQdisc *qdisc, char *buf, gsize len)
+{
+	char str_dev[TO_STRING_DEV_BUF_SIZE];
+
+	if (!nm_utils_to_string_buffer_init_null (qdisc, &buf, &len))
+		return buf;
+
+	g_snprintf (buf, len, "%s%s family %d handle %x parent %x info %x",
+	            qdisc->kind,
+	            _to_string_dev (NULL, qdisc->ifindex, str_dev, sizeof (str_dev)),
+	            qdisc->addr_family,
+	            qdisc->handle,
+	            qdisc->parent,
+	            qdisc->info);
+
+	return buf;
+}
+
+void
+nm_platform_qdisc_hash_update (const NMPlatformQdisc *obj, NMHashState *h)
+{
+	nm_hash_update_str (h, obj->kind);
+	nm_hash_update_vals (h,
+	                     obj->ifindex,
+	                     obj->addr_family,
+	                     obj->handle,
+	                     obj->parent,
+	                     obj->info);
+}
+
+int
+nm_platform_qdisc_cmp (const NMPlatformQdisc *a, const NMPlatformQdisc *b)
+{
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, ifindex);
+	NM_CMP_FIELD (a, b, parent);
+	NM_CMP_FIELD_STR_INTERNED (a, b, kind);
+	NM_CMP_FIELD (a, b, addr_family);
+	NM_CMP_FIELD (a, b, handle);
+	NM_CMP_FIELD (a, b, info);
+
+	return 0;
+}
+
+const char *
+nm_platform_tfilter_to_string (const NMPlatformTfilter *tfilter, char *buf, gsize len)
+{
+	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	char act_buf[300];
+	char *p;
+	gsize l;
+
+	if (!nm_utils_to_string_buffer_init_null (tfilter, &buf, &len))
+		return buf;
+
+	if (tfilter->action.kind) {
+		p = act_buf;
+		l = sizeof (act_buf);
+
+		nm_utils_strbuf_append (&p, &l, " \"%s\"", tfilter->action.kind);
+		if (nm_streq (tfilter->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) {
+			gs_free char *t = NULL;
+
+			nm_utils_strbuf_append (&p, &l,
+			                        " (\"%s\")",
+			                        nm_utils_str_utf8safe_escape (tfilter->action.kind,
+			                                                        NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL
+			                                                      | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII,
+			                                                      &t));
+		}
+	} else
+		act_buf[0] = '\0';
+
+	g_snprintf (buf, len, "%s%s family %d handle %x parent %x info %x%s",
+	            tfilter->kind,
+	            _to_string_dev (NULL, tfilter->ifindex, str_dev, sizeof (str_dev)),
+	            tfilter->addr_family,
+	            tfilter->handle,
+	            tfilter->parent,
+	            tfilter->info,
+	            act_buf);
+
+	return buf;
+}
+
+void
+nm_platform_tfilter_hash_update (const NMPlatformTfilter *obj, NMHashState *h)
+{
+	nm_hash_update_str (h, obj->kind);
+	nm_hash_update_vals (h,
+	                     obj->ifindex,
+	                     obj->addr_family,
+	                     obj->handle,
+	                     obj->parent,
+	                     obj->info);
+	nm_hash_update_str (h, obj->action.kind);
+	if (obj->action.kind) {
+		if (nm_streq (obj->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE))
+			nm_hash_update_str (h, obj->action.simple.sdata);
+	}
+}
+
+int
+nm_platform_tfilter_cmp (const NMPlatformTfilter *a, const NMPlatformTfilter *b)
+{
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, ifindex);
+	NM_CMP_FIELD (a, b, parent);
+	NM_CMP_FIELD_STR_INTERNED (a, b, kind);
+	NM_CMP_FIELD (a, b, addr_family);
+	NM_CMP_FIELD (a, b, handle);
+	NM_CMP_FIELD (a, b, info);
+
+	NM_CMP_FIELD_STR_INTERNED (a, b, action.kind);
+	if (a->action.kind) {
+		if (nm_streq (a->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE))
+			NM_CMP_FIELD_STR (a, b, action.simple.sdata);
+	}
+
+	return 0;
+}
+
 void
 nm_platform_link_hash_update (const NMPlatformLink *obj, NMHashState *h)
 {
@@ -5538,6 +5834,7 @@ nm_platform_ip4_route_hash_update (const NMPlatformIP4Route *obj, NMPlatformIPRo
 		                     obj->initcwnd,
 		                     obj->initrwnd,
 		                     obj->mtu,
+		                     obj->r_rtm_flags & RTNH_F_ONLINK,
 		                     NM_HASH_COMBINE_BOOLS (guint8,
 		                                            obj->lock_window,
 		                                            obj->lock_cwnd,
@@ -5563,8 +5860,8 @@ nm_platform_ip4_route_hash_update (const NMPlatformIP4Route *obj, NMPlatformIPRo
 		                     obj->initcwnd,
 		                     obj->initrwnd,
 		                     obj->mtu,
+		                     obj->r_rtm_flags & (RTM_F_CLONED | RTNH_F_ONLINK),
 		                     NM_HASH_COMBINE_BOOLS (guint8,
-		                                            obj->rt_cloned,
 		                                            obj->lock_window,
 		                                            obj->lock_cwnd,
 		                                            obj->lock_initcwnd,
@@ -5589,8 +5886,8 @@ nm_platform_ip4_route_hash_update (const NMPlatformIP4Route *obj, NMPlatformIPRo
 		                     obj->initcwnd,
 		                     obj->initrwnd,
 		                     obj->mtu,
+		                     obj->r_rtm_flags,
 		                     NM_HASH_COMBINE_BOOLS (guint8,
-		                                            obj->rt_cloned,
 		                                            obj->lock_window,
 		                                            obj->lock_cwnd,
 		                                            obj->lock_initcwnd,
@@ -5627,6 +5924,8 @@ nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route
 			NM_CMP_FIELD (a, b, initcwnd);
 			NM_CMP_FIELD (a, b, initrwnd);
 			NM_CMP_FIELD (a, b, mtu);
+			NM_CMP_DIRECT (a->r_rtm_flags & RTNH_F_ONLINK,
+			               b->r_rtm_flags & RTNH_F_ONLINK);
 			NM_CMP_FIELD_UNSAFE (a, b, lock_window);
 			NM_CMP_FIELD_UNSAFE (a, b, lock_cwnd);
 			NM_CMP_FIELD_UNSAFE (a, b, lock_initcwnd);
@@ -5660,7 +5959,11 @@ nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route
 		}
 		NM_CMP_FIELD (a, b, mss);
 		NM_CMP_FIELD (a, b, pref_src);
-		NM_CMP_FIELD_UNSAFE (a, b, rt_cloned);
+		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
+			NM_CMP_DIRECT (a->r_rtm_flags & (RTM_F_CLONED | RTNH_F_ONLINK),
+			               b->r_rtm_flags & (RTM_F_CLONED | RTNH_F_ONLINK));
+		} else
+			NM_CMP_FIELD (a, b, r_rtm_flags);
 		NM_CMP_FIELD (a, b, tos);
 		NM_CMP_FIELD_UNSAFE (a, b, lock_window);
 		NM_CMP_FIELD_UNSAFE (a, b, lock_cwnd);
@@ -5717,8 +6020,8 @@ nm_platform_ip6_route_hash_update (const NMPlatformIP6Route *obj, NMPlatformIPRo
 		                     obj->src_plen,
 		                     nmp_utils_ip_config_source_round_trip_rtprot (obj->rt_source),
 		                     obj->mss,
+		                     obj->r_rtm_flags & RTM_F_CLONED,
 		                     NM_HASH_COMBINE_BOOLS (guint8,
-		                                            obj->rt_cloned,
 		                                            obj->lock_window,
 		                                            obj->lock_cwnd,
 		                                            obj->lock_initcwnd,
@@ -5744,8 +6047,8 @@ nm_platform_ip6_route_hash_update (const NMPlatformIP6Route *obj, NMPlatformIPRo
 		                     obj->src_plen,
 		                     obj->rt_source,
 		                     obj->mss,
+		                     obj->r_rtm_flags,
 		                     NM_HASH_COMBINE_BOOLS (guint8,
-		                                            obj->rt_cloned,
 		                                            obj->lock_window,
 		                                            obj->lock_cwnd,
 		                                            obj->lock_initcwnd,
@@ -5810,7 +6113,11 @@ nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route
 			NM_CMP_FIELD (a, b, rt_source);
 		}
 		NM_CMP_FIELD (a, b, mss);
-		NM_CMP_FIELD_UNSAFE (a, b, rt_cloned);
+		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
+			NM_CMP_DIRECT (a->r_rtm_flags & RTM_F_CLONED,
+			               b->r_rtm_flags & RTM_F_CLONED);
+		} else
+			NM_CMP_FIELD (a, b, r_rtm_flags);
 		NM_CMP_FIELD_UNSAFE (a, b, lock_window);
 		NM_CMP_FIELD_UNSAFE (a, b, lock_cwnd);
 		NM_CMP_FIELD_UNSAFE (a, b, lock_initcwnd);
@@ -5927,6 +6234,18 @@ log_ip6_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatform
 	_LOGD ("signal: route   6 %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_ip6_route_to_string (route, NULL, 0));
 }
 
+static void
+log_qdisc (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformQdisc *qdisc, NMPlatformSignalChangeType change_type, gpointer user_data)
+{
+	_LOGD ("signal: qdisc %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_qdisc_to_string (qdisc, NULL, 0));
+}
+
+static void
+log_tfilter (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatformTfilter *tfilter, NMPlatformSignalChangeType change_type, gpointer user_data)
+{
+	_LOGD ("signal: tfilter %7s: %s", nm_platform_signal_change_type_to_string (change_type), nm_platform_tfilter_to_string (tfilter, NULL, 0));
+}
+
 /*****************************************************************************/
 
 void
@@ -6200,4 +6519,6 @@ nm_platform_class_init (NMPlatformClass *platform_class)
 	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, log_ip6_address);
 	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,   NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,   log_ip4_route);
 	SIGNAL (NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,   NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,   log_ip6_route);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_QDISC,       NM_PLATFORM_SIGNAL_QDISC_CHANGED,       log_qdisc);
+	SIGNAL (NM_PLATFORM_SIGNAL_ID_TFILTER,     NM_PLATFORM_SIGNAL_TFILTER_CHANGED,     log_tfilter);
 }
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index d155c109..f6bf02bf 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.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 (C) 2009 - 2010 Red Hat, Inc.
+ * Copyright (C) 2009 - 2017 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_PLATFORM_H__
@@ -236,6 +236,8 @@ typedef enum { /*< skip >*/
 	NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS,
 	NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,
 	NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,
+	NM_PLATFORM_SIGNAL_ID_QDISC,
+	NM_PLATFORM_SIGNAL_ID_TFILTER,
 	_NM_PLATFORM_SIGNAL_ID_LAST,
 } NMPlatformSignalIdType;
 
@@ -375,13 +377,6 @@ typedef union {
 	\
 	guint8 plen; \
 	\
-	/* the route has rtm_flags set to RTM_F_CLONED. Such a route
-	 * is hidden by platform and does not exist from the point-of-view
-	 * of platform users. This flag is internal to track those hidden
-	 * routes. Such a route is not alive, according to nmp_object_is_alive(). */ \
-	bool rt_cloned:1; \
-	\
-	\
 	/* RTA_METRICS:
 	 *
 	 * For IPv4 routes, these properties are part of their
@@ -401,6 +396,18 @@ typedef union {
 	bool lock_initrwnd:1; \
 	bool lock_mtu:1; \
 	\
+	/* rtnh_flags
+	 *
+	 * Routes with rtm_flags RTM_F_CLONED are hidden by platform and
+	 * do not exist from the point-of-view of platform users.
+	 * Such a route is not alive, according to nmp_object_is_alive().
+	 *
+	 * XXX: currently we ignore all flags except RTM_F_CLONED
+	 * and RTNH_F_ONLINK for IPv4.
+	 * We also may not properly consider the flags as part of the ID
+	 * in route-cmp. */ \
+	unsigned r_rtm_flags; \
+	\
 	/* RTA_METRICS.RTAX_ADVMSS (iproute2: advmss) */ \
 	guint32 mss; \
 	\
@@ -441,23 +448,11 @@ typedef struct {
 	};
 } NMPlatformIPRoute;
 
-#if _NM_CC_SUPPORT_GENERIC
-#define NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route) \
-	(_Generic ((route), \
-	           const NMPlatformIPRoute  *: ((const NMPlatformIPRoute *) (route))->plen, \
-	                 NMPlatformIPRoute  *: ((const NMPlatformIPRoute *) (route))->plen, \
-	           const NMPlatformIPXRoute *: ((const NMPlatformIPRoute *) (route))->plen, \
-	                 NMPlatformIPXRoute *: ((const NMPlatformIPRoute *) (route))->plen, \
-	           const NMPlatformIP4Route *: ((const NMPlatformIPRoute *) (route))->plen, \
-	                 NMPlatformIP4Route *: ((const NMPlatformIPRoute *) (route))->plen, \
-	           const NMPlatformIP6Route *: ((const NMPlatformIPRoute *) (route))->plen, \
-	                 NMPlatformIP6Route *: ((const NMPlatformIPRoute *) (route))->plen, \
-	           const void               *: ((const NMPlatformIPRoute *) (route))->plen, \
-	                 void               *: ((const NMPlatformIPRoute *) (route))->plen) == 0)
-#else
+#define NM_PLATFORM_IP_ROUTE_CAST(route) \
+	NM_CONSTCAST (NMPlatformIPRoute, (route), NMPlatformIPXRoute, NMPlatformIP4Route, NMPlatformIP6Route)
+
 #define NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route) \
-	( ((const NMPlatformIPRoute *) (route))->plen <= 0 )
-#endif
+	(NM_PLATFORM_IP_ROUTE_CAST (route)->plen <= 0)
 
 struct _NMPlatformIP4Route {
 	__NMPlatformIPRoute_COMMON;
@@ -534,6 +529,37 @@ typedef union {
 
 #undef __NMPlatformIPRoute_COMMON
 
+typedef struct {
+	__NMPlatformObject_COMMON;
+	const char *kind;
+	int addr_family;
+	guint32 handle;
+	guint32 parent;
+	guint32 info;
+} NMPlatformQdisc;
+
+typedef struct {
+	char sdata[32];
+} NMPlatformActionSimple;
+
+typedef struct {
+	const char *kind;
+	union {
+		NMPlatformActionSimple simple;
+	};
+} NMPlatformAction;
+
+#define NM_PLATFORM_ACTION_KIND_SIMPLE "simple"
+
+typedef struct {
+	__NMPlatformObject_COMMON;
+	const char *kind;
+	int addr_family;
+	guint32 handle;
+	guint32 parent;
+	guint32 info;
+	NMPlatformAction action;
+} NMPlatformTfilter;
 
 #undef __NMPlatformObject_COMMON
 
@@ -805,6 +831,8 @@ typedef struct {
 	gboolean    (*mesh_set_channel)      (NMPlatform *, int ifindex, guint32 channel);
 	gboolean    (*mesh_set_ssid)         (NMPlatform *, int ifindex, const guint8 *ssid, gsize len);
 
+	gboolean (*object_delete) (NMPlatform *, const NMPObject *obj);
+
 	gboolean (*ip4_address_add) (NMPlatform *,
 	                             int ifindex,
 	                             in_addr_t address,
@@ -829,14 +857,20 @@ typedef struct {
 	                                 NMPNlmFlags flags,
 	                                 int addr_family,
 	                                 const NMPlatformIPRoute *route);
-	gboolean (*ip_route_delete) (NMPlatform *, const NMPObject *obj);
-
 	NMPlatformError (*ip_route_get) (NMPlatform *self,
 	                                 int addr_family,
 	                                 gconstpointer address,
 	                                 int oif_ifindex,
 	                                 NMPObject **out_route);
 
+	NMPlatformError (*qdisc_add)   (NMPlatform *self,
+	                                NMPNlmFlags flags,
+	                                const NMPlatformQdisc *qdisc);
+
+	NMPlatformError (*tfilter_add)   (NMPlatform *self,
+	                                  NMPNlmFlags flags,
+	                                  const NMPlatformTfilter *tfilter);
+
 	NMPlatformKernelSupportFlags (*check_kernel_support) (NMPlatform * self,
 	                                                      NMPlatformKernelSupportFlags request_flags);
 } NMPlatformClass;
@@ -857,6 +891,8 @@ typedef struct {
 #define NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED "ip6-address-changed"
 #define NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED "ip4-route-changed"
 #define NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED "ip6-route-changed"
+#define NM_PLATFORM_SIGNAL_QDISC_CHANGED "qdisc-changed"
+#define NM_PLATFORM_SIGNAL_TFILTER_CHANGED "tfilter-changed"
 
 const char *nm_platform_signal_change_type_to_string (NMPlatformSignalChangeType change_type);
 
@@ -1194,6 +1230,8 @@ NMPlatformError nm_platform_link_sit_add (NMPlatform *self,
 
 const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address);
 
+gboolean nm_platform_object_delete (NMPlatform *self, const NMPObject *route);
+
 gboolean nm_platform_ip4_address_add (NMPlatform *self,
                                       int ifindex,
                                       in_addr_t address,
@@ -1228,8 +1266,6 @@ NMPlatformError nm_platform_ip_route_add (NMPlatform *self,
 NMPlatformError nm_platform_ip4_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route);
 NMPlatformError nm_platform_ip6_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route);
 
-gboolean nm_platform_ip_route_delete (NMPlatform *self, const NMPObject *route);
-
 GPtrArray *nm_platform_ip_route_get_prune_list (NMPlatform *self,
                                                 int addr_family,
                                                 int ifindex,
@@ -1252,6 +1288,20 @@ NMPlatformError nm_platform_ip_route_get (NMPlatform *self,
                                           int oif_ifindex,
                                           NMPObject **out_route);
 
+NMPlatformError nm_platform_qdisc_add   (NMPlatform *self,
+                                         NMPNlmFlags flags,
+                                         const NMPlatformQdisc *qdisc);
+gboolean nm_platform_qdisc_sync         (NMPlatform *self,
+                                         int ifindex,
+                                         GPtrArray *known_qdiscs);
+
+NMPlatformError nm_platform_tfilter_add   (NMPlatform *self,
+                                           NMPNlmFlags flags,
+                                           const NMPlatformTfilter *tfilter);
+gboolean nm_platform_tfilter_sync         (NMPlatform *self,
+                                           int ifindex,
+                                           GPtrArray *known_tfilters);
+
 const char *nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len);
 const char *nm_platform_lnk_gre_to_string (const NMPlatformLnkGre *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_infiniband_to_string (const NMPlatformLnkInfiniband *lnk, char *buf, gsize len);
@@ -1266,6 +1316,8 @@ const char *nm_platform_ip4_address_to_string (const NMPlatformIP4Address *addre
 const char *nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *buf, gsize len);
 const char *nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsize len);
 const char *nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsize len);
+const char *nm_platform_qdisc_to_string (const NMPlatformQdisc *qdisc, char *buf, gsize len);
+const char *nm_platform_tfilter_to_string (const NMPlatformTfilter *tfilter, char *buf, gsize len);
 
 const char *nm_platform_vlan_qos_mapping_to_string (const char *name,
                                                     const NMVlanQosMapping *map,
@@ -1301,6 +1353,9 @@ nm_platform_ip6_route_cmp_full (const NMPlatformIP6Route *a, const NMPlatformIP6
 	return nm_platform_ip6_route_cmp (a, b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL);
 }
 
+int nm_platform_qdisc_cmp (const NMPlatformQdisc *a, const NMPlatformQdisc *b);
+int nm_platform_tfilter_cmp (const NMPlatformTfilter *a, const NMPlatformTfilter *b);
+
 void nm_platform_link_hash_update (const NMPlatformLink *obj, NMHashState *h);
 void nm_platform_ip4_address_hash_update (const NMPlatformIP4Address *obj, NMHashState *h);
 void nm_platform_ip6_address_hash_update (const NMPlatformIP6Address *obj, NMHashState *h);
@@ -1316,6 +1371,9 @@ void nm_platform_lnk_sit_hash_update (const NMPlatformLnkSit *obj, NMHashState *
 void nm_platform_lnk_vlan_hash_update (const NMPlatformLnkVlan *obj, NMHashState *h);
 void nm_platform_lnk_vxlan_hash_update (const NMPlatformLnkVxlan *obj, NMHashState *h);
 
+void nm_platform_qdisc_hash_update (const NMPlatformQdisc *obj, NMHashState *h);
+void nm_platform_tfilter_hash_update (const NMPlatformTfilter *obj, NMHashState *h);
+
 NMPlatformKernelSupportFlags nm_platform_check_kernel_support (NMPlatform *self,
                                                                NMPlatformKernelSupportFlags request_flags);
 
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
index 34215828..bc305f01 100644
--- a/src/platform/nmp-netns.c
+++ b/src/platform/nmp-netns.c
@@ -290,6 +290,7 @@ _netns_new (GError **error)
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "Failed opening netns: %s",
 		             g_strerror (errsv));
+		errno = errsv;
 		return NULL;
 	}
 
@@ -300,6 +301,7 @@ _netns_new (GError **error)
 		             "Failed opening mntns: %s",
 		             g_strerror (errsv));
 		nm_close (fd_net);
+		errno = errsv;
 		return NULL;
 	}
 
@@ -479,6 +481,7 @@ nmp_netns_new (void)
 	if (!_stack_peek ()) {
 		/* there are no netns instances. We cannot create a new one
 		 * (because after unshare we couldn't return to the original one). */
+		errno = ENOTSUP;
 		return NULL;
 	}
 
@@ -508,6 +511,7 @@ nmp_netns_new (void)
 
 	self = _netns_new (&error);
 	if (!self) {
+		errsv = errno;
 		_LOGE (NULL, "failed to create netns after unshare: %s", error->message);
 		g_clear_error (&error);
 		goto err_out;
@@ -518,6 +522,7 @@ nmp_netns_new (void)
 	return self;
 err_out:
 	_netns_switch_pop (NULL, _CLONE_NS_ALL);
+	errno = errsv;
 	return NULL;
 }
 
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index a8600705..1a9b9325 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -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 (C) 2015 Red Hat, Inc.
+ * Copyright (C) 2015 - 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -186,11 +186,13 @@ _idx_obj_part (const DedupMultiIdxType *idx_type,
 		}
 		return 1;
 
-	case NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX:
+	case NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX:
 		if (   !NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_a), NMP_OBJECT_TYPE_IP4_ADDRESS,
 		                                                NMP_OBJECT_TYPE_IP6_ADDRESS,
 		                                                NMP_OBJECT_TYPE_IP4_ROUTE,
-		                                                NMP_OBJECT_TYPE_IP6_ROUTE)
+		                                                NMP_OBJECT_TYPE_IP6_ROUTE,
+		                                                NMP_OBJECT_TYPE_QDISC,
+		                                                NMP_OBJECT_TYPE_TFILTER)
 		    || !nmp_object_is_visible (obj_a)) {
 			if (h)
 				nm_hash_update_val (h, obj_a);
@@ -737,6 +739,8 @@ _vt_cmd_plobj_to_string_id (ip4_address, NMPlatformIP4Address, "%d: %s/%d%s%s",
                                                                obj->peer_address != obj->address ? "," : "",
                                                                obj->peer_address != obj->address ? nm_utils_inet4_ntop (nm_utils_ip4_address_clear_host_address (obj->peer_address, obj->plen), buf2) : "");
 _vt_cmd_plobj_to_string_id (ip6_address, NMPlatformIP6Address, "%d: %s",        obj->ifindex, nm_utils_inet6_ntop (&obj->address, buf1));
+_vt_cmd_plobj_to_string_id (qdisc,       NMPlatformQdisc,      "%d: %d",        obj->ifindex, obj->parent);
+_vt_cmd_plobj_to_string_id (tfilter,     NMPlatformTfilter,    "%d: %d",        obj->ifindex, obj->parent);
 
 void
 nmp_object_hash_update (const NMPObject *obj, NMHashState *h)
@@ -1035,6 +1039,14 @@ _vt_cmd_plobj_id_cmp (ip6_address, NMPlatformIP6Address,
                       /* for IPv6 addresses, the prefix length is not part of the primary identifier. */
                       NM_CMP_FIELD_IN6ADDR (obj1, obj2, address);
 )
+_vt_cmd_plobj_id_cmp (qdisc, NMPlatformQdisc,
+                      NM_CMP_FIELD (obj1, obj2, ifindex);
+                      NM_CMP_FIELD (obj1, obj2, parent);
+)
+_vt_cmd_plobj_id_cmp (tfilter, NMPlatformTfilter,
+                      NM_CMP_FIELD (obj1, obj2, ifindex);
+                      NM_CMP_FIELD (obj1, obj2, handle);
+)
 
 static int
 _vt_cmd_plobj_id_cmp_ip4_route (const NMPlatformObject *obj1, const NMPlatformObject *obj2)
@@ -1113,6 +1125,16 @@ _vt_cmd_plobj_id_hash_update (ip4_route, NMPlatformIP4Route, {
 _vt_cmd_plobj_id_hash_update (ip6_route, NMPlatformIP6Route, {
 	nm_platform_ip6_route_hash_update (obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h);
 })
+_vt_cmd_plobj_id_hash_update (qdisc, NMPlatformQdisc, {
+	nm_hash_update_vals (h,
+	                     obj->ifindex,
+	                     obj->parent);
+})
+_vt_cmd_plobj_id_hash_update (tfilter, NMPlatformTfilter, {
+	nm_hash_update_vals (h,
+	                     obj->ifindex,
+	                     obj->handle);
+})
 
 static inline void
 _vt_cmd_plobj_hash_update_ip4_route (const NMPlatformObject *obj, NMHashState *h)
@@ -1170,7 +1192,20 @@ _vt_cmd_obj_is_alive_ipx_route (const NMPObject *obj)
 	 * Instead we create a dead object, and nmp_cache_update_netlink()
 	 * will remove the old version of the update.
 	 **/
-	return obj->object.ifindex > 0 && !obj->ip_route.rt_cloned;
+	return    obj->object.ifindex > 0
+	       && !NM_FLAGS_HAS (obj->ip_route.r_rtm_flags, RTM_F_CLONED);
+}
+
+static gboolean
+_vt_cmd_obj_is_alive_qdisc (const NMPObject *obj)
+{
+	return obj->object.ifindex > 0;
+}
+
+static gboolean
+_vt_cmd_obj_is_alive_tfilter (const NMPObject *obj)
+{
+	return obj->object.ifindex > 0;
 }
 
 gboolean
@@ -1202,6 +1237,12 @@ _vt_cmd_obj_is_visible_link (const NMPObject *obj)
 
 /*****************************************************************************/
 
+static const guint8 _supported_cache_ids_object[] = {
+	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+	NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX,
+	0,
+};
+
 static const guint8 _supported_cache_ids_link[] = {
 	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
 	NMP_CACHE_ID_TYPE_LINK_BY_IFNAME,
@@ -1210,13 +1251,13 @@ static const guint8 _supported_cache_ids_link[] = {
 
 static const guint8 _supported_cache_ids_ipx_address[] = {
 	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
-	NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX,
+	NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX,
 	0,
 };
 
 static const guint8 _supported_cache_ids_ipx_route[] = {
 	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
-	NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX,
+	NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX,
 	NMP_CACHE_ID_TYPE_DEFAULT_ROUTES,
 	NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
 	0,
@@ -1501,6 +1542,8 @@ nmp_lookup_init_obj_type (NMPLookup *lookup,
 	case NMP_OBJECT_TYPE_IP6_ADDRESS:
 	case NMP_OBJECT_TYPE_IP4_ROUTE:
 	case NMP_OBJECT_TYPE_IP6_ROUTE:
+	case NMP_OBJECT_TYPE_QDISC:
+	case NMP_OBJECT_TYPE_TFILTER:
 		o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
 		lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_TYPE;
 		return _L (lookup);
@@ -1528,9 +1571,9 @@ nmp_lookup_init_link_by_ifname (NMPLookup *lookup,
 }
 
 const NMPLookup *
-nmp_lookup_init_addrroute (NMPLookup *lookup,
-                           NMPObjectType obj_type,
-                           int ifindex)
+nmp_lookup_init_object (NMPLookup *lookup,
+                        NMPObjectType obj_type,
+                        int ifindex)
 {
 	NMPObject *o;
 
@@ -1538,7 +1581,9 @@ nmp_lookup_init_addrroute (NMPLookup *lookup,
 	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS,
 	                                NMP_OBJECT_TYPE_IP6_ADDRESS,
 	                                NMP_OBJECT_TYPE_IP4_ROUTE,
-	                                NMP_OBJECT_TYPE_IP6_ROUTE));
+	                                NMP_OBJECT_TYPE_IP6_ROUTE,
+	                                NMP_OBJECT_TYPE_QDISC,
+	                                NMP_OBJECT_TYPE_TFILTER));
 
 	if (ifindex <= 0) {
 		return nmp_lookup_init_obj_type (lookup,
@@ -1547,7 +1592,7 @@ nmp_lookup_init_addrroute (NMPLookup *lookup,
 
 	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
 	o->object.ifindex = ifindex;
-	lookup->cache_id_type = NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX;
+	lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX;
 	return _L (lookup);
 }
 
@@ -2566,6 +2611,42 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_plobj_hash_update              = _vt_cmd_plobj_hash_update_ip6_route,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_route_cmp_full,
 	},
+	[NMP_OBJECT_TYPE_QDISC - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_QDISC,
+		.sizeof_data                        = sizeof (NMPObjectQdisc),
+		.sizeof_public                      = sizeof (NMPlatformQdisc),
+		.obj_type_name                      = "qdisc",
+		.rtm_gettype                        = RTM_GETQDISC,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_QDISC,
+		.signal_type                        = NM_PLATFORM_SIGNAL_QDISC_CHANGED,
+		.supported_cache_ids                = _supported_cache_ids_object,
+		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_qdisc,
+		.cmd_plobj_id_cmp                   = _vt_cmd_plobj_id_cmp_qdisc,
+		.cmd_plobj_id_hash_update           = _vt_cmd_plobj_id_hash_update_qdisc,
+		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_qdisc,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_qdisc_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_qdisc_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_qdisc_cmp,
+	},
+	[NMP_OBJECT_TYPE_TFILTER - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_TFILTER,
+		.sizeof_data                        = sizeof (NMPObjectTfilter),
+		.sizeof_public                      = sizeof (NMPlatformTfilter),
+		.obj_type_name                      = "tfilter",
+		.rtm_gettype                        = RTM_GETTFILTER,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_TFILTER,
+		.signal_type                        = NM_PLATFORM_SIGNAL_TFILTER_CHANGED,
+		.supported_cache_ids                = _supported_cache_ids_object,
+		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_tfilter,
+		.cmd_plobj_id_cmp                   = _vt_cmd_plobj_id_cmp_tfilter,
+		.cmd_plobj_id_hash_update           = _vt_cmd_plobj_id_hash_update_tfilter,
+		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_tfilter,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_tfilter_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_tfilter_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_tfilter_cmp,
+	},
 	[NMP_OBJECT_TYPE_LNK_GRE - 1] = {
 		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_GRE,
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index 41fd08cb..e3862f2d 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.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 (C) 2015 Red Hat, Inc.
+ * Copyright (C) 2015 - 2017 Red Hat, Inc.
  */
 
 #ifndef __NMP_OBJECT_H__
@@ -35,8 +35,8 @@ typedef enum { /*< skip >*/
 
 typedef enum { /*< skip >*/
 	NMP_CACHE_OPS_UNCHANGED       = NM_PLATFORM_SIGNAL_NONE,
-	NMP_CACHE_OPS_UPDATED         = NM_PLATFORM_SIGNAL_CHANGED,
 	NMP_CACHE_OPS_ADDED           = NM_PLATFORM_SIGNAL_ADDED,
+	NMP_CACHE_OPS_UPDATED         = NM_PLATFORM_SIGNAL_CHANGED,
 	NMP_CACHE_OPS_REMOVED         = NM_PLATFORM_SIGNAL_REMOVED,
 } NMPCacheOpsType;
 
@@ -48,12 +48,12 @@ typedef enum { /*< skip >*/
  * but only route objects can be indexed by NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT.
  *
  * Of one index type, there can be multiple indexes or not.
- * For example, of the index type NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX there
+ * For example, of the index type NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX there
  * are multiple instances (for different route/addresses, v4/v6, per-ifindex).
  *
  * But one object, can only be indexed by one particular index of a
  * type. For example, a certain address instance is only indexed by
- * the index NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX with
+ * the index NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX with
  * matching v4/v6 and ifindex -- or maybe not at all if it isn't visible.
  * */
 typedef enum { /*< skip >*/
@@ -82,8 +82,8 @@ typedef enum { /*< skip >*/
 	 * separate for IPv4 and IPv6. */
 	NMP_CACHE_ID_TYPE_DEFAULT_ROUTES,
 
-	/* all the addresses/routes (by object-type) for an ifindex. */
-	NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX,
+	/* all the objects that have an ifindex (by object-type) for an ifindex. */
+	NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX,
 
 	/* Consider all the destination fields of a route, that is, the ID without the ifindex
 	 * and gateway (meaning: network/plen,metric).
@@ -225,6 +225,14 @@ typedef struct {
 	NMPlatformIP6Route _public;
 } NMPObjectIP6Route;
 
+typedef struct {
+	NMPlatformQdisc _public;
+} NMPObjectQdisc;
+
+typedef struct {
+	NMPlatformTfilter _public;
+} NMPObjectTfilter;
+
 struct _NMPObject {
 	union {
 		NMDedupMultiObj parent;
@@ -276,6 +284,11 @@ struct _NMPObject {
 		NMPlatformIP6Route      ip6_route;
 		NMPObjectIP4Route       _ip4_route;
 		NMPObjectIP6Route       _ip6_route;
+
+		NMPlatformQdisc         qdisc;
+		NMPObjectQdisc          _qdisc;
+		NMPlatformTfilter       tfilter;
+		NMPObjectTfilter        _tfilter;
 	};
 };
 
@@ -341,7 +354,7 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 		typeof (obj) _obj = (obj); \
 		\
 		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_LINK); \
-		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->link : NULL; \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->link : NULL; \
 	})
 
 #define NMP_OBJECT_CAST_IP_ADDRESS(obj) \
@@ -349,7 +362,7 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 		typeof (obj) _obj = (obj); \
 		\
 		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)); \
-		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ip_address : NULL; \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip_address : NULL; \
 	})
 
 #define NMP_OBJECT_CAST_IPX_ADDRESS(obj) \
@@ -357,7 +370,7 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 		typeof (obj) _obj = (obj); \
 		\
 		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)); \
-		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ipx_address : NULL; \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ipx_address : NULL; \
 	})
 
 #define NMP_OBJECT_CAST_IP4_ADDRESS(obj) \
@@ -365,7 +378,7 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 		typeof (obj) _obj = (obj); \
 		\
 		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_IP4_ADDRESS); \
-		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ip4_address : NULL; \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip4_address : NULL; \
 	})
 
 #define NMP_OBJECT_CAST_IP6_ADDRESS(obj) \
@@ -373,7 +386,7 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 		typeof (obj) _obj = (obj); \
 		\
 		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_IP6_ADDRESS); \
-		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ip6_address : NULL; \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip6_address : NULL; \
 	})
 
 #define NMP_OBJECT_CAST_IPX_ROUTE(obj) \
@@ -381,7 +394,7 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 		typeof (obj) _obj = (obj); \
 		\
 		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); \
-		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ipx_route : NULL; \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ipx_route : NULL; \
 	})
 
 #define NMP_OBJECT_CAST_IP_ROUTE(obj) \
@@ -389,7 +402,7 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 		typeof (obj) _obj = (obj); \
 		\
 		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); \
-		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ip_route : NULL; \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip_route : NULL; \
 	})
 
 #define NMP_OBJECT_CAST_IP4_ROUTE(obj) \
@@ -397,7 +410,7 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 		typeof (obj) _obj = (obj); \
 		\
 		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_IP4_ROUTE); \
-		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ip4_route : NULL; \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip4_route : NULL; \
 	})
 
 #define NMP_OBJECT_CAST_IP6_ROUTE(obj) \
@@ -405,7 +418,23 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 		typeof (obj) _obj = (obj); \
 		\
 		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_IP6_ROUTE); \
-		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ip6_route : NULL; \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip6_route : NULL; \
+	})
+
+#define NMP_OBJECT_CAST_QDISC(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_QDISC); \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->qdisc : NULL; \
+	})
+
+#define NMP_OBJECT_CAST_TFILTER(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_TFILTER); \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->tfilter : NULL; \
 	})
 
 const NMPClass *nmp_class_from_type (NMPObjectType obj_type);
@@ -536,9 +565,9 @@ const NMPLookup *nmp_lookup_init_obj_type (NMPLookup *lookup,
                                            NMPObjectType obj_type);
 const NMPLookup *nmp_lookup_init_link_by_ifname (NMPLookup *lookup,
                                                  const char *ifname);
-const NMPLookup *nmp_lookup_init_addrroute (NMPLookup *lookup,
-                                            NMPObjectType obj_type,
-                                            int ifindex);
+const NMPLookup *nmp_lookup_init_object (NMPLookup *lookup,
+                                         NMPObjectType obj_type,
+                                         int ifindex);
 const NMPLookup *nmp_lookup_init_route_default (NMPLookup *lookup,
                                                 NMPObjectType obj_type);
 const NMPLookup *nmp_lookup_init_route_by_weak_id (NMPLookup *lookup,
@@ -717,26 +746,26 @@ nm_platform_lookup_link_by_ifname (NMPlatform *platform,
 }
 
 static inline const NMDedupMultiHeadEntry *
-nm_platform_lookup_addrroute (NMPlatform *platform,
-                              NMPObjectType obj_type,
-                              int ifindex)
+nm_platform_lookup_object (NMPlatform *platform,
+                           NMPObjectType obj_type,
+                           int ifindex)
 {
 	NMPLookup lookup;
 
-	nmp_lookup_init_addrroute (&lookup, obj_type, ifindex);
+	nmp_lookup_init_object (&lookup, obj_type, ifindex);
 	return nm_platform_lookup (platform, &lookup);
 }
 
 static inline GPtrArray *
-nm_platform_lookup_addrroute_clone (NMPlatform *platform,
-                                    NMPObjectType obj_type,
-                                    int ifindex,
-                                    NMPObjectPredicateFunc predicate,
-                                    gpointer user_data)
+nm_platform_lookup_object_clone (NMPlatform *platform,
+                                 NMPObjectType obj_type,
+                                 int ifindex,
+                                 NMPObjectPredicateFunc predicate,
+                                 gpointer user_data)
 {
 	NMPLookup lookup;
 
-	nmp_lookup_init_addrroute (&lookup, obj_type, ifindex);
+	nmp_lookup_init_object (&lookup, obj_type, ifindex);
 	return nm_platform_lookup_clone (platform, &lookup, predicate, user_data);
 }
 
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 18076712..9a12f8f9 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -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 2016 Red Hat, Inc.
+ * Copyright 2016 - 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -78,9 +78,9 @@ _ipx_address_get_all (NMPlatform *self, int ifindex, NMPObjectType obj_type)
 	g_assert (NM_IS_PLATFORM (self));
 	g_assert (ifindex > 0);
 	g_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS));
-	nmp_lookup_init_addrroute (&lookup,
-	                           obj_type,
-	                           ifindex);
+	nmp_lookup_init_object (&lookup,
+	                        obj_type,
+	                        ifindex);
 	return nmp_cache_lookup_to_array (nm_platform_lookup (self, &lookup),
 	                                  obj_type,
 	                                  FALSE /*addresses are always visible. */);
@@ -108,9 +108,9 @@ nmtstp_platform_ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t n
 	nm_platform_process_events (platform);
 
 	nm_dedup_multi_iter_for_each (&iter,
-	                              nm_platform_lookup_addrroute (platform,
-	                                                            NMP_OBJECT_TYPE_IP4_ROUTE,
-	                                                            ifindex)) {
+	                              nm_platform_lookup_object (platform,
+	                                                         NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                                         ifindex)) {
 		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (iter.current->obj);
 
 		if (   r->ifindex != ifindex
@@ -120,7 +120,7 @@ nmtstp_platform_ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t n
 			continue;
 		}
 
-		return nm_platform_ip_route_delete (platform, NMP_OBJECT_UP_CAST (r));
+		return nm_platform_object_delete (platform, NMP_OBJECT_UP_CAST (r));
 	}
 
 	return TRUE;
@@ -134,9 +134,9 @@ nmtstp_platform_ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_
 	nm_platform_process_events (platform);
 
 	nm_dedup_multi_iter_for_each (&iter,
-	                              nm_platform_lookup_addrroute (platform,
-	                                                            NMP_OBJECT_TYPE_IP6_ROUTE,
-	                                                            ifindex)) {
+	                              nm_platform_lookup_object (platform,
+	                                                         NMP_OBJECT_TYPE_IP6_ROUTE,
+	                                                         ifindex)) {
 		const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (iter.current->obj);
 
 		if (   r->ifindex != ifindex
@@ -146,7 +146,7 @@ nmtstp_platform_ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_
 			continue;
 		}
 
-		return nm_platform_ip_route_delete (platform, NMP_OBJECT_UP_CAST (r));
+		return nm_platform_object_delete (platform, NMP_OBJECT_UP_CAST (r));
 	}
 
 	return TRUE;
@@ -1703,8 +1703,8 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 		errsv = errno;
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "pipe() failed with %d (%s)", errsv, strerror (errsv));
-		close (pipefd_c2p[0]);
-		close (pipefd_c2p[1]);
+		nm_close (pipefd_c2p[0]);
+		nm_close (pipefd_c2p[1]);
 		return FALSE;
 	}
 
@@ -1713,18 +1713,18 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 		errsv = errno;
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "fork() failed with %d (%s)", errsv, strerror (errsv));
-		close (pipefd_c2p[0]);
-		close (pipefd_c2p[1]);
-		close (pipefd_p2c[0]);
-		close (pipefd_p2c[1]);
+		nm_close (pipefd_c2p[0]);
+		nm_close (pipefd_c2p[1]);
+		nm_close (pipefd_p2c[0]);
+		nm_close (pipefd_p2c[1]);
 		return FALSE;
 	}
 
 	if (pid == 0) {
 		char read_buf[1];
 
-		close (pipefd_c2p[0]); /* close read-end */
-		close (pipefd_p2c[1]); /* close write-end */
+		nm_close (pipefd_c2p[0]); /* close read-end */
+		nm_close (pipefd_p2c[1]); /* close write-end */
 
 		if (unshare (unshare_flags) != 0) {
 			errsv = errno;
@@ -1742,7 +1742,7 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 			if (errsv == 0)
 				errsv = -2;
 		}
-		close (pipefd_c2p[1]);
+		nm_close (pipefd_c2p[1]);
 
 		/* wait until parent process terminates (or kills us). */
 		if (errsv == 0) {
@@ -1750,19 +1750,19 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 				r = read (pipefd_p2c[0], read_buf, sizeof (read_buf));
 			} while (r < 0 && errno == EINTR);
 		}
-		close (pipefd_p2c[0]);
+		nm_close (pipefd_p2c[0]);
 		_exit (0);
 	}
 
-	close (pipefd_c2p[1]); /* close write-end */
-	close (pipefd_p2c[0]); /* close read-end */
+	nm_close (pipefd_c2p[1]); /* close write-end */
+	nm_close (pipefd_p2c[0]); /* close read-end */
 
 	/* sync with child process. */
 	do {
 		r = read (pipefd_c2p[0], &errsv, sizeof (errsv));
 	} while (r < 0 && errno == EINTR);
 
-	close (pipefd_c2p[0]);
+	nm_close (pipefd_c2p[0]);
 
 	if (   r != sizeof (errsv)
 	    || errsv != 0) {
@@ -1775,7 +1775,7 @@ nmtstp_namespace_create (int unshare_flags, GError **error)
 			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 			             "child process signaled failure %d (%s)", errsv, strerror (errsv));
 		}
-		close (pipefd_p2c[1]);
+		nm_close (pipefd_p2c[1]);
 		kill (pid, SIGKILL);
 		do {
 			pid2 = waitpid (pid, &status, 0);
@@ -1809,7 +1809,7 @@ nmtstp_namespace_handle_release (NMTstpNamespaceHandle *ns_handle)
 
 	g_return_if_fail (ns_handle->pid > 0);
 
-	close (ns_handle->pipe_fd);
+	nm_close (ns_handle->pipe_fd);
 	ns_handle->pipe_fd = 0;
 
 	kill (ns_handle->pid, SIGKILL);
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index 4010aa2f..fb406a3f 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -227,22 +227,22 @@ static inline GPtrArray *
 nmtstp_ip4_route_get_all (NMPlatform *platform,
                           int ifindex)
 {
-	return nm_platform_lookup_addrroute_clone (platform,
-	                                           NMP_OBJECT_TYPE_IP4_ROUTE,
-	                                           ifindex,
-	                                           nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel,
-	                                           NULL);
+	return nm_platform_lookup_object_clone (platform,
+	                                        NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                        ifindex,
+	                                        nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel,
+	                                        NULL);
 }
 
 static inline GPtrArray *
 nmtstp_ip6_route_get_all (NMPlatform *platform,
                           int ifindex)
 {
-	return nm_platform_lookup_addrroute_clone (platform,
-	                                           NMP_OBJECT_TYPE_IP6_ROUTE,
-	                                           ifindex,
-	                                           nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel,
-	                                           NULL);
+	return nm_platform_lookup_object_clone (platform,
+	                                        NMP_OBJECT_TYPE_IP6_ROUTE,
+	                                        ifindex,
+	                                        nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel,
+	                                        NULL);
 }
 
 /*****************************************************************************/
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 9c72371c..d3a10bd6 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -1916,6 +1916,7 @@ _test_netns_check_skip (void)
 	static int support = -1;
 	static int support_errsv = 0;
 	NMPNetns *netns;
+	gs_unref_object NMPNetns *netns2 = NULL;
 
 	netns = nmp_netns_get_current ();
 	if (!netns) {
@@ -1931,10 +1932,20 @@ _test_netns_check_skip (void)
 			support_errsv = errno;
 	}
 	if (!support) {
-			_LOGD ("setns() failed with \"%s\". This indicates missing support (valgrind?)", g_strerror (support_errsv));
-			g_test_skip ("No netns support (setns failed)");
+		_LOGD ("setns() failed with \"%s\". This indicates missing support (valgrind?)", g_strerror (support_errsv));
+		g_test_skip ("No netns support (setns failed)");
 		return TRUE;
 	}
+
+	netns2 = nmp_netns_new ();
+	if (!netns2) {
+		/* skip tests for https://bugzilla.gnome.org/show_bug.cgi?id=790214 */
+		g_assert_cmpint (errno, ==, EINVAL);
+		g_test_skip ("No netns support to create another netns");
+		return TRUE;
+	}
+	nmp_netns_pop (netns2);
+
 	return FALSE;
 }
 
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index a02388d2..3228de83 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -15,12 +15,13 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2015 Red Hat, Inc.
+ * Copyright (C) 2015 - 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
 
 #include <libudev.h>
+#include <linux/pkt_sched.h>
 
 #include "platform/nmp-object.h"
 #include "nm-utils/nm-udev-utils.h"
@@ -454,6 +455,93 @@ test_cache_link (void)
 	nmp_cache_free (cache);
 }
 
+const char noqueue[] = "noqueue";
+const char fq_codel[] = "fq_codel";
+const char ingress[] = "ingress";
+
+static const NMPlatformQdisc pl_qdisc_1a = {
+	.ifindex = 1,
+	.kind = noqueue,
+	.addr_family = AF_UNSPEC,
+	.handle = 0,
+	.parent = TC_H_ROOT,
+	.info = 0,
+};
+
+static const NMPlatformQdisc pl_qdisc_1b = {
+	.ifindex = 1,
+	.kind = fq_codel,
+	.addr_family = AF_UNSPEC,
+	.handle = 0,
+	.parent = TC_H_ROOT,
+	.info = 0,
+};
+
+static const NMPlatformQdisc pl_qdisc_1c = {
+	.ifindex = 1,
+	.kind = ingress,
+	.addr_family = AF_UNSPEC,
+	.handle = TC_H_MAKE(TC_H_INGRESS, 0),
+	.parent = TC_H_INGRESS,
+	.info = 0,
+};
+
+static const NMPlatformQdisc pl_qdisc_2 = {
+	.ifindex = 2,
+	.kind = fq_codel,
+	.addr_family = AF_UNSPEC,
+	.handle = 0,
+	.parent = TC_H_ROOT,
+	.info = 0,
+};
+
+static void
+test_cache_qdisc (void)
+{
+	NMPCache *cache;
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = NULL;
+	NMPLookup lookup;
+	const NMDedupMultiHeadEntry *head_entry;
+	nm_auto_nmpobj NMPObject *obj1a = nmp_object_new (NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_1a);
+	nm_auto_nmpobj NMPObject *obj1b = nmp_object_new (NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_1b);
+	nm_auto_nmpobj NMPObject *obj1c = nmp_object_new (NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_1c);
+	nm_auto_nmpobj NMPObject *obj2 = nmp_object_new (NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_2);
+
+	multi_idx = nm_dedup_multi_index_new ();
+	cache = nmp_cache_new (multi_idx, nmtst_get_rand_int () % 2);
+
+	g_assert (nmp_cache_lookup_obj (cache, obj1a) == NULL);
+
+	g_assert (nmp_cache_update_netlink (cache, obj1a, FALSE, NULL, NULL) == NMP_CACHE_OPS_ADDED);
+	g_assert (nmp_cache_lookup_obj (cache, obj1a) == obj1a);
+	g_assert (nmp_cache_lookup_obj (cache, obj1b) == obj1a);
+	g_assert (nmp_cache_lookup_obj (cache, obj2) == NULL);
+
+	g_assert (nmp_cache_update_netlink (cache, obj1b, FALSE, NULL, NULL) == NMP_CACHE_OPS_UPDATED);
+	g_assert (nmp_cache_lookup_obj (cache, obj1a) == obj1b);
+	g_assert (nmp_cache_lookup_obj (cache, obj1b) == obj1b);
+	g_assert (nmp_cache_lookup_obj (cache, obj2) == NULL);
+
+	g_assert (nmp_cache_update_netlink (cache, obj1c, FALSE, NULL, NULL) == NMP_CACHE_OPS_ADDED);
+	g_assert (nmp_cache_lookup_obj (cache, obj1a) == obj1b);
+	g_assert (nmp_cache_lookup_obj (cache, obj1b) == obj1b);
+	g_assert (nmp_cache_lookup_obj (cache, obj1c) == obj1c);
+	g_assert (nmp_cache_lookup_obj (cache, obj2) == NULL);
+
+	g_assert (nmp_cache_update_netlink (cache, obj2, FALSE, NULL, NULL) == NMP_CACHE_OPS_ADDED);
+	g_assert (nmp_cache_lookup_obj (cache, obj1a) == obj1b);
+	g_assert (nmp_cache_lookup_obj (cache, obj1b) == obj1b);
+	g_assert (nmp_cache_lookup_obj (cache, obj2) == obj2);
+
+	head_entry = nmp_cache_lookup (cache,
+	                               nmp_lookup_init_object (&lookup,
+	                                                       NMP_OBJECT_TYPE_QDISC,
+	                                                       1));
+	g_assert (head_entry->len == 2);
+
+	nmp_cache_free (cache);
+}
+
 /*****************************************************************************/
 
 NMTST_DEFINE ();
@@ -500,6 +588,7 @@ main (int argc, char **argv)
 
 	g_test_add_func ("/nmp-object/obj-base", test_obj_base);
 	g_test_add_func ("/nmp-object/cache_link", test_cache_link);
+	g_test_add_func ("/nmp-object/cache_qdisc", test_cache_qdisc);
 
 	result = g_test_run ();
 
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 2c00fada..fe97db07 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -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 2016 Red Hat, Inc.
+ * Copyright 2016 - 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -31,6 +31,42 @@
 #define EX             NMTSTP_ENV1_EX
 
 static void
+_wait_for_ipv4_addr_device_route (NMPlatform *platform,
+                                  gint64 timeout_ms,
+                                  int ifindex,
+                                  in_addr_t addr,
+                                  guint8 plen)
+{
+	/* Wait that the addresses gets a device-route. After adding a address,
+	 * the device route is not added immediately. It takes a moment... */
+
+	addr = nm_utils_ip4_address_clear_host_address (addr, plen);
+	NMTST_WAIT_ASSERT (400, {
+		NMDedupMultiIter iter;
+		NMPLookup lookup;
+		const NMPObject *o;
+
+		nmp_cache_iter_for_each (&iter,
+		                         nm_platform_lookup (platform,
+		                                             nmp_lookup_init_object (&lookup,
+		                                                                     NMP_OBJECT_TYPE_IP4_ROUTE,
+		                                                                     ifindex)),
+		                         &o) {
+			const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (o);
+
+			if (   r->plen == plen
+			    && addr == nm_utils_ip4_address_clear_host_address (r->network, plen)
+			    && r->metric == 0
+			    && r->scope_inv == nm_platform_route_scope_inv (RT_SCOPE_LINK)
+			    && r->rt_source == NM_IP_CONFIG_SOURCE_RTPROT_KERNEL)
+				return;
+		}
+		nmtstp_assert_wait_for_signal (platform,
+		                               (nmtst_wait_end_us - g_get_monotonic_time ()) / 1000);
+	});
+}
+
+static void
 _wait_for_ipv6_addr_non_tentative (NMPlatform *platform,
                                    gint64 timeout_ms,
                                    int ifindex,
@@ -44,7 +80,7 @@ _wait_for_ipv6_addr_non_tentative (NMPlatform *platform,
 	 * small amount of time, which prevents the immediate addition of the route
 	 * with RTA_PREFSRC */
 
-	NMTST_WAIT_ASSERT (400, {
+	NMTST_WAIT_ASSERT (timeout_ms, {
 		gboolean should_wait = FALSE;
 		const NMPlatformIP6Address *plt_addr;
 
@@ -63,7 +99,6 @@ _wait_for_ipv6_addr_non_tentative (NMPlatform *platform,
 	});
 }
 
-
 static void
 ip4_route_callback (NMPlatform *platform, int obj_type_i, int ifindex, const NMPlatformIP4Route *received, int change_type_i, SignalData *data)
 {
@@ -417,7 +452,7 @@ test_ip_route_get (void)
 	g_assert (!NMP_OBJECT_IS_STACKINIT (route));
 	g_assert (route->parent._ref_count == 1);
 	r = NMP_OBJECT_CAST_IP4_ROUTE (route);
-	g_assert (r->rt_cloned);
+	g_assert (NM_FLAGS_HAS (r->r_rtm_flags, RTM_F_CLONED));
 	g_assert (r->ifindex == ifindex);
 	g_assert (r->network == a);
 	g_assert (r->plen == 32);
@@ -448,63 +483,114 @@ test_ip4_zero_gateway (void)
 }
 
 static void
-test_ip4_route_options (void)
+test_ip4_route_options (gconstpointer test_data)
 {
-	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
-	NMPlatformIP4Route route = { };
-	in_addr_t network;
-	GPtrArray *routes;
-	NMPlatformIP4Route rts[1];
+	const int TEST_IDX = GPOINTER_TO_INT (test_data);
+	const int IFINDEX = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	gs_unref_ptrarray GPtrArray *routes = NULL;
+#define RTS_MAX 3
+	NMPlatformIP4Route rts_add[RTS_MAX] = { };
+	NMPlatformIP4Route rts_cmp[RTS_MAX] = { };
+	NMPlatformIP4Address addr[1] = { };
+	guint i;
+	guint rts_n = 0;
+	guint addr_n = 0;
 
-	inet_pton (AF_INET, "172.16.1.0", &network);
+	switch (TEST_IDX) {
+	case 1:
+		rts_add[rts_n++] = ((NMPlatformIP4Route) {
+			.ifindex = IFINDEX,
+			.rt_source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("172.16.1.0"),
+			.plen = 24,
+			.metric = 20,
+			.tos = 0x28,
+			.window = 10000,
+			.cwnd = 16,
+			.initcwnd = 30,
+			.initrwnd = 50,
+			.mtu = 1350,
+			.lock_cwnd = TRUE,
+		});
+		break;
+	case 2:
+		addr[addr_n++] = ((NMPlatformIP4Address) {
+			.ifindex = IFINDEX,
+			.address = nmtst_inet4_from_string ("172.16.1.5"),
+			.peer_address = nmtst_inet4_from_string ("172.16.1.5"),
+			.plen = 24,
+			.lifetime = NM_PLATFORM_LIFETIME_PERMANENT,
+			.preferred = NM_PLATFORM_LIFETIME_PERMANENT,
+			.n_ifa_flags = 0,
+		});
+		rts_add[rts_n++] = ((NMPlatformIP4Route) {
+			.ifindex = IFINDEX,
+			.rt_source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("172.17.1.0"),
+			.gateway = nmtst_inet4_from_string ("172.16.1.1"),
+			.plen = 24,
+			.metric = 20,
+		});
+		rts_add[rts_n++] = ((NMPlatformIP4Route) {
+			.ifindex = IFINDEX,
+			.rt_source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("172.19.1.0"),
+			.gateway = nmtst_inet4_from_string ("172.18.1.1"),
+			.r_rtm_flags = RTNH_F_ONLINK,
+			.plen = 24,
+			.metric = 20,
+		});
+		break;
+	default:
+		g_assert_not_reached ();
+		break;
+	}
+	g_assert (rts_n <= G_N_ELEMENTS (rts_add));
+	g_assert (addr_n <= G_N_ELEMENTS (addr));
 
-	route.ifindex = ifindex;
-	route.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	route.network = network;
-	route.plen = 24;
-	route.metric = 20;
-	route.tos = 0x28;
-	route.window = 10000;
-	route.cwnd = 16;
-	route.initcwnd = 30;
-	route.initrwnd = 50;
-	route.mtu = 1350;
-	route.lock_cwnd = TRUE;
+	for (i = 0; i < addr_n; i++) {
+		const NMPlatformIP4Address *a = &addr[i];
+
+		g_assert (a->ifindex == IFINDEX);
+		g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET,
+		                                       a->ifindex,
+		                                       a->address,
+		                                       a->plen,
+		                                       a->peer_address,
+		                                       a->lifetime,
+		                                       a->preferred,
+		                                       a->n_ifa_flags,
+		                                       a->label));
+		if (a->peer_address == a->address)
+			_wait_for_ipv4_addr_device_route (NM_PLATFORM_GET, 200, a->ifindex, a->address, a->plen);
+	}
 
-	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &route) == NM_PLATFORM_ERROR_SUCCESS);
+	for (i = 0; i < rts_n; i++)
+		g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i]) == NM_PLATFORM_ERROR_SUCCESS);
 
-	/* Test route listing */
-	routes = nmtstp_ip4_route_get_all (NM_PLATFORM_GET, ifindex);
-	memset (rts, 0, sizeof (rts));
-	rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
-	rts[0].scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK);
-	rts[0].network = network;
-	rts[0].plen = 24;
-	rts[0].ifindex = ifindex;
-	rts[0].metric = 20;
-	rts[0].tos = 0x28;
-	rts[0].window = 10000;
-	rts[0].cwnd = 16;
-	rts[0].initcwnd = 30;
-	rts[0].initrwnd = 50;
-	rts[0].mtu = 1350;
-	rts[0].lock_cwnd = TRUE;
-	g_assert_cmpint (routes->len, ==, 1);
-	nmtst_platform_ip4_routes_equal_aptr ((const NMPObject *const*) routes->pdata, rts, routes->len, TRUE);
+	for (i = 0; i < rts_n; i++) {
+		rts_cmp[i] = rts_add[i];
+		nm_platform_ip_route_normalize (AF_INET, NM_PLATFORM_IP_ROUTE_CAST (&rts_cmp[i]));
+	}
 
-	/* Remove route */
-	g_assert (nm_platform_ip_route_delete (NM_PLATFORM_GET, routes->pdata[0]));
+	routes = nmtstp_ip4_route_get_all (NM_PLATFORM_GET, IFINDEX);
+	g_assert_cmpint (routes->len, ==, rts_n);
+	nmtst_platform_ip4_routes_equal_aptr ((const NMPObject *const*) routes->pdata, rts_cmp, routes->len, TRUE);
 
-	g_ptr_array_unref (routes);
+	for (i = 0; i < rts_n; i++) {
+		g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, IFINDEX,
+		                                            rts_add[i].network, rts_add[i].plen,
+		                                            rts_add[i].metric));
+	}
+#undef RTS_MAX
 }
 
-
 static void
 test_ip6_route_options (gconstpointer test_data)
 {
 	const int TEST_IDX = GPOINTER_TO_INT (test_data);
 	const int IFINDEX = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
-	GPtrArray *routes;
+	gs_unref_ptrarray GPtrArray *routes = NULL;
 #define RTS_MAX 3
 	NMPlatformIP6Route rts_add[RTS_MAX] = { };
 	NMPlatformIP6Route rts_cmp[RTS_MAX] = { };
@@ -582,6 +668,8 @@ test_ip6_route_options (gconstpointer test_data)
 	default:
 		g_assert_not_reached ();
 	}
+	g_assert (rts_n <= G_N_ELEMENTS (rts_add));
+	g_assert (addr_n <= G_N_ELEMENTS (addr));
 
 	for (i = 0; i < addr_n; i++) {
 		g_assert (addr[i].ifindex == IFINDEX);
@@ -595,28 +683,19 @@ test_ip6_route_options (gconstpointer test_data)
 		                                       addr[i].preferred,
 		                                       addr[i].n_ifa_flags));
 	}
-
 	_wait_for_ipv6_addr_non_tentative (NM_PLATFORM_GET, 400, IFINDEX, addr_n, addr_in6);
 
 	for (i = 0; i < rts_n; i++)
 		g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i]) == NM_PLATFORM_ERROR_SUCCESS);
 
-	routes = nmtstp_ip6_route_get_all (NM_PLATFORM_GET, IFINDEX);
-	switch (TEST_IDX) {
-	case 1:
-	case 2:
-	case 3:
-		for (i = 0; i < rts_n; i++) {
-			rts_cmp[i] = rts_add[i];
-			rts_cmp[i].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
-		}
-		break;
-	default:
-		g_assert_not_reached ();
+	for (i = 0; i < rts_n; i++) {
+		rts_cmp[i] = rts_add[i];
+		nm_platform_ip_route_normalize (AF_INET6, NM_PLATFORM_IP_ROUTE_CAST (&rts_cmp[i]));
 	}
+
+	routes = nmtstp_ip6_route_get_all (NM_PLATFORM_GET, IFINDEX);
 	g_assert_cmpint (routes->len, ==, rts_n);
 	nmtst_platform_ip6_routes_equal_aptr ((const NMPObject *const*) routes->pdata, rts_cmp, routes->len, TRUE);
-	g_ptr_array_unref (routes);
 
 	for (i = 0; i < rts_n; i++) {
 		g_assert (nmtstp_platform_ip6_route_delete (NM_PLATFORM_GET, IFINDEX,
@@ -631,6 +710,7 @@ test_ip6_route_options (gconstpointer test_data)
 		                        rts_add[i].network,
 		                        rts_add[i].plen);
 	}
+#undef RTS_MAX
 }
 
 /*****************************************************************************/
@@ -714,7 +794,7 @@ again_find_idx:
 			order_len--;
 
 			r->ifindex = iface_data[idx].ifindex;
-			g_assert (nm_platform_ip_route_delete (platform, &o));
+			g_assert (nm_platform_object_delete (platform, &o));
 		}
 
 		head_entry = nm_platform_lookup (platform,
@@ -771,7 +851,9 @@ _nmtstp_setup_tests (void)
 	add_test_func ("/route/ip4", test_ip4_route);
 	add_test_func ("/route/ip6", test_ip6_route);
 	add_test_func ("/route/ip4_metric0", test_ip4_route_metric0);
-	add_test_func ("/route/ip4_options", test_ip4_route_options);
+	add_test_func_data ("/route/ip4_options/1", test_ip4_route_options, GINT_TO_POINTER (1));
+	if (nmtstp_is_root_test ())
+		add_test_func_data ("/route/ip4_options/2", test_ip4_route_options, GINT_TO_POINTER (2));
 	add_test_func_data ("/route/ip6_options/1", test_ip6_route_options, GINT_TO_POINTER (1));
 	add_test_func_data ("/route/ip6_options/2", test_ip6_route_options, GINT_TO_POINTER (2));
 	add_test_func_data ("/route/ip6_options/3", test_ip6_route_options, GINT_TO_POINTER (3));