summary refs log tree commit diff
path: root/src/platform/nm-linux-platform.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/nm-linux-platform.c')
-rw-r--r--src/platform/nm-linux-platform.c460
1 files changed, 403 insertions, 57 deletions
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;