about summary refs log tree commit diff
path: root/src/platform
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/nm-linux-platform.c140
-rw-r--r--src/platform/tests/Makefile.am8
-rw-r--r--src/platform/tests/Makefile.in62
-rw-r--r--src/platform/tests/dump.c139
-rw-r--r--src/platform/tests/monitor.c22
-rw-r--r--src/platform/tests/platform.c902
-rw-r--r--src/platform/tests/test-link.c6
7 files changed, 84 insertions, 1195 deletions
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 2dd0b512..ee6ba421 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -384,6 +384,7 @@ _nl_link_parse_info_data (struct nl_sock *sk, int ifindex,
 	NMNLInfoDataClosure data = { .parser = parser, .parser_data = parser_data };
 	struct nl_msg *msg = NULL;
 	struct nl_cb *cb;
+	struct nl_cb *cb0;
 	int err;
 
 	err = rtnl_link_build_get_request (ifindex, NULL, &msg);
@@ -395,7 +396,9 @@ _nl_link_parse_info_data (struct nl_sock *sk, int ifindex,
 	if (err < 0)
 		return err;
 
-	cb = nl_cb_clone (nl_socket_get_cb (sk));
+	cb0 = nl_socket_get_cb (sk);
+	cb = nl_cb_clone (cb0);
+	nl_cb_put (cb0);
 	if (cb == NULL)
 		return -NLE_NOMEM;
 	nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, _nl_link_parse_info_data_cb, &data);
@@ -416,8 +419,11 @@ _nl_sock_flush_data (struct nl_sock *sk)
 {
 	int nle;
 	struct nl_cb *cb;
+	struct nl_cb *cb0;
 
-	cb = nl_cb_clone (nl_socket_get_cb (sk));
+	cb0 = nl_socket_get_cb (sk);
+	cb = nl_cb_clone (cb0);
+	nl_cb_put (cb0);
 	if (cb == NULL)
 		return -NLE_NOMEM;
 
@@ -747,7 +753,6 @@ struct _NMLinuxPlatformPrivate {
 	} delayed_action;
 
 	GHashTable *prune_candidates;
-	GHashTable *delayed_deletion;
 
 	GHashTable *wifi_data;
 };
@@ -1658,33 +1663,6 @@ 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);
@@ -1793,39 +1771,6 @@ cache_prune_candidates_prune (NMPlatform *platform)
 }
 
 static void
-cache_delayed_deletion_prune (NMPlatform *platform)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GPtrArray *prune_list = NULL;
-	GHashTableIter iter;
-	guint i;
-	NMPObject *obj;
-
-	if (g_hash_table_size (priv->delayed_deletion) == 0)
-		return;
-
-	g_hash_table_iter_init (&iter, priv->delayed_deletion);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &obj)) {
-		if (obj) {
-			if (!prune_list)
-				prune_list = g_ptr_array_new_full (g_hash_table_size (priv->delayed_deletion), (GDestroyNotify) nmp_object_unref);
-			g_ptr_array_add (prune_list, nmp_object_ref (obj));
-		}
-	}
-
-	g_hash_table_remove_all (priv->delayed_deletion);
-
-	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));
-			cache_remove_netlink (platform, obj, NULL, NULL, NM_PLATFORM_REASON_EXTERNAL);
-		}
-		g_ptr_array_unref (prune_list);
-	}
-}
-
-static void
 cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data)
 {
 	NMPlatform *platform = NM_PLATFORM (user_data);
@@ -1945,6 +1890,20 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 				                         NULL);
 			}
 		}
+		if (   NM_IN_SET (ops_type, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED)
+		    && (new && new->_link.netlink.is_in_netlink)
+		    && (!old || !old->_link.netlink.is_in_netlink))
+		{
+			if (   new->link.type == NM_LINK_TYPE_VETH
+			    && new->link.parent == 0) {
+				/* the initial notification when adding a veth pair can lack the parent/IFLA_LINK
+				 * (https://bugzilla.redhat.com/show_bug.cgi?id=1285827).
+				 * Request it again. */
+				delayed_action_schedule (platform,
+				                         DELAYED_ACTION_TYPE_REFRESH_LINK,
+				                         GINT_TO_POINTER (new->link.ifindex));
+			}
+		}
 		{
 			/* on enslave/release, we also refresh the master. */
 			int ifindex1 = 0, ifindex2 = 0;
@@ -1964,7 +1923,22 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 				if (ifindex2 > 0 && ifindex1 != ifindex2)
 					delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (ifindex2));
 			}
-
+		}
+		{
+			if (   (       (ops_type == NMP_CACHE_OPS_REMOVED)
+			        || (   (ops_type == NMP_CACHE_OPS_UPDATED)
+			            && new
+			            && !new->_link.netlink.is_in_netlink))
+			    && old
+			    && old->_link.netlink.is_in_netlink
+			    && old->link.master) {
+				/* sometimes we receive a wrong RTM_DELLINK message when unslaving
+				 * a device. Refetch the link again to check whether the device
+				 * is really gone.
+				 *
+				 * https://bugzilla.redhat.com/show_bug.cgi?id=1285719#c2 */
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (old->link.ifindex));
+			}
 		}
 		break;
 	case NMP_OBJECT_TYPE_IP4_ADDRESS:
@@ -2051,13 +2025,8 @@ do_request_link (NMPlatform *platform, int ifindex, const char *name, gboolean h
 	_LOGt ("do_request_link (%d,%s)", ifindex, name ? name : "");
 
 	if (ifindex > 0) {
-		NMPObject *obj;
-
 		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));
-		g_hash_table_insert (priv->delayed_deletion, obj, NULL);
 	}
 
 	event_handler_read_netlink_all (platform, FALSE);
@@ -2067,7 +2036,6 @@ do_request_link (NMPlatform *platform, int ifindex, const char *name, gboolean h
 
 	event_handler_read_netlink_all (platform, TRUE);
 
-	cache_delayed_deletion_prune (platform);
 	cache_prune_candidates_prune (platform);
 
 	if (handle_delayed_action)
@@ -2373,7 +2341,6 @@ static int
 event_notification (struct nl_msg *msg, gpointer user_data)
 {
 	NMPlatform *platform = NM_PLATFORM (user_data);
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (user_data);
 	auto_nl_object struct nl_object *nlo = NULL;
 	auto_nmp_obj NMPObject *obj = NULL;
 	struct nlmsghdr *msghdr;
@@ -2416,31 +2383,12 @@ 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) {
-				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:
 		case RTM_NEWROUTE:
 			cache_update_netlink (platform, obj, &obj_cache, NULL, NM_PLATFORM_REASON_EXTERNAL);
 			break;
 
 		case RTM_DELLINK:
-			if (   NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK
-			    && 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));
-				g_hash_table_insert (priv->delayed_deletion, nmp_object_ref (obj), nmp_object_ref (obj));
-				break;
-			}
-			/* fall-through */
 		case RTM_DELADDR:
 		case RTM_DELROUTE:
 			cache_remove_netlink (platform, obj, &obj_cache, NULL, NM_PLATFORM_REASON_EXTERNAL);
@@ -4636,11 +4584,16 @@ event_handler_read_netlink_one (NMPlatform *platform)
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	int nle;
 
+	errno = 0;
 	nle = nl_recvmsgs_default (priv->nlh_event);
 
 	/* Work around a libnl bug fixed in 3.2.22 (375a6294) */
-	if (nle == 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
+	if (nle == 0 && errno == EAGAIN) {
+		/* EAGAIN is equal to EWOULDBLOCK. If it would not be, we'd have to
+		 * workaround libnl3 mapping EWOULDBLOCK to -NLE_FAILURE. */
+		G_STATIC_ASSERT (EAGAIN == EWOULDBLOCK);
 		nle = -NLE_AGAIN;
+	}
 
 	if (nle < 0)
 		switch (nle) {
@@ -4883,10 +4836,6 @@ nm_linux_platform_init (NMLinuxPlatform *self)
 
 	self->priv = priv;
 
-	priv->delayed_deletion = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
-	                                                (GEqualFunc) nmp_object_id_equal,
-	                                                (GDestroyNotify) nmp_object_unref,
-	                                                (GDestroyNotify) nmp_object_unref);
 	priv->cache = nmp_cache_new ();
 	priv->delayed_action.list_master_connected = g_ptr_array_new ();
 	priv->delayed_action.list_refresh_link = g_ptr_array_new ();
@@ -4989,7 +4938,6 @@ dispose (GObject *object)
 	nm_clear_g_source (&priv->delayed_action.idle_id);
 
 	g_clear_pointer (&priv->prune_candidates, g_hash_table_unref);
-	g_clear_pointer (&priv->delayed_deletion, g_hash_table_unref);
 
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->dispose (object);
 }
diff --git a/src/platform/tests/Makefile.am b/src/platform/tests/Makefile.am
index 35aa098a..085f56ca 100644
--- a/src/platform/tests/Makefile.am
+++ b/src/platform/tests/Makefile.am
@@ -31,9 +31,7 @@ PLATFORM_LDADD = \
 @GNOME_CODE_COVERAGE_RULES@
 
 noinst_PROGRAMS = \
-	dump \
 	monitor \
-	platform \
 	test-link-fake \
 	test-link-linux \
 	test-address-fake \
@@ -50,12 +48,6 @@ EXTRA_DIST = test-common.h
 monitor_SOURCES = monitor.c $(PLATFORM_SOURCES)
 monitor_LDADD = $(PLATFORM_LDADD)
 
-dump_SOURCES = dump.c $(PLATFORM_SOURCES)
-dump_LDADD = $(PLATFORM_LDADD)
-
-platform_SOURCES = platform.c $(PLATFORM_SOURCES)
-platform_LDADD = $(PLATFORM_LDADD)
-
 test_link_fake_SOURCES = test-link.c $(TEST_SOURCES)
 test_link_fake_CPPFLAGS = \
 	$(AM_CPPFLAGS) \
diff --git a/src/platform/tests/Makefile.in b/src/platform/tests/Makefile.in
index f45f3a9c..e21a3bb6 100644
--- a/src/platform/tests/Makefile.in
+++ b/src/platform/tests/Makefile.in
@@ -89,12 +89,12 @@ POST_UNINSTALL = :
 build_triplet = @build@
 host_triplet = @host@
 @REQUIRE_ROOT_TESTS_TRUE@am__append_1 = -DREQUIRE_ROOT_TESTS=1
-noinst_PROGRAMS = dump$(EXEEXT) monitor$(EXEEXT) platform$(EXEEXT) \
-	test-link-fake$(EXEEXT) test-link-linux$(EXEEXT) \
-	test-address-fake$(EXEEXT) test-address-linux$(EXEEXT) \
-	test-general$(EXEEXT) test-nmp-object$(EXEEXT) \
-	test-route-fake$(EXEEXT) test-route-linux$(EXEEXT) \
-	test-cleanup-fake$(EXEEXT) test-cleanup-linux$(EXEEXT)
+noinst_PROGRAMS = monitor$(EXEEXT) test-link-fake$(EXEEXT) \
+	test-link-linux$(EXEEXT) test-address-fake$(EXEEXT) \
+	test-address-linux$(EXEEXT) test-general$(EXEEXT) \
+	test-nmp-object$(EXEEXT) test-route-fake$(EXEEXT) \
+	test-route-linux$(EXEEXT) test-cleanup-fake$(EXEEXT) \
+	test-cleanup-linux$(EXEEXT)
 TESTS = test-address-fake$(EXEEXT) test-address-linux$(EXEEXT) \
 	test-cleanup-fake$(EXEEXT) test-cleanup-linux$(EXEEXT) \
 	test-general$(EXEEXT) test-link-fake$(EXEEXT) \
@@ -125,19 +125,13 @@ CONFIG_CLEAN_VPATH_FILES =
 PROGRAMS = $(noinst_PROGRAMS)
 am__objects_1 = nm-platform.$(OBJEXT) nm-fake-platform.$(OBJEXT) \
 	nm-linux-platform.$(OBJEXT)
-am_dump_OBJECTS = dump.$(OBJEXT) $(am__objects_1)
-dump_OBJECTS = $(am_dump_OBJECTS)
-dump_DEPENDENCIES = $(PLATFORM_LDADD)
+am_monitor_OBJECTS = monitor.$(OBJEXT) $(am__objects_1)
+monitor_OBJECTS = $(am_monitor_OBJECTS)
+monitor_DEPENDENCIES = $(PLATFORM_LDADD)
 AM_V_lt = $(am__v_lt_@AM_V@)
 am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
 am__v_lt_0 = --silent
 am__v_lt_1 = 
-am_monitor_OBJECTS = monitor.$(OBJEXT) $(am__objects_1)
-monitor_OBJECTS = $(am_monitor_OBJECTS)
-monitor_DEPENDENCIES = $(PLATFORM_LDADD)
-am_platform_OBJECTS = platform.$(OBJEXT) $(am__objects_1)
-platform_OBJECTS = $(am_platform_OBJECTS)
-platform_DEPENDENCIES = $(PLATFORM_LDADD)
 am__objects_2 = test_address_fake-nm-platform.$(OBJEXT) \
 	test_address_fake-nm-fake-platform.$(OBJEXT) \
 	test_address_fake-nm-linux-platform.$(OBJEXT)
@@ -251,18 +245,18 @@ AM_V_CCLD = $(am__v_CCLD_@AM_V@)
 am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
 am__v_CCLD_0 = @echo "  CCLD    " $@;
 am__v_CCLD_1 = 
-SOURCES = $(dump_SOURCES) $(monitor_SOURCES) $(platform_SOURCES) \
-	$(test_address_fake_SOURCES) $(test_address_linux_SOURCES) \
-	$(test_cleanup_fake_SOURCES) $(test_cleanup_linux_SOURCES) \
-	$(test_general_SOURCES) $(test_link_fake_SOURCES) \
-	$(test_link_linux_SOURCES) $(test_nmp_object_SOURCES) \
-	$(test_route_fake_SOURCES) $(test_route_linux_SOURCES)
-DIST_SOURCES = $(dump_SOURCES) $(monitor_SOURCES) $(platform_SOURCES) \
-	$(test_address_fake_SOURCES) $(test_address_linux_SOURCES) \
-	$(test_cleanup_fake_SOURCES) $(test_cleanup_linux_SOURCES) \
-	$(test_general_SOURCES) $(test_link_fake_SOURCES) \
-	$(test_link_linux_SOURCES) $(test_nmp_object_SOURCES) \
-	$(test_route_fake_SOURCES) $(test_route_linux_SOURCES)
+SOURCES = $(monitor_SOURCES) $(test_address_fake_SOURCES) \
+	$(test_address_linux_SOURCES) $(test_cleanup_fake_SOURCES) \
+	$(test_cleanup_linux_SOURCES) $(test_general_SOURCES) \
+	$(test_link_fake_SOURCES) $(test_link_linux_SOURCES) \
+	$(test_nmp_object_SOURCES) $(test_route_fake_SOURCES) \
+	$(test_route_linux_SOURCES)
+DIST_SOURCES = $(monitor_SOURCES) $(test_address_fake_SOURCES) \
+	$(test_address_linux_SOURCES) $(test_cleanup_fake_SOURCES) \
+	$(test_cleanup_linux_SOURCES) $(test_general_SOURCES) \
+	$(test_link_fake_SOURCES) $(test_link_linux_SOURCES) \
+	$(test_nmp_object_SOURCES) $(test_route_fake_SOURCES) \
+	$(test_route_linux_SOURCES)
 am__can_run_installinfo = \
   case $$AM_UPDATE_INFO_DIR in \
     n|no|NO) false;; \
@@ -779,10 +773,6 @@ PLATFORM_LDADD = \
 EXTRA_DIST = test-common.h
 monitor_SOURCES = monitor.c $(PLATFORM_SOURCES)
 monitor_LDADD = $(PLATFORM_LDADD)
-dump_SOURCES = dump.c $(PLATFORM_SOURCES)
-dump_LDADD = $(PLATFORM_LDADD)
-platform_SOURCES = platform.c $(PLATFORM_SOURCES)
-platform_LDADD = $(PLATFORM_LDADD)
 test_link_fake_SOURCES = test-link.c $(TEST_SOURCES)
 test_link_fake_CPPFLAGS = \
 	$(AM_CPPFLAGS) \
@@ -894,18 +884,10 @@ clean-noinstPROGRAMS:
 	echo " rm -f" $$list; \
 	rm -f $$list
 
-dump$(EXEEXT): $(dump_OBJECTS) $(dump_DEPENDENCIES) $(EXTRA_dump_DEPENDENCIES) 
-	@rm -f dump$(EXEEXT)
-	$(AM_V_CCLD)$(LINK) $(dump_OBJECTS) $(dump_LDADD) $(LIBS)
-
 monitor$(EXEEXT): $(monitor_OBJECTS) $(monitor_DEPENDENCIES) $(EXTRA_monitor_DEPENDENCIES) 
 	@rm -f monitor$(EXEEXT)
 	$(AM_V_CCLD)$(LINK) $(monitor_OBJECTS) $(monitor_LDADD) $(LIBS)
 
-platform$(EXEEXT): $(platform_OBJECTS) $(platform_DEPENDENCIES) $(EXTRA_platform_DEPENDENCIES) 
-	@rm -f platform$(EXEEXT)
-	$(AM_V_CCLD)$(LINK) $(platform_OBJECTS) $(platform_LDADD) $(LIBS)
-
 test-address-fake$(EXEEXT): $(test_address_fake_OBJECTS) $(test_address_fake_DEPENDENCIES) $(EXTRA_test_address_fake_DEPENDENCIES) 
 	@rm -f test-address-fake$(EXEEXT)
 	$(AM_V_CCLD)$(LINK) $(test_address_fake_OBJECTS) $(test_address_fake_LDADD) $(LIBS)
@@ -952,12 +934,10 @@ mostlyclean-compile:
 distclean-compile:
 	-rm -f *.tab.c
 
-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dump.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/monitor.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-fake-platform.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-linux-platform.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-platform.Po@am__quote@
-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/platform.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-general.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-nmp-object.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test_address_fake-nm-fake-platform.Po@am__quote@
diff --git a/src/platform/tests/dump.c b/src/platform/tests/dump.c
deleted file mode 100644
index 54de1da7..00000000
--- a/src/platform/tests/dump.c
+++ /dev/null
@@ -1,139 +0,0 @@
-#include "config.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <arpa/inet.h>
-
-#include "nm-platform.h"
-#include "nm-linux-platform.h"
-#include "nm-fake-platform.h"
-#include "nm-macros-internal.h"
-
-static void
-dump_interface (NMPlatformLink *link)
-{
-	GArray *ip6_addresses;
-	GArray *ip4_addresses;
-	const NMPlatformIP6Address *ip6_address;
-	const NMPlatformIP4Address *ip4_address;
-	GArray *ip6_routes;
-	GArray *ip4_routes;
-	const NMPlatformIP6Route *ip6_route;
-	const NMPlatformIP4Route *ip4_route;
-	char networkstr[INET6_ADDRSTRLEN];
-	char gatewaystr[INET6_ADDRSTRLEN];
-	int vlan_id, vlan_parent;
-	const char *address;
-	size_t addrlen;
-	int i;
-
-	g_assert (NM_FLAGS_HAS (link->flags, IFF_UP) || !link->connected);
-
-	printf ("%d: %s: %s", link->ifindex, link->name, nm_link_type_to_string (link->type));
-	if (NM_FLAGS_HAS (link->flags, IFF_UP))
-		printf (" %s", link->connected ? "CONNECTED" : "DISCONNECTED");
-	else
-		printf (" DOWN");
-	if (NM_FLAGS_HAS (link->flags, IFF_NOARP))
-		printf (" noarp");
-	if (link->master)
-		printf (" master %d", link->master);
-	if (link->parent)
-		printf (" parent %d", link->parent);
-	printf (" mtu %d", link->mtu);
-	printf ("\n");
-	if (link->driver)
-		printf ("    driver: %s\n", link->driver);
-	printf ("    UDI: %s\n", nm_platform_link_get_udi (NM_PLATFORM_GET, link->ifindex));
-	if (!nm_platform_vlan_get_info (NM_PLATFORM_GET, link->ifindex, &vlan_parent, &vlan_id))
-		g_assert_not_reached ();
-	if (vlan_parent)
-		printf ("    vlan parent %d id %d\n", vlan_parent, vlan_id);
-
-	if (nm_platform_link_is_software (NM_PLATFORM_GET, link->ifindex))
-		printf ("    class software\n");
-	if (nm_platform_link_supports_slaves (NM_PLATFORM_GET, link->ifindex))
-		printf ("    class supports-slaves\n");
-	if (nm_platform_link_supports_carrier_detect (NM_PLATFORM_GET, link->ifindex))
-		printf ("    feature carrier-detect\n");
-	if (nm_platform_link_supports_vlans (NM_PLATFORM_GET, link->ifindex))
-		printf ("    feature vlans\n");
-
-	address = nm_platform_link_get_address (NM_PLATFORM_GET, link->ifindex, &addrlen);
-	if (address) {
-		printf ("    link-address ");
-		for (i = 0; i < addrlen; i++)
-			printf ("%s%02hhx", i ? ":" : "", address[i]);
-		printf ("\n");
-	}
-
-	ip4_addresses = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, link->ifindex);
-	ip6_addresses = nm_platform_ip6_address_get_all (NM_PLATFORM_GET, link->ifindex);
-
-	g_assert (ip4_addresses);
-	g_assert (ip6_addresses);
-
-	for (i = 0; i < ip4_addresses->len; i++) {
-		ip4_address = &g_array_index (ip4_addresses, NMPlatformIP4Address, i);
-		printf ("    ip4-address %s\n", nm_platform_ip4_address_to_string (ip4_address));
-	}
-
-	for (i = 0; i < ip6_addresses->len; i++) {
-		ip6_address = &g_array_index (ip6_addresses, NMPlatformIP6Address, i);
-		printf ("    ip6-address %s\n", nm_platform_ip6_address_to_string (ip6_address));
-	}
-
-	g_array_unref (ip4_addresses);
-	g_array_unref (ip6_addresses);
-
-	ip4_routes = nm_platform_ip4_route_get_all (NM_PLATFORM_GET, link->ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-	ip6_routes = nm_platform_ip6_route_get_all (NM_PLATFORM_GET, link->ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-
-	g_assert (ip4_routes);
-	g_assert (ip6_routes);
-
-	for (i = 0; i < ip4_routes->len; i++) {
-		ip4_route = &g_array_index (ip4_routes, NMPlatformIP4Route, i);
-		inet_ntop (AF_INET, &ip4_route->network, networkstr, sizeof (networkstr));
-		inet_ntop (AF_INET, &ip4_route->gateway, gatewaystr, sizeof (gatewaystr));
-		printf ("    ip4-route %s/%d via %s\n", networkstr, ip4_route->plen, gatewaystr);
-	}
-
-	for (i = 0; i < ip6_routes->len; i++) {
-		ip6_route = &g_array_index (ip6_routes, NMPlatformIP6Route, i);
-		inet_ntop (AF_INET6, &ip6_route->network, networkstr, sizeof (networkstr));
-		inet_ntop (AF_INET6, &ip6_route->gateway, gatewaystr, sizeof (gatewaystr));
-		printf ("    ip6-route %s/%d via %s\n", networkstr, ip6_route->plen, gatewaystr);
-	}
-
-	g_array_unref (ip4_routes);
-	g_array_unref (ip6_routes);
-}
-
-static void
-dump_all (void)
-{
-	GArray *links = nm_platform_link_get_all (NM_PLATFORM_GET);
-	int i;
-
-	for (i = 0; i < links->len; i++)
-		dump_interface (&g_array_index (links, NMPlatformLink, i));
-}
-
-int
-main (int argc, char **argv)
-{
-#if !GLIB_CHECK_VERSION (2, 35, 0)
-	g_type_init ();
-#endif
-
-	g_assert (argc <= 2);
-	if (argc > 1 && !g_strcmp0 (argv[1], "--fake"))
-		nm_fake_platform_setup ();
-	else
-		nm_linux_platform_setup ();
-
-	dump_all ();
-
-	return EXIT_SUCCESS;
-}
diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c
index 9aff1047..f1058db8 100644
--- a/src/platform/tests/monitor.c
+++ b/src/platform/tests/monitor.c
@@ -3,10 +3,13 @@
 #include <stdlib.h>
 #include <syslog.h>
 
-#include "nm-fake-platform.h"
 #include "nm-linux-platform.h"
 #include "nm-logging.h"
 
+#include "nm-test-utils.h"
+
+NMTST_DEFINE ();
+
 int
 main (int argc, char **argv)
 {
@@ -16,15 +19,16 @@ main (int argc, char **argv)
 	g_type_init ();
 #endif
 
+	if (!g_getenv ("G_MESSAGES_DEBUG"))
+		g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+	nmtst_init_with_logging (&argc, &argv, "DEBUG", "ALL");
+
+	nm_log_info (LOGD_PLATFORM, "platform monitor start");
+
 	loop = g_main_loop_new (NULL, FALSE);
-	nm_logging_setup ("debug", NULL, NULL, NULL);
-	openlog (G_LOG_DOMAIN, LOG_CONS | LOG_PERROR, LOG_DAEMON);
-
-	g_assert (argc <= 2);
-	if (argc > 1 && !g_strcmp0 (argv[1], "--fake"))
-		nm_fake_platform_setup ();
-	else
-		nm_linux_platform_setup ();
+
+	nm_linux_platform_setup ();
 
 	g_main_loop_run (loop);
 
diff --git a/src/platform/tests/platform.c b/src/platform/tests/platform.c
deleted file mode 100644
index 24e828c1..00000000
--- a/src/platform/tests/platform.c
+++ /dev/null
@@ -1,902 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* nm-platform.c - Handle runtime kernel networking configuration
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2013 Red Hat, Inc.
- */
-
-#include "config.h"
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <arpa/inet.h>
-#include <netlink/route/addr.h>
-
-#include "gsystem-local-alloc.h"
-#include "nm-platform.h"
-#include "nm-linux-platform.h"
-#include "nm-fake-platform.h"
-
-#define error(...) fprintf (stderr, __VA_ARGS__)
-
-typedef gboolean boolean_t;
-typedef int decimal_t;
-typedef const char *string_t;
-
-#define print_boolean(value) printf ("%s\n", value ? "yes" : "no")
-#define print_decimal(value) printf ("%d\n", value)
-#define print_string(value) printf ("%s\n", value)
-
-static gboolean
-do_sysctl_set (char **argv)
-{
-	return nm_platform_sysctl_set (NM_PLATFORM_GET, argv[0], argv[1]);
-}
-
-static gboolean
-do_sysctl_get (char **argv)
-{
-	gs_free char *value = nm_platform_sysctl_get (NM_PLATFORM_GET, argv[0]);
-
-	printf ("%s\n", value);
-
-	return !!value;
-}
-
-static int
-parse_ifindex (const char *str)
-{
-	char *endptr;
-	int ifindex = 0;
-
-	ifindex = strtol (str, &endptr, 10);
-
-	if (*endptr) {
-		ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, str);
-	}
-
-	return ifindex;
-}
-
-static gboolean
-do_link_get_all (char **argv)
-{
-	GArray *links;
-	NMPlatformLink *device;
-	int i;
-
-	links = nm_platform_link_get_all (NM_PLATFORM_GET);
-	for (i = 0; i < links->len; i++) {
-		device = &g_array_index (links, NMPlatformLink, i);
-
-		printf ("%d: %s type %d\n", device->ifindex, device->name, device->type);
-	}
-	g_array_unref (links);
-
-	return TRUE;
-}
-
-static gboolean
-do_dummy_add (char **argv)
-{
-	return nm_platform_dummy_add (NM_PLATFORM_GET, argv[0], NULL) == NM_PLATFORM_ERROR_SUCCESS;
-}
-
-static gboolean
-do_bridge_add (char **argv)
-{
-	return nm_platform_bridge_add (NM_PLATFORM_GET, argv[0], NULL, 0, NULL) == NM_PLATFORM_ERROR_SUCCESS;
-}
-
-static gboolean
-do_bond_add (char **argv)
-{
-	return nm_platform_bond_add (NM_PLATFORM_GET, argv[0], NULL) == NM_PLATFORM_ERROR_SUCCESS;
-}
-
-static gboolean
-do_team_add (char **argv)
-{
-	return nm_platform_team_add (NM_PLATFORM_GET, argv[0], NULL) == NM_PLATFORM_ERROR_SUCCESS;
-}
-
-static gboolean
-do_vlan_add (char **argv)
-{
-	const char *name = *argv++;
-	int parent = parse_ifindex (*argv++);
-	int vlanid = strtol (*argv++, NULL, 10);
-	guint32 vlan_flags = strtol (*argv++, NULL, 10);
-
-	return nm_platform_vlan_add (NM_PLATFORM_GET, name, parent, vlanid, vlan_flags, NULL) == NM_PLATFORM_ERROR_SUCCESS;
-}
-
-static gboolean
-do_link_exists (char **argv)
-{
-	gboolean value = !!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, argv[0]);
-
-	print_boolean (value);
-
-	return TRUE;
-}
-
-#define LINK_CMD(cmdname) \
-	static gboolean \
-	do_link_##cmdname (char **argv) \
-	{ \
-		int ifindex = parse_ifindex (argv[0]); \
-		return ifindex ? nm_platform_link_##cmdname (NM_PLATFORM_GET, ifindex) : FALSE; \
-	}
-
-#define LINK_CMD_GET_FULL(cmdname, type, cond) \
-	static gboolean \
-	do_link_##cmdname (char **argv) \
-	{ \
-		int ifindex = parse_ifindex (argv[0]); \
-		if (ifindex) { \
-			type##_t value = nm_platform_link_##cmdname (NM_PLATFORM_GET, ifindex); \
-			if (cond) { \
-				print_##type (value); \
-				return TRUE; \
-			} \
-		} \
-		return FALSE; \
-	}
-#define LINK_CMD_GET(cmdname, type) LINK_CMD_GET_FULL (cmdname, type, TRUE);
-
-LINK_CMD (delete)
-
-/* do_link_delete_by_ifname:
- *
- * We don't need this as we allow ifname instead of ifindex anyway.
- */
-
-static gboolean
-do_link_get_ifindex (char **argv)
-{
-	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, argv[0]);
-
-	if (ifindex)
-		printf ("%d\n", ifindex);
-
-	return !!ifindex;
-}
-
-LINK_CMD_GET_FULL (get_name, string, value)
-LINK_CMD_GET_FULL (get_type, decimal, value > 0)
-LINK_CMD_GET (is_software, boolean)
-LINK_CMD_GET (supports_slaves, boolean)
-
-static gboolean
-do_link_set_up (char **argv)
-{
-	int ifindex = parse_ifindex (argv[0]);
-
-	return ifindex ? nm_platform_link_set_up (NM_PLATFORM_GET, ifindex, NULL) : FALSE;
-}
-
-LINK_CMD (set_down)
-LINK_CMD (set_arp)
-LINK_CMD (set_noarp)
-LINK_CMD_GET (is_up, boolean)
-LINK_CMD_GET (is_connected, boolean)
-LINK_CMD_GET (uses_arp, boolean)
-
-static gboolean
-do_link_set_address (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	char *hex = *argv++;
-	int hexlen = strlen (hex);
-	char address[hexlen/2];
-	char *endptr;
-	int i;
-
-	g_assert (!(hexlen % 2));
-
-	for (i = 0; i < sizeof (address); i++) {
-		char digit[3];
-
-		digit[0] = hex[2*i];
-		digit[1] = hex[2*i+1];
-		digit[2] = '\0';
-
-		address[i] = strtoul (digit, &endptr, 16);
-		g_assert (!*endptr);
-	}
-
-	return nm_platform_link_set_address (NM_PLATFORM_GET, ifindex, address, sizeof (address));
-}
-
-static gboolean
-do_link_get_address (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	const char *address;
-	size_t length;
-	int i;
-
-	address = nm_platform_link_get_address (NM_PLATFORM_GET, ifindex, &length);
-
-	if (!address || length <= 0)
-		return FALSE;
-
-	for (i = 0; i < length; i++)
-		printf ("%02x", address[i]);
-	printf ("\n");
-
-	return TRUE;
-}
-
-static gboolean
-do_link_set_mtu (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	int mtu = strtoul (*argv++, NULL, 10);
-
-	return nm_platform_link_set_mtu (NM_PLATFORM_GET, ifindex, mtu);
-}
-
-LINK_CMD_GET (get_mtu, decimal);
-LINK_CMD_GET (supports_carrier_detect, boolean)
-LINK_CMD_GET (supports_vlans, boolean)
-
-static gboolean
-do_link_enslave (char **argv)
-{
-	int master = parse_ifindex (*argv++);
-	int slave = parse_ifindex (*argv++);
-
-	return nm_platform_link_enslave (NM_PLATFORM_GET, master, slave);
-}
-
-static gboolean
-do_link_release (char **argv)
-{
-	int master = parse_ifindex (*argv++);
-	int slave = parse_ifindex (*argv++);
-
-	return nm_platform_link_release (NM_PLATFORM_GET, master, slave);
-}
-
-LINK_CMD_GET (get_master, decimal)
-
-static gboolean
-do_master_set_option (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	const char *option = *argv++;
-	const char *value = *argv++;
-
-	return nm_platform_master_set_option (NM_PLATFORM_GET, ifindex, option, value);
-}
-
-static gboolean
-do_master_get_option (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	const char *option = *argv++;
-	gs_free char *value = nm_platform_master_get_option (NM_PLATFORM_GET, ifindex, option);
-
-	printf ("%s\n", value);
-
-	return !!value;
-}
-
-static gboolean
-do_slave_set_option (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	const char *option = *argv++;
-	const char *value = *argv++;
-
-	return nm_platform_slave_set_option (NM_PLATFORM_GET, ifindex, option, value);
-}
-
-static gboolean
-do_slave_get_option (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	const char *option = *argv++;
-	gs_free char *value = nm_platform_slave_get_option (NM_PLATFORM_GET, ifindex, option);
-
-	printf ("%s\n", value);
-
-	return !!value;
-}
-
-static gboolean
-do_vlan_get_info (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	int parent;
-	int vlanid;
-
-	if (!nm_platform_vlan_get_info (NM_PLATFORM_GET, ifindex, &parent, &vlanid))
-		return FALSE;
-
-	printf ("%d %d\n", parent, vlanid);
-
-	return TRUE;
-}
-
-static gboolean
-do_vlan_set_ingress_map (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	int from = strtol (*argv++, NULL, 10);
-	int to = strtol (*argv++, NULL, 10);
-
-	return nm_platform_vlan_set_ingress_map (NM_PLATFORM_GET, ifindex, from, to);
-}
-
-static gboolean
-do_vlan_set_egress_map (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	int from = strtol (*argv++, NULL, 10);
-	int to = strtol (*argv++, NULL, 10);
-
-	return nm_platform_vlan_set_egress_map (NM_PLATFORM_GET, ifindex, from, to);
-}
-
-static gboolean
-do_veth_get_properties (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	NMPlatformVethProperties props;
-
-	if (!nm_platform_veth_get_properties (NM_PLATFORM_GET, ifindex, &props))
-		return FALSE;
-
-	printf ("peer: %d\n", props.peer);
-
-	return TRUE;
-}
-
-static gboolean
-do_tun_get_properties (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	NMPlatformTunProperties props;
-
-	if (!nm_platform_tun_get_properties (NM_PLATFORM_GET, ifindex, &props))
-		return FALSE;
-
-	printf ("mode: %s\n", props.mode);
-	if (props.owner == -1)
-		printf ("owner: none\n");
-	else
-		printf ("owner: %lu\n", (gulong) props.owner);
-	if (props.group == -1)
-		printf ("group: none\n");
-	else
-		printf ("group: %lu\n", (gulong) props.group);
-	printf ("no-pi: ");
-	print_boolean (props.no_pi);
-	printf ("vnet-hdr: ");
-	print_boolean (props.vnet_hdr);
-	printf ("multi-queue: ");
-	print_boolean (props.multi_queue);
-
-	return TRUE;
-}
-
-static gboolean
-do_macvlan_get_properties (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	NMPlatformMacvlanProperties props;
-
-	if (!nm_platform_macvlan_get_properties (NM_PLATFORM_GET, ifindex, &props))
-		return FALSE;
-
-	printf ("parent: %d\n", props.parent_ifindex);
-	printf ("mode: %s\n", props.mode);
-	printf ("no-promisc: ");
-	print_boolean (props.no_promisc);
-	return TRUE;
-}
-
-static gboolean
-do_vxlan_get_properties (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	NMPlatformVxlanProperties props;
-	char addrstr[INET6_ADDRSTRLEN];
-
-	if (!nm_platform_vxlan_get_properties (NM_PLATFORM_GET, ifindex, &props))
-		return FALSE;
-
-	printf ("parent-ifindex: %u\n", props.parent_ifindex);
-	printf ("id: %u\n", props.id);
-	if (props.group)
-		inet_ntop (AF_INET, &props.group, addrstr, sizeof (addrstr));
-	else if (props.group6.s6_addr[0])
-		inet_ntop (AF_INET6, &props.group6, addrstr, sizeof (addrstr));
-	else
-		strcpy (addrstr, "-");
-	printf ("group: %s\n", addrstr);
-	if (props.local)
-		inet_ntop (AF_INET, &props.local, addrstr, sizeof (addrstr));
-	else if (props.local6.s6_addr[0])
-		inet_ntop (AF_INET6, &props.local6, addrstr, sizeof (addrstr));
-	else
-		strcpy (addrstr, "-");
-	printf ("local: %s\n", addrstr);
-	printf ("tos: %u\n", props.tos);
-	printf ("ttl: %u\n", props.ttl);
-	printf ("learning: ");
-	print_boolean (props.learning);
-	printf ("ageing: %u\n", props.ageing);
-	printf ("limit: %u\n", props.limit);
-	printf ("dst-port: %u\n", props.dst_port);
-	printf ("src-port-min: %u\n", props.src_port_min);
-	printf ("src-port-max: %u\n", props.src_port_max);
-	printf ("proxy: ");
-	print_boolean (props.proxy);
-	printf ("rsc: ");
-	print_boolean (props.rsc);
-	printf ("l2miss: ");
-	print_boolean (props.l2miss);
-	printf ("l3miss: ");
-	print_boolean (props.l3miss);
-
-	return TRUE;
-}
-
-static gboolean
-do_gre_get_properties (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	NMPlatformGreProperties props;
-	char addrstr[INET_ADDRSTRLEN];
-
-	if (!nm_platform_gre_get_properties (NM_PLATFORM_GET, ifindex, &props))
-		return FALSE;
-
-	printf ("parent-ifindex: %u\n", props.parent_ifindex);
-	printf ("input-flags: %u\n", props.input_flags);
-	printf ("output-flags: %u\n", props.input_flags);
-	printf ("input-key: %u\n", props.input_key);
-	printf ("output-key: %u\n", props.output_key);
-	if (props.local)
-		inet_ntop (AF_INET, &props.local, addrstr, sizeof (addrstr));
-	else
-		strcpy (addrstr, "-");
-	printf ("local: %s\n", addrstr);
-	if (props.remote)
-		inet_ntop (AF_INET, &props.remote, addrstr, sizeof (addrstr));
-	else
-		strcpy (addrstr, "-");
-	printf ("remote: %s\n", addrstr);
-	printf ("ttl: %u\n", props.ttl);
-	printf ("tos: %u\n", props.tos);
-	printf ("path-mtu-discovery: ");
-	print_boolean (props.path_mtu_discovery);
-
-	return TRUE;
-}
-
-static gboolean
-do_ip4_address_get_all (char **argv)
-{
-	int ifindex = parse_ifindex (argv[0]);
-	GArray *addresses;
-	NMPlatformIP4Address *address;
-	char addrstr[INET_ADDRSTRLEN];
-	int i;
-
-	if (ifindex) {
-		addresses = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
-		for (i = 0; i < addresses->len; i++) {
-			address = &g_array_index (addresses, NMPlatformIP4Address, i);
-			inet_ntop (AF_INET, &address->address, addrstr, sizeof (addrstr));
-			printf ("%s/%d\n", addrstr, address->plen);
-		}
-		g_array_unref (addresses);
-	}
-
-	return !!ifindex;
-}
-
-static gboolean
-do_ip6_address_get_all (char **argv)
-{
-	int ifindex = parse_ifindex (argv[0]);
-	GArray *addresses;
-	NMPlatformIP6Address *address;
-	char addrstr[INET6_ADDRSTRLEN];
-	int i;
-
-	if (ifindex) {
-		addresses = nm_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
-		for (i = 0; i < addresses->len; i++) {
-			address = &g_array_index (addresses, NMPlatformIP6Address, i);
-			inet_ntop (AF_INET6, &address->address, addrstr, sizeof (addrstr));
-			printf ("%s/%d\n", addrstr, address->plen);
-		}
-		g_array_unref (addresses);
-	}
-
-	return !!ifindex;
-}
-
-static gboolean
-parse_ip_address (int family, char *str, gpointer address, int *plen)
-{
-	char *endptr;
-
-	if (plen)
-		*plen = 0;
-
-	if (plen) {
-		char *ptr = strchr (str, '/');
-		if (ptr) {
-			*ptr++ = '\0';
-			*plen = strtol (ptr, &endptr, 10);
-			if (*endptr)
-				ptr = NULL;
-		}
-		if (!ptr) {
-			error ("Bad format of IP address, expected address/plen.\n");
-			return FALSE;
-		}
-	}
-
-	if (inet_pton (family, str, address))
-		return TRUE;
-
-	error ("Bad format of IP address, expected address%s.\n", plen ? "/plen" : "");
-	return FALSE;
-}
-
-typedef in_addr_t ip4_t;
-typedef struct in6_addr ip6_t;
-
-#define parse_ip4_address(s, a, p) parse_ip_address (AF_INET, s, a, p)
-#define parse_ip6_address(s, a, p) parse_ip_address (AF_INET6, s, a, p)
-
-static gboolean
-do_ip4_address_add (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	ip4_t address;
-	int plen;
-
-	if (ifindex && parse_ip4_address (*argv++, &address, &plen)) {
-		guint32 lifetime = strtol (*argv++, NULL, 10);
-		guint32 preferred = strtol (*argv++, NULL, 10);
-
-		gboolean value = nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, address, 0, plen, lifetime, preferred, NULL);
-		return value;
-	} else
-		return FALSE;
-}
-
-static gboolean
-do_ip6_address_add (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	ip6_t address;
-	int plen;
-
-	if (ifindex && parse_ip6_address (*argv++, &address, &plen)) {
-		guint32 lifetime = strtol (*argv++, NULL, 10);
-		guint32 preferred = strtol (*argv++, NULL, 10);
-		guint flags = (*argv) ? rtnl_addr_str2flags (*argv++) : 0;
-
-		gboolean value = nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, address, in6addr_any, plen, lifetime, preferred, flags);
-		return value;
-	} else
-		return FALSE;
-}
-
-#define ADDR_CMD_FULL(v, cmdname, print, ...) \
-	static gboolean \
-	do_##v##_address_##cmdname (char **argv) \
-	{ \
-		int ifindex = parse_ifindex (*argv++); \
-		v##_t address; \
-		int plen; \
-		if (ifindex && parse_##v##_address (*argv++, &address, &plen)) { \
-			gboolean value = !!nm_platform_##v##_address_##cmdname (NM_PLATFORM_GET, ifindex, address, plen, ##__VA_ARGS__); \
-			if (print) { \
-				print_boolean (value); \
-				return TRUE; \
-			} else \
-				return value; \
-		} else \
-			return FALSE; \
-	}
-#define ADDR_CMD(cmdname) ADDR_CMD_FULL (ip4, cmdname, FALSE, 0) ADDR_CMD_FULL (ip6, cmdname, FALSE)
-#define ADDR_CMD_PRINT(cmdname) ADDR_CMD_FULL (ip4, cmdname, TRUE) ADDR_CMD_FULL (ip6, cmdname, TRUE)
-
-ADDR_CMD (delete)
-ADDR_CMD_PRINT (get)
-
-static gboolean
-do_ip4_route_get_all (char **argv)
-{
-	int ifindex = parse_ifindex (argv[0]);
-	GArray *routes;
-	NMPlatformIP4Route *route;
-	char networkstr[INET_ADDRSTRLEN], gatewaystr[INET_ADDRSTRLEN];
-	int i;
-
-	if (ifindex) {
-		routes = nm_platform_ip4_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-		for (i = 0; i < routes->len; i++) {
-			route = &g_array_index (routes, NMPlatformIP4Route, i);
-			inet_ntop (AF_INET, &route->network, networkstr, sizeof (networkstr));
-			inet_ntop (AF_INET, &route->gateway, gatewaystr, sizeof (gatewaystr));
-			printf ("%s/%d via %s metric %d\n",
-					networkstr, route->plen, gatewaystr, route->metric);
-		}
-		g_array_unref (routes);
-	}
-
-	return !!ifindex;
-}
-
-static gboolean
-do_ip6_route_get_all (char **argv)
-{
-	int ifindex = parse_ifindex (argv[0]);
-	GArray *routes;
-	NMPlatformIP6Route *route;
-	char networkstr[INET6_ADDRSTRLEN], gatewaystr[INET6_ADDRSTRLEN];
-	int i;
-
-	if (ifindex) {
-		routes = nm_platform_ip6_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-		for (i = 0; i < routes->len; i++) {
-			route = &g_array_index (routes, NMPlatformIP6Route, i);
-			inet_ntop (AF_INET6, &route->network, networkstr, sizeof (networkstr));
-			inet_ntop (AF_INET6, &route->gateway, gatewaystr, sizeof (gatewaystr));
-			printf ("%s/%d via %s metric %d\n",
-					networkstr, route->plen, gatewaystr, route->metric);
-		}
-		g_array_unref (routes);
-	}
-
-	return !!ifindex;
-}
-
-static gboolean
-do_ip4_route_add (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	in_addr_t network, gateway;
-	int plen, metric, mss;
-
-	parse_ip4_address (*argv++, &network, &plen);
-	parse_ip4_address (*argv++, &gateway, NULL);
-	metric = strtol (*argv++, NULL, 10);
-	mss = strtol (*argv++, NULL, 10);
-
-	return nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER,
-	                                  network, plen, gateway, 0,
-	                                  metric, mss);
-}
-
-static gboolean
-do_ip6_route_add (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	struct in6_addr network, gateway;
-	int plen, metric, mss;
-
-	parse_ip6_address (*argv++, &network, &plen);
-	parse_ip6_address (*argv++, &gateway, NULL);
-	metric = strtol (*argv++, NULL, 10);
-	mss = strtol (*argv++, NULL, 10);
-	return nm_platform_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER,
-	                                  network, plen, gateway,
-	                                  metric, mss);
-}
-
-static gboolean
-do_ip4_route_delete (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	in_addr_t network;
-	int plen, metric;
-
-	parse_ip4_address (*argv++, &network, &plen);
-	metric = strtol (*argv++, NULL, 10);
-
-	return nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric);
-}
-
-static gboolean
-do_ip6_route_delete (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	struct in6_addr network;
-	int plen, metric;
-
-	parse_ip6_address (*argv++, &network, &plen);
-	metric = strtol (*argv++, NULL, 10);
-
-	return nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric);
-}
-
-static gboolean
-do_ip4_route_get (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	in_addr_t network;
-	int plen, metric;
-
-	parse_ip4_address (*argv++, &network, &plen);
-	metric = strtol (*argv++, NULL, 10);
-
-	print_boolean (!!nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric));
-	return TRUE;
-}
-
-static gboolean
-do_ip6_route_get (char **argv)
-{
-	int ifindex = parse_ifindex (*argv++);
-	struct in6_addr network;
-	int plen, metric;
-
-	parse_ip6_address (*argv++, &network, &plen);
-	metric = strtol (*argv++, NULL, 10);
-
-	print_boolean (!!nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric));
-	return TRUE;
-}
-
-typedef struct {
-	const char *name;
-	const char *help;
-	int (*handler) (char **argv);
-	int argc;
-	const char *arghelp;
-} command_t;
-
-static const command_t commands[] = {
-	{ "sysctl-set", "get /proc/sys or /sys value", do_sysctl_set, 2, "<path> <value>" },
-	{ "sysctl-get", "get /proc/sys or /sys value", do_sysctl_get, 1, "<value>" },
-	{ "link-get-all", "print all links", do_link_get_all, 0, "" },
-	{ "dummy-add", "add dummy interface", do_dummy_add, 1, "<ifname>" },
-	{ "bridge-add", "add bridge interface", do_bridge_add, 1, "<ifname>" },
-	{ "bond-add", "add bond interface", do_bond_add, 1, "<ifname>" },
-	{ "team-add", "add team interface", do_team_add, 1, "<ifname>" },
-	{ "vlan-add", "add vlan interface", do_vlan_add, 4, "<ifname> <parent> <vlanid> <vlanflags>" },
-	{ "link-exists", "check ifname for existance", do_link_exists, 1, "<ifname>" },
-	{ "link-delete", "delete interface", do_link_delete, 1, "<ifname/ifindex>" },
-	{ "link-get-ifindex>", "get interface index", do_link_get_ifindex, 1, "<ifname>" },
-	{ "link-get-name", "get interface name", do_link_get_name, 1, "<ifindex>" },
-	{ "link-get-type", "get interface type", do_link_get_type, 1, "<ifname/ifindex>" },
-	{ "link-is-software", "check if interface is a software one", do_link_is_software, 1, "<ifname/ifindex>" },
-	{ "link-supports-slaves", "check if interface supports slaves", do_link_supports_slaves, 1, "<ifname/ifindex>" },
-	{ "link-set-up", "set interface up", do_link_set_up, 1, "<ifname/ifindex>" },
-	{ "link-set-down", "set interface down", do_link_set_down, 1, "<ifname/ifindex>" },
-	{ "link-set-arp", "activate interface arp", do_link_set_arp, 1, "<ifname/ifindex>" },
-	{ "link-set-noarp", "deactivate interface arp", do_link_set_noarp, 1, "<ifname/ifindex>" },
-	{ "link-is-up", "check if interface is up", do_link_is_up, 1, "<ifname/ifindex>" },
-	{ "link-is-connected", "check interface carrier", do_link_is_connected, 1, "<ifname/ifindex>" },
-	{ "link-uses-arp", "check whether interface uses arp", do_link_uses_arp, 1, "<ifname/ifindex>" },
-	{ "link-get-address", "print link address", do_link_get_address, 1, "<ifname/ifindex>" },
-	{ "link-set-address", "set link address", do_link_set_address, 2, "<ifname/ifindex> <hex>" },
-	{ "link-get-mtu", "print link mtu", do_link_get_mtu, 1, "<ifname/ifindex>" },
-	{ "link-set-mtu", "set link mtu", do_link_set_mtu, 2, "<ifname/ifindex> <mtu>" },
-	{ "link-supports-carrier-detect", "check whether interface supports carrier detect",
-		do_link_supports_carrier_detect, 1, "<ifname/ifindex>" },
-	{ "link-supports-vlans", "check whether interface supports VLANs",
-		do_link_supports_vlans, 1, "<ifname/ifindex>" },
-	{ "link-enslave", "enslave slave interface with master", do_link_enslave, 2, "<master> <slave>" },
-	{ "link-release", "release save interface from master", do_link_release, 2, "<master> <slave>" },
-	{ "link-get-master", "print master interface of a slave", do_link_get_master, 1, "<ifname/ifindex>" },
-	{ "link-master-set-option", "set master option", do_master_set_option, 3,
-		"<ifname/ifindex> <option> <value>" },
-	{ "link-master-get-option", "get master option", do_master_get_option, 2,
-		"<ifname/ifindex> <option>" },
-	{ "link-slave-set-option", "set slave option", do_slave_set_option, 3,
-		"<ifname/ifindex> <option>" },
-	{ "link-slave-get-option", "get slave option", do_slave_get_option, 2,
-		"<ifname/ifindex> <option>" },
-	{ "vlan-get-info", "get vlan info", do_vlan_get_info, 1, "<ifname/ifindex>" },
-	{ "vlan-set-ingress-map", "set vlan ingress map", do_vlan_set_ingress_map, 3,
-		"<ifname/ifindex> <from> <to>" },
-	{ "vlan-set-egress-map", "set vlan egress map", do_vlan_set_egress_map, 3,
-		"<ifname/ifindex> <from> <to>" },
-	{ "veth-get-properties", "get veth properties", do_veth_get_properties, 1,
-	  "<ifname/ifindex>" },
-	{ "tun-get-properties", "get tun/tap properties", do_tun_get_properties, 1,
-	  "<ifname/ifindex>" },
-	{ "macvlan-get-properties", "get macvlan properties", do_macvlan_get_properties, 1,
-	  "<ifname/ifindex>" },
-	{ "vxlan-get-properties", "get vxlan properties", do_vxlan_get_properties, 1,
-	  "<ifname/ifindex>" },
-	{ "gre-get-properties", "get gre properties", do_gre_get_properties, 1,
-	  "<ifname/ifindex>" },
-	{ "ip4-address-get-all", "print all IPv4 addresses", do_ip4_address_get_all, 1, "<ifname/ifindex>" },
-	{ "ip6-address-get-all", "print all IPv6 addresses", do_ip6_address_get_all, 1, "<ifname/ifindex>" },
-	{ "ip4-address-add", "add IPv4 address", do_ip4_address_add, 4, "<ifname/ifindex> <address>/<plen> <lifetime> <>" },
-	{ "ip6-address-add", "add IPv6 address", do_ip6_address_add, 4, "<ifname/ifindex> <address>/<plen> <lifetime> [<flags>] <>" },
-	{ "ip4-address-delete", "delete IPv4 address", do_ip4_address_delete, 2,
-		"<ifname/ifindex> <address>/<plen>" },
-	{ "ip6-address-delete", "delete IPv6 address", do_ip6_address_delete, 2,
-		"<ifname/ifindex> <address>/<plen>" },
-	{ "ip4-address-exists", "check for existence of IPv4 address", do_ip4_address_get, 2,
-		"<ifname/ifindex> <address>/<plen>" },
-	{ "ip6-address-exists", "check for existence of IPv6 address", do_ip6_address_get, 2,
-		"<ifname/ifindex> <address>/<plen>" },
-	{ "ip4-route-get-all", "print all IPv4 routes", do_ip4_route_get_all, 1, "<ifname/ifindex>" },
-	{ "ip6-route-get-all", "print all IPv6 routes", do_ip6_route_get_all, 1, "<ifname/ifindex>" },
-	{ "ip4-route-add", "add IPv4 route", do_ip4_route_add, 5,
-		"<ifname/ifindex> <network>/<plen> <gateway> <metric> <mss>" },
-	{ "ip6-route-add", "add IPv6 route", do_ip6_route_add, 5,
-		"<ifname/ifindex> <network>/<plen> <gateway> <metric> <mss>" },
-	{ "ip4-route-delete", "delete IPv4 route", do_ip4_route_delete, 3,
-		"<ifname/ifindex> <network>/<plen> <metric>" },
-	{ "ip6-route-delete", "delete IPv6 route", do_ip6_route_delete, 3,
-		"<ifname/ifindex> <network>/<plen> <metric>" },
-	{ "ip4-route-exists", "check for existence of IPv4 route", do_ip4_route_get, 3,
-		"<ifname/ifindex> <network>/<plen> <metric>" },
-	{ "ip6-route-exists", "check for existence of IPv6 route", do_ip6_route_get, 3,
-		"<ifname/ifindex> <network>/<plen> <metric>" },
-	{ NULL, NULL, NULL, 0, NULL },
-};
-
-int
-main (int argc, char **argv)
-{
-	const char *arg0 = *argv++;
-	const command_t *command = NULL;
-	gboolean status = TRUE;
-
-#if !GLIB_CHECK_VERSION (2, 35, 0)
-	g_type_init ();
-#endif
-
-	if (*argv && !g_strcmp0 (argv[1], "--fake")) {
-		nm_fake_platform_setup ();
-	} else
-		nm_linux_platform_setup ();
-
-	if (*argv)
-		for (command = commands; command->name; command++)
-			if (g_str_has_prefix (command->name, *argv))
-				break;
-
-	if (command && command->name) {
-		argv++;
-		if (g_strv_length (argv) == command->argc)
-			status = command->handler (argv);
-		else {
-			error ("Wrong number of arguments to '%s' (expected %d).\n\nUsage: %s %s %s\n-- %s\n",
-					command->name, command->argc,
-					arg0, command->name, command->arghelp, command->help);
-			return EXIT_FAILURE;
-		}
-	} else {
-		error ("Usage: %s COMMAND\n\n", arg0);
-		error ("COMMAND\n");
-		for (command = commands; command->name; command++)
-			error ("  %s %s\n    -- %s\n", command->name, command->arghelp, command->help);
-		error ("\n");
-	}
-
-	return EXIT_SUCCESS;
-}
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index f9645d85..47cbcec1 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -274,10 +274,14 @@ test_slave (int master, int type, SignalData *master_changed)
 	}
 
 	/* Release */
+	ensure_no_signal (link_added);
 	ensure_no_signal (link_changed);
+	ensure_no_signal (link_removed);
 	g_assert (nm_platform_link_release (NM_PLATFORM_GET, master, ifindex));
 	g_assert_cmpint (nm_platform_link_get_master (NM_PLATFORM_GET, ifindex), ==, 0);
+	accept_signals (link_added, 0, 1);
 	accept_signals (link_changed, 1, 3);
+	accept_signals (link_removed, 0, 1);
 	accept_signals (master_changed, 1, 2);
 
 	ensure_no_signal (master_changed);
@@ -289,7 +293,9 @@ test_slave (int master, int type, SignalData *master_changed)
 	ensure_no_signal (master_changed);
 
 	/* Remove */
+	ensure_no_signal (link_added);
 	ensure_no_signal (link_changed);
+	ensure_no_signal (link_removed);
 	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
 	accept_signals (master_changed, 0, 1);
 	accept_signals (link_changed, 0, 1);