summary refs log tree commit diff
path: root/src/platform
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2015-11-24 00:06:32 +0100
committerMichael Biebl <biebl@debian.org>2015-11-24 00:06:32 +0100
commita6ece1a2aa19a6268335c87d4fdef20123dd04a5 (patch)
tree87f1961faacdfafb1c4fee5f2feb6bcb5c06813b /src/platform
parent81836c2d44802b4cca833d7775dd627e0797a7e2 (diff)
Imported Upstream version 1.0.8 upstream/1.0.8
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/Makefile.in1
-rw-r--r--src/platform/nm-linux-platform.c199
-rw-r--r--src/platform/nm-platform-utils.c29
-rw-r--r--src/platform/nm-platform.c6
-rw-r--r--src/platform/nmp-object.c6
-rw-r--r--src/platform/tests/Makefile.am1
-rw-r--r--src/platform/tests/Makefile.in8
-rw-r--r--src/platform/tests/test-link.c47
8 files changed, 214 insertions, 83 deletions
diff --git a/src/platform/Makefile.in b/src/platform/Makefile.in
index 0616c88b..e8b73294 100644
--- a/src/platform/Makefile.in
+++ b/src/platform/Makefile.in
@@ -313,6 +313,7 @@ LOG_DRIVER = @LOG_DRIVER@
 LTLIBICONV = @LTLIBICONV@
 LTLIBINTL = @LTLIBINTL@
 LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
 MAINT = @MAINT@
 MAKEINFO = @MAKEINFO@
 MANIFEST_TOOL = @MANIFEST_TOOL@
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index cd0a4e07..2dd0b512 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -169,6 +169,9 @@ _nl_get_vtable (void)
 			vtable.f_nl_has_capability = &_nl_f_nl_has_capability;
 
 		trace ("libnl: rtnl_link_get_link_netnsid() %s", vtable.f_rtnl_link_get_link_netnsid ? "supported" : "not supported");
+
+		g_return_val_if_fail (vtable.handle, &vtable);
+		g_return_val_if_fail (vtable.handle_route, &vtable);
 	}
 
 	return &vtable;
@@ -463,7 +466,7 @@ _nl_sock_request_link (NMPlatform *platform, struct nl_sock *sk, int ifindex, co
 
 	g_return_val_if_fail (ifindex > 0 || name, -NLE_INVAL);
 
-	_LOGT ("sock: request-link %d%s%s%s", ifindex, name ? ", \"" : "", name ? name : "", name ? "\"" : "");
+	_LOGt ("sock: request-link %d%s%s%s", ifindex, name ? ", \"" : "", name ? name : "", name ? "\"" : "");
 
 	if ((err = rtnl_link_build_get_request (ifindex, name, &msg)) < 0)
 		return err;
@@ -488,7 +491,7 @@ _nl_sock_request_all (NMPlatform *platform, struct nl_sock *sk, NMPObjectType ob
 
 	klass = nmp_class_from_type (obj_type);
 
-	_LOGT ("sock: request-all-%s", klass->obj_type_name);
+	_LOGt ("sock: request-all-%s", klass->obj_type_name);
 
 	/* reimplement
 	 *   nl_rtgen_request (sk, klass->rtm_gettype, klass->addr_family, NLM_F_DUMP);
@@ -898,7 +901,7 @@ link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *c
 		obj = _lookup_link_cached (platform, rtnl_link_get_ifindex (rtnllink), completed_from_cache, link_cached);
 		if (obj && obj->link.kind) {
 			rtnl_type = obj->link.kind;
-			_LOGT ("link_extract_type(): complete kind from cache: ifindex=%d, kind=%s", rtnl_link_get_ifindex (rtnllink), rtnl_type);
+			_LOGt ("link_extract_type(): complete kind from cache: ifindex=%d, kind=%s", rtnl_link_get_ifindex (rtnllink), rtnl_type);
 		}
 	}
 	if (out_kind)
@@ -944,18 +947,18 @@ link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *c
 		gs_free char *anycast_mask = NULL;
 		gs_free char *devtype = NULL;
 
-		if (arptype == 256) {
-			/* Some s390 CTC-type devices report 256 for the encapsulation type
-			 * for some reason, but we need to call them Ethernet.
-			 */
-			if (!g_strcmp0 (driver, "ctcm"))
-				return NM_LINK_TYPE_ETHERNET;
-		}
-
 		/* Fallback OVS detection for kernel <= 3.16 */
 		if (nmp_utils_ethtool_get_driver_info (ifname, &driver, NULL, NULL)) {
 			if (!g_strcmp0 (driver, "openvswitch"))
 				return NM_LINK_TYPE_OPENVSWITCH;
+
+			if (arptype == 256) {
+				/* Some s390 CTC-type devices report 256 for the encapsulation type
+				 * for some reason, but we need to call them Ethernet.
+				 */
+				if (!g_strcmp0 (driver, "ctcm"))
+					return NM_LINK_TYPE_ETHERNET;
+			}
 		}
 
 		sysfs_path = g_strdup_printf ("/sys/class/net/%s", ifname);
@@ -1009,8 +1012,8 @@ _nmp_vt_cmd_plobj_init_from_nl_link (NMPlatform *platform, NMPlatformObject *_ob
 
 	nm_assert (memcmp (obj, ((char [sizeof (NMPObjectLink)]) { 0 }), sizeof (NMPObjectLink)) == 0);
 
-	if (_LOGT_ENABLED () && !NM_IN_SET (rtnl_link_get_family (nlo), AF_UNSPEC, AF_BRIDGE))
-		_LOGT ("netlink object for ifindex %d has unusual family %d", rtnl_link_get_ifindex (nlo), rtnl_link_get_family (nlo));
+	if (_LOGt_ENABLED () && !NM_IN_SET (rtnl_link_get_family (nlo), AF_UNSPEC, AF_BRIDGE))
+		_LOGt ("netlink object for ifindex %d has unusual family %d", rtnl_link_get_ifindex (nlo), rtnl_link_get_family (nlo));
 
 	obj->ifindex = rtnl_link_get_ifindex (nlo);
 
@@ -1132,6 +1135,7 @@ _rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr, gint32 *out
 	guint32 last_update_time = rtnl_addr_get_last_update_time ((struct rtnl_addr *) rtnladdr);
 	struct timespec tp;
 	gint64 now_nl, now_nm, result;
+	int err;
 
 	/* timestamp is unset. Default to 1. */
 	if (!last_update_time) {
@@ -1142,7 +1146,8 @@ _rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr, gint32 *out
 
 	/* do all the calculations in milliseconds scale */
 
-	clock_gettime (CLOCK_MONOTONIC, &tp);
+	err = clock_gettime (CLOCK_MONOTONIC, &tp);
+	g_assert (err == 0);
 	now_nm = nm_utils_get_monotonic_timestamp_ms ();
 	now_nl = (((gint64) tp.tv_sec) * ((gint64) 1000)) +
 	         (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000));
@@ -1466,7 +1471,7 @@ do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cach
 
 	klass = NMP_OBJECT_GET_CLASS (obj);
 
-	_LOGT ("emit signal %s %s: %s (%ld)",
+	_LOGt ("emit signal %s %s: %s (%ld)",
 	       klass->signal_type,
 	       nm_platform_signal_change_type_to_string ((NMPlatformSignalChangeType) cache_op),
 	       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0),
@@ -1523,8 +1528,8 @@ delayed_action_to_string (DelayedActionType action_type)
 	}
 }
 
-#define _LOGT_delayed_action(action_type, arg, operation) \
-    _LOGT ("delayed-action: %s %s (%d) [%p / %d]", ""operation, delayed_action_to_string (action_type), (int) action_type, arg, GPOINTER_TO_INT (arg))
+#define _LOGt_delayed_action(action_type, arg, operation) \
+    _LOGt ("delayed-action: %s %s (%d) [%p / %d]", ""operation, delayed_action_to_string (action_type), (int) action_type, arg, GPOINTER_TO_INT (arg))
 
 static void
 delayed_action_handle_MASTER_CONNECTED (NMPlatform *platform, int master_ifindex)
@@ -1579,7 +1584,7 @@ delayed_action_handle_one (NMPlatform *platform)
 			priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_MASTER_CONNECTED;
 		nm_assert (_nm_utils_ptrarray_find_first (priv->delayed_action.list_master_connected->pdata, priv->delayed_action.list_master_connected->len, user_data) < 0);
 
-		_LOGT_delayed_action (DELAYED_ACTION_TYPE_MASTER_CONNECTED, user_data, "handle");
+		_LOGt_delayed_action (DELAYED_ACTION_TYPE_MASTER_CONNECTED, user_data, "handle");
 		delayed_action_handle_MASTER_CONNECTED (platform, GPOINTER_TO_INT (user_data));
 		return TRUE;
 	}
@@ -1588,7 +1593,7 @@ delayed_action_handle_one (NMPlatform *platform)
 	/* Next we prefer read-netlink, because the buffer size is limited and we want to process events
 	 * from netlink early. */
 	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_READ_NETLINK)) {
-		_LOGT_delayed_action (DELAYED_ACTION_TYPE_READ_NETLINK, NULL, "handle");
+		_LOGt_delayed_action (DELAYED_ACTION_TYPE_READ_NETLINK, NULL, "handle");
 		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_READ_NETLINK;
 		delayed_action_handle_READ_NETLINK (platform);
 		return TRUE;
@@ -1601,10 +1606,10 @@ delayed_action_handle_one (NMPlatform *platform)
 
 		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_ALL;
 
-		if (_LOGT_ENABLED ()) {
+		if (_LOGt_ENABLED ()) {
 			for (iflags = (DelayedActionType) 0x1LL; iflags <= DELAYED_ACTION_TYPE_MAX; iflags <<= 1) {
 				if (NM_FLAGS_HAS (flags, iflags))
-					_LOGT_delayed_action (iflags, NULL, "handle");
+					_LOGt_delayed_action (iflags, NULL, "handle");
 			}
 		}
 
@@ -1617,11 +1622,11 @@ delayed_action_handle_one (NMPlatform *platform)
 
 	user_data = priv->delayed_action.list_refresh_link->pdata[0];
 	g_ptr_array_remove_index_fast (priv->delayed_action.list_refresh_link, 0);
-	if (priv->delayed_action.list_master_connected->len == 0)
+	if (priv->delayed_action.list_refresh_link->len == 0)
 		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
 	nm_assert (_nm_utils_ptrarray_find_first (priv->delayed_action.list_refresh_link->pdata, priv->delayed_action.list_refresh_link->len, user_data) < 0);
 
-	_LOGT_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, user_data, "handle");
+	_LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, user_data, "handle");
 
 	delayed_action_handle_REFRESH_LINK (platform, GPOINTER_TO_INT (user_data));
 
@@ -1653,6 +1658,33 @@ delayed_action_handle_idle (gpointer user_data)
 }
 
 static void
+delayed_action_clear_REFRESH_LINK (NMPlatform *platform, int ifindex)
+{
+	NMLinuxPlatformPrivate *priv;
+	gssize idx;
+	gpointer user_data;
+
+	if (ifindex <= 0)
+		return;
+
+	priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	if (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK))
+		return;
+
+	user_data = GINT_TO_POINTER (ifindex);
+
+	idx = _nm_utils_ptrarray_find_first (priv->delayed_action.list_refresh_link->pdata, priv->delayed_action.list_refresh_link->len, user_data);
+	if (idx < 0)
+		return;
+
+	_LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, user_data, "clear");
+
+	g_ptr_array_remove_index_fast (priv->delayed_action.list_refresh_link, idx);
+	if (priv->delayed_action.list_refresh_link->len == 0)
+		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
+}
+
+static void
 delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gpointer user_data)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
@@ -1673,10 +1705,10 @@ delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gp
 
 	priv->delayed_action.flags |= action_type;
 
-	if (_LOGT_ENABLED ()) {
+	if (_LOGt_ENABLED ()) {
 		for (iflags = (DelayedActionType) 0x1LL; iflags <= DELAYED_ACTION_TYPE_MAX; iflags <<= 1) {
 			if (NM_FLAGS_HAS (action_type, iflags))
-				_LOGT_delayed_action (iflags, user_data, "schedule");
+				_LOGt_delayed_action (iflags, user_data, "schedule");
 		}
 	}
 
@@ -1694,7 +1726,7 @@ cache_prune_candidates_record_all (NMPlatform *platform, NMPObjectType obj_type)
 	priv->prune_candidates = nmp_cache_lookup_all_to_hash (priv->cache,
 	                                                       nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, obj_type, FALSE),
 	                                                       priv->prune_candidates);
-	_LOGT ("cache-prune: record %s (now %u candidates)", nmp_class_from_type (obj_type)->obj_type_name,
+	_LOGt ("cache-prune: record %s (now %u candidates)", nmp_class_from_type (obj_type)->obj_type_name,
 	       priv->prune_candidates ? g_hash_table_size (priv->prune_candidates) : 0);
 }
 
@@ -1711,8 +1743,8 @@ cache_prune_candidates_record_one (NMPlatform *platform, NMPObject *obj)
 	if (!priv->prune_candidates)
 		priv->prune_candidates = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) nmp_object_unref, NULL);
 
-	if (_LOGT_ENABLED () && !g_hash_table_contains (priv->prune_candidates, obj))
-		_LOGT ("cache-prune: record-one: %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+	if (_LOGt_ENABLED () && !g_hash_table_contains (priv->prune_candidates, obj))
+		_LOGt ("cache-prune: record-one: %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
 	g_hash_table_add (priv->prune_candidates, nmp_object_ref (obj));
 }
 
@@ -1726,8 +1758,8 @@ cache_prune_candidates_drop (NMPlatform *platform, const NMPObject *obj)
 
 	priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	if (priv->prune_candidates) {
-		if (_LOGT_ENABLED () && g_hash_table_contains (priv->prune_candidates, obj))
-			_LOGT ("cache-prune: drop-one: %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+		if (_LOGt_ENABLED () && g_hash_table_contains (priv->prune_candidates, obj))
+			_LOGt ("cache-prune: drop-one: %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
 		g_hash_table_remove (priv->prune_candidates, obj);
 	}
 }
@@ -1752,7 +1784,7 @@ cache_prune_candidates_prune (NMPlatform *platform)
 	while (g_hash_table_iter_next (&iter, (gpointer *)&obj, NULL)) {
 		auto_nmp_obj NMPObject *obj_cache = NULL;
 
-		_LOGT ("cache-prune: prune %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+		_LOGt ("cache-prune: prune %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
 		cache_op = nmp_cache_remove (priv->cache, obj, TRUE, &obj_cache, &was_visible, cache_pre_hook, platform);
 		do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL);
 	}
@@ -1786,7 +1818,7 @@ cache_delayed_deletion_prune (NMPlatform *platform)
 	if (prune_list) {
 		for (i = 0; i < prune_list->len; i++) {
 			obj = prune_list->pdata[i];
-			_LOGT ("delayed-deletion: delete %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+			_LOGt ("delayed-deletion: delete %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
 			cache_remove_netlink (platform, obj, NULL, NULL, NM_PLATFORM_REASON_EXTERNAL);
 		}
 		g_ptr_array_unref (prune_list);
@@ -1813,7 +1845,7 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 
 	nm_assert (klass == (new ? NMP_OBJECT_GET_CLASS (new) : NMP_OBJECT_GET_CLASS (old)));
 
-	_LOGT ("update-cache-%s: %s: %s%s%s",
+	_LOGt ("update-cache-%s: %s: %s%s%s",
 	       klass->obj_type_name,
 	       (ops_type == NMP_CACHE_OPS_UPDATED
 	           ? "UPDATE"
@@ -1869,6 +1901,37 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 			}
 		}
 		{
+			int ifindex = -1;
+
+			/* removal of a link could be caused by moving the link to another netns.
+			 * In this case, we potentially have to update other links that have this link as parent.
+			 * Currently, kernel misses to sent us a notification in this case (rh #1262908). */
+
+			if (   ops_type == NMP_CACHE_OPS_REMOVED
+			    && old /* <-- nonsensical, make coverity happy */
+			    && old->_link.netlink.is_in_netlink)
+				ifindex = old->link.ifindex;
+			else if (   ops_type == NMP_CACHE_OPS_UPDATED
+			         && old && new /* <-- nonsensical, make coverity happy */
+			         && old->_link.netlink.is_in_netlink
+			         && !new->_link.netlink.is_in_netlink)
+				ifindex = new->link.ifindex;
+
+			if (ifindex > 0) {
+				const NMPlatformLink *const *links;
+
+				links = cache_lookup_all_objects (NMPlatformLink, platform, NMP_OBJECT_TYPE_LINK, FALSE);
+				if (links) {
+					for (; *links; links++) {
+						const NMPlatformLink *l = (*links);
+
+						if (l->parent == ifindex)
+							delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (l->ifindex));
+					}
+				}
+			}
+		}
+		{
 			/* if a link goes down, we must refresh routes */
 			if (   ops_type == NMP_CACHE_OPS_UPDATED
 			    && old && new /* <-- nonsensical, make coverity happy */
@@ -1974,7 +2037,7 @@ _new_sequence_number (NMPlatform *platform, guint32 seq)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 
-	_LOGT ("_new_sequence_number(): new sequence number %u", seq);
+	_LOGt ("_new_sequence_number(): new sequence number %u", seq);
 
 	priv->nlh_seq_expect = seq;
 }
@@ -1985,7 +2048,7 @@ do_request_link (NMPlatform *platform, int ifindex, const char *name, gboolean h
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	guint32 seq;
 
-	_LOGT ("do_request_link (%d,%s)", ifindex, name ? name : "");
+	_LOGt ("do_request_link (%d,%s)", ifindex, name ? name : "");
 
 	if (ifindex > 0) {
 		NMPObject *obj;
@@ -1993,7 +2056,7 @@ do_request_link (NMPlatform *platform, int ifindex, const char *name, gboolean h
 		cache_prune_candidates_record_one (platform,
 		                                   (NMPObject *) nmp_cache_lookup_link (priv->cache, ifindex));
 		obj = nmp_object_new_link (ifindex);
-		_LOGT ("delayed-deletion: protect object %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+		_LOGt ("delayed-deletion: protect object %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
 		g_hash_table_insert (priv->delayed_deletion, obj, NULL);
 	}
 
@@ -2038,9 +2101,11 @@ do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean ha
 
 			/* clear any delayed action that request a refresh of this object type. */
 			priv->delayed_action.flags &= ~iflags;
+			_LOGt_delayed_action (iflags, NULL, "handle (do-request-all)");
 			if (obj_type == NMP_OBJECT_TYPE_LINK) {
 				priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
 				g_ptr_array_set_size (priv->delayed_action.list_refresh_link, 0);
+				_LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, NULL, "clear (do-request-all)");
 			}
 
 			event_handler_read_netlink_all (platform, FALSE);
@@ -2081,7 +2146,7 @@ kernel_add_object (NMPlatform *platform, NMPObjectType obj_type, const struct nl
 		g_return_val_if_reached (-NLE_INVAL);
 	}
 
-	_LOGT ("kernel-add-%s: returned %s (%d)",
+	_LOGt ("kernel-add-%s: returned %s (%d)",
 	       nmp_class_from_type (obj_type)->obj_type_name, nl_geterror (nle), -nle);
 
 	switch (nle) {
@@ -2125,20 +2190,20 @@ kernel_delete_object (NMPlatform *platform, NMPObjectType object_type, const str
 	case -NLE_SUCCESS:
 		return NLE_SUCCESS;
 	case -NLE_OBJ_NOTFOUND:
-		_LOGT ("kernel-delete-%s: failed with \"%s\" (%d), meaning the object was already removed",
+		_LOGt ("kernel-delete-%s: failed with \"%s\" (%d), meaning the object was already removed",
 		       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
 		return -NLE_SUCCESS;
 	case -NLE_FAILURE:
 		if (object_type == NMP_OBJECT_TYPE_IP6_ADDRESS) {
 			/* On RHEL7 kernel, deleting a non existing address fails with ENXIO (which libnl maps to NLE_FAILURE) */
-			_LOGT ("kernel-delete-%s: deleting address failed with \"%s\" (%d), meaning the address was already removed",
+			_LOGt ("kernel-delete-%s: deleting address failed with \"%s\" (%d), meaning the address was already removed",
 			       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
 			return NLE_SUCCESS;
 		}
 		break;
 	case -NLE_NOADDR:
 		if (object_type == NMP_OBJECT_TYPE_IP4_ADDRESS || object_type == NMP_OBJECT_TYPE_IP6_ADDRESS) {
-			_LOGT ("kernel-delete-%s: deleting address failed with \"%s\" (%d), meaning the address was already removed",
+			_LOGt ("kernel-delete-%s: deleting address failed with \"%s\" (%d), meaning the address was already removed",
 			       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
 			return -NLE_SUCCESS;
 		}
@@ -2146,7 +2211,7 @@ kernel_delete_object (NMPlatform *platform, NMPObjectType object_type, const str
 	default:
 		break;
 	}
-	_LOGT ("kernel-delete-%s: failed with %s (%d)",
+	_LOGt ("kernel-delete-%s: failed with %s (%d)",
 	       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
 	return nle;
 }
@@ -2183,7 +2248,7 @@ kernel_change_link (NMPlatform *platform, struct rtnl_link *nlo, gboolean *compl
 
 		obj_cache = nmp_cache_lookup_link (priv->cache, ifindex);
 		if (!obj_cache || !obj_cache->_link.netlink.is_in_netlink) {
-			_LOGT ("kernel-change-link: failure changing link %d: cannot complete link", ifindex);
+			_LOGt ("kernel-change-link: failure changing link %d: cannot complete link", ifindex);
 			*complete_from_cache = FALSE;
 			return -NLE_INVAL;
 		}
@@ -2204,7 +2269,7 @@ kernel_change_link (NMPlatform *platform, struct rtnl_link *nlo, gboolean *compl
 
 	nle = rtnl_link_build_add_request (nlo, nlflags, &msg);
 	if (nle < 0) {
-		_LOGT ("kernel-change-link: failure changing link %d: cannot construct message (%s, %d)",
+		_LOGt ("kernel-change-link: failure changing link %d: cannot construct message (%s, %d)",
 		       ifindex, nl_geterror (nle), -nle);
 		return nle;
 	}
@@ -2234,18 +2299,18 @@ errout:
 	 */
 	switch (nle) {
 	case -NLE_SUCCESS:
-		_LOGT ("kernel-change-link: success changing link %d", ifindex);
+		_LOGt ("kernel-change-link: success changing link %d", ifindex);
 		break;
 	case -NLE_EXIST:
-		_LOGT ("kernel-change-link: success changing link %d: %s (%d)",
+		_LOGt ("kernel-change-link: success changing link %d: %s (%d)",
 		       ifindex, nl_geterror (nle), -nle);
 		break;
 	case -NLE_OBJ_NOTFOUND:
-		_LOGT ("kernel-change-link: failure changing link %d: firmware not found (%s, %d)",
+		_LOGt ("kernel-change-link: failure changing link %d: firmware not found (%s, %d)",
 		       ifindex, nl_geterror (nle), -nle);
 		break;
 	default:
-		_LOGT ("kernel-change-link: failure changing link %d: netlink error (%s, %d)",
+		_LOGt ("kernel-change-link: failure changing link %d: netlink error (%s, %d)",
 		       ifindex, nl_geterror (nle), -nle);
 		break;
 	}
@@ -2277,13 +2342,13 @@ event_seq_check (struct nl_msg *msg, gpointer user_data)
 	priv->nlh_seq_last = hdr->nlmsg_seq;
 
 	if (priv->nlh_seq_expect == 0)
-		_LOGT ("event_seq_check(): seq %u received (not waited)", hdr->nlmsg_seq);
+		_LOGt ("event_seq_check(): seq %u received (not waited)", hdr->nlmsg_seq);
 	else if (hdr->nlmsg_seq == priv->nlh_seq_expect) {
-		_LOGT ("event_seq_check(): seq %u received", hdr->nlmsg_seq);
+		_LOGt ("event_seq_check(): seq %u received", hdr->nlmsg_seq);
 
 		priv->nlh_seq_expect = 0;
 	} else
-		_LOGT ("event_seq_check(): seq %u received (wait for %u)", hdr->nlmsg_seq, priv->nlh_seq_last);
+		_LOGt ("event_seq_check(): seq %u received (wait for %u)", hdr->nlmsg_seq, priv->nlh_seq_last);
 
 	return NL_OK;
 }
@@ -2291,7 +2356,7 @@ event_seq_check (struct nl_msg *msg, gpointer user_data)
 static int
 event_err (struct sockaddr_nl *nla, struct nlmsgerr *nlerr, gpointer platform)
 {
-	_LOGT ("event_err(): error from kernel: %s (%d) for request %d",
+	_LOGt ("event_err(): error from kernel: %s (%d) for request %d",
 	       strerror (nlerr ? -nlerr->error : 0),
 	       nlerr ? -nlerr->error : 0,
 	       NM_LINUX_PLATFORM_GET_PRIVATE (platform)->nlh_seq_last);
@@ -2333,13 +2398,13 @@ event_notification (struct nl_msg *msg, gpointer user_data)
 		/* The event notifies about a deleted object. We don't need to initialize all the
 		 * fields of the nmp-object. Shortcut nmp_object_from_nl(). */
 		obj = nmp_object_from_nl (platform, nlo, TRUE, TRUE);
-		_LOGD ("event-notification: %s, seq %u: %s",
+		_LOGT ("event-notification: %s, seq %u: %s",
 		       _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)),
 		       msghdr->nlmsg_seq, nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
 		break;
 	default:
 		obj = nmp_object_from_nl (platform, nlo, FALSE, TRUE);
-		_LOGD ("event-notification: %s, seq %u: %s",
+		_LOGT ("event-notification: %s, seq %u: %s",
 		       _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)),
 		       msghdr->nlmsg_seq, nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
 		break;
@@ -2351,12 +2416,14 @@ event_notification (struct nl_msg *msg, gpointer user_data)
 		switch (msghdr->nlmsg_type) {
 
 		case RTM_NEWLINK:
-			if (   NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK
-			    && g_hash_table_lookup (priv->delayed_deletion, obj) != NULL) {
-				/* the object is scheduled for delayed deletion. Replace that object
-				 * by clearing the value from priv->delayed_deletion. */
-				_LOGT ("delayed-deletion: clear delayed deletion of protected object %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
-				g_hash_table_insert (priv->delayed_deletion, nmp_object_ref (obj), NULL);
+			if (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK) {
+				if (g_hash_table_lookup (priv->delayed_deletion, obj) != NULL) {
+					/* the object is scheduled for delayed deletion. Replace that object
+					 * by clearing the value from priv->delayed_deletion. */
+					_LOGt ("delayed-deletion: clear delayed deletion of protected object %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+					g_hash_table_insert (priv->delayed_deletion, nmp_object_ref (obj), NULL);
+				}
+				delayed_action_clear_REFRESH_LINK (platform, obj->link.ifindex);
 			}
 			/* fall-through */
 		case RTM_NEWADDR:
@@ -2369,7 +2436,7 @@ event_notification (struct nl_msg *msg, gpointer user_data)
 			    && g_hash_table_contains (priv->delayed_deletion, obj)) {
 				/* We sometimes receive spurious RTM_DELLINK events. In this case, we want to delay
 				 * the deletion of the object until later. */
-				_LOGT ("delayed-deletion: delay deletion of protected object %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+				_LOGt ("delayed-deletion: delay deletion of protected object %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
 				g_hash_table_insert (priv->delayed_deletion, nmp_object_ref (obj), nmp_object_ref (obj));
 				break;
 			}
@@ -2681,7 +2748,7 @@ do_add_link (NMPlatform *platform, const char *name, const struct rtnl_link *nlo
 	 * link so that it is in the cache. A better solution would be to do everything
 	 * via one netlink socket. */
 	if (!nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, 0, obj_needle.link.name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL)) {
-		_LOGT ("do-add-link: reload: the added link is not yet ready. Request %s", obj_needle.link.name);
+		_LOGt ("do-add-link: reload: the added link is not yet ready. Request %s", obj_needle.link.name);
 		do_request_link (platform, 0, obj_needle.link.name, TRUE);
 	}
 
@@ -2733,7 +2800,7 @@ do_add_addrroute (NMPlatform *platform, const NMPObject *obj_id, const struct nl
 	/* FIXME: instead of re-requesting the added object, add it via nlh_event
 	 * so that the events are in sync. */
 	if (!nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj_id)) {
-		_LOGT ("do-add-%s: reload: the added object is not yet ready. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+		_LOGt ("do-add-%s: reload: the added object is not yet ready. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
 		do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id), TRUE);
 	}
 
@@ -2771,12 +2838,12 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, const struct nl
 
 		obj = nmp_cache_lookup_link_full (priv->cache, obj_id->link.ifindex, obj_id->link.ifindex <= 0 && obj_id->link.name[0] ? obj_id->link.name : NULL, FALSE, NM_LINK_TYPE_NONE, NULL, NULL);
 		if (obj && obj->_link.netlink.is_in_netlink) {
-			_LOGT ("do-delete-%s: reload: the deleted object is not yet removed. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+			_LOGt ("do-delete-%s: reload: the deleted object is not yet removed. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
 			do_request_link (platform, obj_id->link.ifindex, obj_id->link.name, TRUE);
 		}
 	} else {
 		if (nmp_cache_lookup_obj (priv->cache, obj_id)) {
-			_LOGT ("do-delete-%s: reload: the deleted object is not yet removed. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+			_LOGt ("do-delete-%s: reload: the deleted object is not yet removed. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
 			do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id), TRUE);
 		}
 	}
@@ -4621,7 +4688,7 @@ event_handler_read_netlink_all (NMPlatform *platform, gboolean wait_for_acks)
 
 		if (!wait_for_acks || priv->nlh_seq_expect == 0) {
 			if (wait_for_seq)
-				_LOGT ("read-netlink-all: ACK for sequence number %u received", priv->nlh_seq_expect);
+				_LOGt ("read-netlink-all: ACK for sequence number %u received", priv->nlh_seq_expect);
 			return any;
 		}
 
@@ -4629,7 +4696,7 @@ event_handler_read_netlink_all (NMPlatform *platform, gboolean wait_for_acks)
 		if (wait_for_seq != priv->nlh_seq_expect) {
 			/* We are waiting for a new sequence number (or we will wait for the first time).
 			 * Reset/start counting the overall wait time. */
-			_LOGT ("read-netlink-all: wait for ACK for sequence number %u...", priv->nlh_seq_expect);
+			_LOGt ("read-netlink-all: wait for ACK for sequence number %u...", priv->nlh_seq_expect);
 			wait_for_seq = priv->nlh_seq_expect;
 			timestamp = now;
 			timeout = TIMEOUT;
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index b7f0947e..e9b8d4be 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -139,21 +139,31 @@ nmp_utils_ethtool_get_permanent_address (const char *ifname,
                                          guint8 *buf,
                                          size_t *length)
 {
-	gs_free struct ethtool_perm_addr *epaddr = NULL;
+	struct {
+		struct ethtool_perm_addr e;
+		guint8 _extra_data[NM_UTILS_HWADDR_LEN_MAX + 1];
+	} edata;
+	guint zeros[NM_UTILS_HWADDR_LEN_MAX] = { 0 };
 
 	if (!ifname)
 		return FALSE;
 
-	epaddr = g_malloc0 (sizeof (*epaddr) + NM_UTILS_HWADDR_LEN_MAX);
-	epaddr->cmd = ETHTOOL_GPERMADDR;
-	epaddr->size = NM_UTILS_HWADDR_LEN_MAX;
+	memset (&edata, 0, sizeof (edata));
+	edata.e.cmd = ETHTOOL_GPERMADDR;
+	edata.e.size = NM_UTILS_HWADDR_LEN_MAX;
 
-	if (!ethtool_get (ifname, epaddr))
+	if (!ethtool_get (ifname, &edata.e))
 		return FALSE;
 
-	g_assert (epaddr->size <= NM_UTILS_HWADDR_LEN_MAX);
-	memcpy (buf, epaddr->data, epaddr->size);
-	*length = epaddr->size;
+	g_assert (edata.e.size <= NM_UTILS_HWADDR_LEN_MAX);
+
+	/* Some drivers might return a permanent address of all zeros.
+	 * Reject that (rh#1264024) */
+	if (memcmp (edata.e.data, zeros, edata.e.size) == 0)
+		return FALSE;
+
+	memcpy (buf, edata.e.data, edata.e.size);
+	*length = edata.e.size;
 	return TRUE;
 }
 
@@ -269,6 +279,9 @@ nmp_utils_ethtool_set_wake_on_lan (const char *ifname,
 {
 	struct ethtool_wolinfo wol_info = { };
 
+	if (wol == NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE)
+		return TRUE;
+
 	nm_log_dbg (LOGD_PLATFORM, "setting Wake-on-LAN options 0x%x, password '%s'",
 	            (unsigned int) wol, wol_password);
 
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index fcb5b061..9948ac8b 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -427,7 +427,7 @@ nm_platform_link_get_all (NMPlatform *self)
 	for (i = 0; i < links->len; i++) {
 		item = &g_array_index (links, NMPlatformLink, i);
 
-		_LOGT ("link-get: %3d: %s", i, nm_platform_link_to_string (item));
+		_LOGt ("link-get: %3d: %s", i, nm_platform_link_to_string (item));
 
 		nm_assert (item->ifindex > 0 && !g_hash_table_contains (unseen, GINT_TO_POINTER (item->ifindex)));
 
@@ -483,7 +483,7 @@ nm_platform_link_get_all (NMPlatform *self)
 			if (item->parent > 0 && g_hash_table_contains (unseen, GINT_TO_POINTER (item->parent)))
 				continue;
 
-			_LOGT ("link-get: add %3d -> %3d: %s", i, j, nm_platform_link_to_string (item));
+			_LOGt ("link-get: add %3d -> %3d: %s", i, j, nm_platform_link_to_string (item));
 
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
 			g_array_index (result, NMPlatformLink, j++) = *item;
@@ -496,7 +496,7 @@ nm_platform_link_get_all (NMPlatform *self)
 			 * This can happen for veth pairs where each peer is parent of the other end. */
 			item = &g_array_index (links, NMPlatformLink, first_idx);
 
-			_LOGT ("link-get: add (loop) %3d -> %3d: %s", first_idx, j, nm_platform_link_to_string (item));
+			_LOGt ("link-get: add (loop) %3d -> %3d: %s", first_idx, j, nm_platform_link_to_string (item));
 
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
 			g_array_index (result, NMPlatformLink, j++) = *item;
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 7fe2d7d3..eb6bfc3a 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -178,7 +178,7 @@ nmp_object_ref (NMPObject *obj)
 	g_return_val_if_fail (obj->_ref_count != NMP_REF_COUNT_STACKINIT, NULL);
 	obj->_ref_count++;
 
-	_LOGT (obj, "ref: %d", obj->_ref_count);
+	_LOGt (obj, "ref: %d", obj->_ref_count);
 
 	return obj;
 }
@@ -189,7 +189,7 @@ nmp_object_unref (NMPObject *obj)
 	if (obj) {
 		g_return_if_fail (obj->_ref_count > 0);
 		g_return_if_fail (obj->_ref_count != NMP_REF_COUNT_STACKINIT);
-		_LOGT (obj, "%s: %d",
+		_LOGt (obj, "%s: %d",
 		       obj->_ref_count <= 1 ? "destroy" : "unref",
 		       obj->_ref_count - 1);
 		if (--obj->_ref_count <= 0) {
@@ -221,7 +221,7 @@ _nmp_object_new_from_class (const NMPClass *klass)
 	obj = g_slice_alloc0 (klass->sizeof_data + G_STRUCT_OFFSET (NMPObject, object));
 	obj->_class = klass;
 	obj->_ref_count = 1;
-	_LOGT (obj, "new");
+	_LOGt (obj, "new");
 	return obj;
 }
 
diff --git a/src/platform/tests/Makefile.am b/src/platform/tests/Makefile.am
index 54844ec1..35aa098a 100644
--- a/src/platform/tests/Makefile.am
+++ b/src/platform/tests/Makefile.am
@@ -1,6 +1,7 @@
 AM_CPPFLAGS = \
 	-I${top_srcdir} \
 	-I${top_srcdir}/include \
+	-I${top_builddir}/include \
 	-I${top_srcdir}/src \
 	-I${top_builddir}/src \
 	-I${top_srcdir}/libnm-core \
diff --git a/src/platform/tests/Makefile.in b/src/platform/tests/Makefile.in
index a9333539..f45f3a9c 100644
--- a/src/platform/tests/Makefile.in
+++ b/src/platform/tests/Makefile.in
@@ -616,6 +616,7 @@ LOG_DRIVER = @LOG_DRIVER@
 LTLIBICONV = @LTLIBICONV@
 LTLIBINTL = @LTLIBINTL@
 LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
 MAINT = @MAINT@
 MAKEINFO = @MAKEINFO@
 MANIFEST_TOOL = @MANIFEST_TOOL@
@@ -757,9 +758,10 @@ with_netconfig = @with_netconfig@
 with_resolvconf = @with_resolvconf@
 with_valgrind = @with_valgrind@
 AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/include \
-	-I${top_srcdir}/src -I${top_builddir}/src \
-	-I${top_srcdir}/libnm-core -I${top_builddir}/libnm-core \
-	-I${srcdir}/.. -DG_LOG_DOMAIN=\""NetworkManager"\" \
+	-I${top_builddir}/include -I${top_srcdir}/src \
+	-I${top_builddir}/src -I${top_srcdir}/libnm-core \
+	-I${top_builddir}/libnm-core -I${srcdir}/.. \
+	-DG_LOG_DOMAIN=\""NetworkManager"\" \
 	-DNETWORKMANAGER_COMPILATION \
 	-DNM_VERSION_MAX_ALLOWED=NM_VERSION_NEXT_STABLE $(GLIB_CFLAGS) \
 	$(GUDEV_CFLAGS) $(LIBNL_CFLAGS) $(am__append_1)
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index ce371b75..f9645d85 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -118,6 +118,41 @@ software_add (NMLinkType link_type, const char *name)
 }
 
 static void
+test_link_changed_signal_cb (NMPlatform *platform,
+                             NMPObjectType obj_type,
+                             int ifindex,
+                             const NMPlatformIP4Route *route,
+                             NMPlatformSignalChangeType change_type,
+                             NMPlatformReason reason,
+                             gboolean *p_test_link_changed_signal_arg)
+{
+	/* test invocation of platform signals with multiple listeners
+	 * connected to the signal. Platform signals have enum-typed
+	 * arguments and there seem to be an issue with invoking such
+	 * signals on s390x and ppc64 archs.
+	 * https://bugzilla.redhat.com/show_bug.cgi?id=1260577
+	 *
+	 * As the test shows, the failure is not reproducible for
+	 * platform signals.
+	 */
+	g_assert (NM_IS_PLATFORM (platform));
+	g_assert (platform == NM_PLATFORM_GET);
+
+	g_assert (ifindex > 0);
+	g_assert (route);
+
+	g_assert_cmpint (obj_type, ==, NMP_OBJECT_TYPE_LINK);
+
+	g_assert_cmpint ((gint64) change_type, !=, (gint64) 0);
+	g_assert_cmpint (change_type, !=, NM_PLATFORM_SIGNAL_NONE);
+
+	g_assert_cmpint ((gint64) reason, !=, (gint64) 0);
+	g_assert_cmpint (reason, !=, NM_PLATFORM_REASON_NONE);
+
+	*p_test_link_changed_signal_arg = TRUE;
+}
+
+static void
 test_slave (int master, int type, SignalData *master_changed)
 {
 	int ifindex;
@@ -125,6 +160,8 @@ test_slave (int master, int type, SignalData *master_changed)
 	SignalData *link_changed, *link_removed;
 	char *value;
 	NMLinkType link_type = nm_platform_link_get_type (NM_PLATFORM_GET, master);
+	gboolean test_link_changed_signal_arg1;
+	gboolean test_link_changed_signal_arg2;
 
 	g_assert (NM_IN_SET (link_type, NM_LINK_TYPE_TEAM, NM_LINK_TYPE_BOND, NM_LINK_TYPE_BRIDGE));
 
@@ -158,11 +195,21 @@ test_slave (int master, int type, SignalData *master_changed)
 	else
 		g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
 
+	test_link_changed_signal_arg1 = FALSE;
+	test_link_changed_signal_arg2 = FALSE;
+	g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (test_link_changed_signal_cb), &test_link_changed_signal_arg1);
+	g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (test_link_changed_signal_cb), &test_link_changed_signal_arg2);
+
 	/* Set master up */
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, master, NULL));
 	g_assert (nm_platform_link_is_up (NM_PLATFORM_GET, master));
 	accept_signals (master_changed, 1, 2);
 
+	g_signal_handlers_disconnect_by_func (NM_PLATFORM_GET, G_CALLBACK (test_link_changed_signal_cb), &test_link_changed_signal_arg1);
+	g_signal_handlers_disconnect_by_func (NM_PLATFORM_GET, G_CALLBACK (test_link_changed_signal_cb), &test_link_changed_signal_arg2);
+	g_assert (test_link_changed_signal_arg1);
+	g_assert (test_link_changed_signal_arg2);
+
 	/* Master with a disconnected slave is disconnected
 	 *
 	 * For some reason, bonding and teaming slaves are automatically set up. We