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-fake-platform.c2
-rw-r--r--src/platform/nm-linux-platform.c282
-rw-r--r--src/platform/nm-platform-utils.c106
-rw-r--r--src/platform/nm-platform-utils.h15
-rw-r--r--src/platform/nm-platform.c199
-rw-r--r--src/platform/nm-platform.h25
-rw-r--r--src/platform/nmp-netns.c732
-rw-r--r--src/platform/nmp-netns.h74
-rw-r--r--src/platform/nmp-object.c27
-rw-r--r--src/platform/nmp-object.h4
-rw-r--r--src/platform/tests/test-address.c14
-rw-r--r--src/platform/tests/test-cleanup.c2
-rw-r--r--src/platform/tests/test-common.c122
-rw-r--r--src/platform/tests/test-common.h19
-rw-r--r--src/platform/tests/test-general.c4
-rw-r--r--src/platform/tests/test-link.c501
-rw-r--r--src/platform/tests/test-nmp-object.c4
-rw-r--r--src/platform/tests/test-route.c4
-rw-r--r--src/platform/wifi/wifi-utils-wext.c25
19 files changed, 1760 insertions, 401 deletions
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 68b72b19..fdb11eac 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -890,6 +890,7 @@ ip4_address_add (NMPlatform *platform,
                  in_addr_t peer_addr,
                  guint32 lifetime,
                  guint32 preferred,
+                 guint32 flags,
                  const char *label)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
@@ -905,6 +906,7 @@ ip4_address_add (NMPlatform *platform,
 	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
 	address.lifetime = lifetime;
 	address.preferred = preferred;
+	address.n_ifa_flags = flags;
 	if (label)
 		g_strlcpy (address.label, label, sizeof (address.label));
 
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 196e75cb..e2ff1f61 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -50,6 +50,7 @@
 
 #include "nm-core-utils.h"
 #include "nmp-object.h"
+#include "nmp-netns.h"
 #include "nm-platform-utils.h"
 #include "wifi/wifi-utils.h"
 #include "wifi/wifi-utils-wext.h"
@@ -221,6 +222,7 @@ static void do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActi
 static void cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data);
 static void cache_prune_candidates_prune (NMPlatform *platform);
 static gboolean event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks);
+static void _assert_netns_current (NMPlatform *platform);
 
 /*****************************************************************************/
 
@@ -647,6 +649,8 @@ _linktype_get_type (NMPlatform *platform,
 {
 	guint i;
 
+	_assert_netns_current (platform);
+
 	if (completed_from_cache) {
 		const NMPObject *obj;
 
@@ -1673,11 +1677,9 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 
 	obj->ip_address.source = NM_IP_CONFIG_SOURCE_KERNEL;
 
-	if (!is_v4) {
-		obj->ip6_address.n_ifa_flags = tb[IFA_FLAGS]
-		                               ? nla_get_u32 (tb[IFA_FLAGS])
-		                               : ifa->ifa_flags;
-	}
+	obj->ip_address.n_ifa_flags = tb[IFA_FLAGS]
+	                              ? nla_get_u32 (tb[IFA_FLAGS])
+	                              : ifa->ifa_flags;
 
 	if (is_v4) {
 		if (tb[IFA_LABEL]) {
@@ -2225,7 +2227,7 @@ _nl_msg_new_address (int nlmsg_type,
 		NLA_PUT (msg, IFA_CACHEINFO, sizeof(ca), &ca);
 	}
 
-	if (flags & ~0xFF) {
+	if (flags & ~((guint32) 0xFF)) {
 		/* only set the IFA_FLAGS attribute, if they actually contain additional
 		 * flags that are not already set to am.ifa_flags.
 		 *
@@ -2412,6 +2414,7 @@ void
 nm_linux_platform_setup (void)
 {
 	g_object_new (NM_TYPE_LINUX_PLATFORM,
+	              NM_PLATFORM_NETNS_SUPPORT, FALSE,
 	              NM_PLATFORM_REGISTER_SINGLETON, TRUE,
 	              NULL);
 }
@@ -2419,6 +2422,16 @@ nm_linux_platform_setup (void)
 /******************************************************************/
 
 static void
+_assert_netns_current (NMPlatform *platform)
+{
+#if NM_MORE_ASSERTS
+	nm_assert (NM_IS_LINUX_PLATFORM (platform));
+
+	nm_assert (NM_IN_SET (nm_platform_netns_get (platform), NULL, nmp_netns_get_current ()));
+#endif
+}
+
+static void
 _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *path, const char *value)
 {
 	GError *error = NULL;
@@ -2451,6 +2464,7 @@ _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *path, const char *va
 static gboolean
 sysctl_set (NMPlatform *platform, const char *path, const char *value)
 {
+	nm_auto_pop_netns NMPNetns *netns = NULL;
 	int fd, tries;
 	gssize nwrote;
 	gsize len;
@@ -2466,6 +2480,9 @@ sysctl_set (NMPlatform *platform, const char *path, const char *value)
 	/* Don't write to suspicious locations */
 	g_assert (!strstr (path, "/../"));
 
+	if (!nm_platform_netns_push (platform, &netns))
+		return FALSE;
+
 	fd = open (path, O_WRONLY | O_TRUNC);
 	if (fd == -1) {
 		if (errno == ENOENT) {
@@ -2580,6 +2597,7 @@ _log_dbg_sysctl_get_impl (NMPlatform *platform, const char *path, const char *co
 static char *
 sysctl_get (NMPlatform *platform, const char *path)
 {
+	nm_auto_pop_netns NMPNetns *netns = NULL;
 	GError *error = NULL;
 	char *contents;
 
@@ -2589,6 +2607,9 @@ sysctl_get (NMPlatform *platform, const char *path)
 	/* Don't write to suspicious locations */
 	g_assert (!strstr (path, "/../"));
 
+	if (!nm_platform_netns_push (platform, &netns))
+		return NULL;
+
 	if (!g_file_get_contents (path, &contents, NULL, &error)) {
 		/* We assume FAILED means EOPNOTSUP */
 		if (   g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)
@@ -2653,6 +2674,9 @@ do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cach
 	nm_assert (!obj || cache_op == NMP_CACHE_OPS_REMOVED || obj == nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj));
 	nm_assert (!obj || cache_op != NMP_CACHE_OPS_REMOVED || obj != nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj));
 
+	/* we raise the signals inside the namespace of the NMPlatform instance. */
+	_assert_netns_current (platform);
+
 	switch (cache_op) {
 	case NMP_CACHE_OPS_ADDED:
 		if (!nmp_object_is_visible (obj))
@@ -3857,6 +3881,7 @@ do_change_link (NMPlatform *platform,
                 int ifindex,
                 struct nl_msg *nlmsg)
 {
+	nm_auto_pop_netns NMPNetns *netns = NULL;
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
 	char s_buf[256];
@@ -3864,6 +3889,9 @@ do_change_link (NMPlatform *platform,
 	NMLogLevel log_level = LOGL_DEBUG;
 	const char *log_result = "failure", *log_detail = "";
 
+	if (!nm_platform_netns_push (platform, &netns))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+
 retry:
 	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result);
 	if (nle < 0) {
@@ -3927,7 +3955,7 @@ link_add (NMPlatform *platform,
 		 * bond0 automatically.
 		 */
 		if (!g_file_test ("/sys/class/net/bonding_masters", G_FILE_TEST_EXISTS))
-			nm_utils_modprobe (NULL, TRUE, "bonding", "max_bonds=0", NULL);
+			(void) nm_utils_modprobe (NULL, TRUE, "bonding", "max_bonds=0", NULL);
 	}
 
 	_LOGD ("link: add link '%s' of type '%s' (%d)",
@@ -3994,7 +4022,7 @@ link_get_type_name (NMPlatform *platform, int ifindex)
 		return nm_link_type_to_string (obj->link.type);
 	}
 	/* Link type not detected. Fallback to rtnl_link_get_type()/IFLA_INFO_KIND. */
-	return str_if_set (obj->link.kind, "unknown");
+	return obj->link.kind ?: "unknown";
 }
 
 static gboolean
@@ -4023,6 +4051,31 @@ link_refresh (NMPlatform *platform, int ifindex)
 	return !!cache_lookup_link (platform, ifindex);
 }
 
+static gboolean
+link_set_netns (NMPlatform *platform,
+                int ifindex,
+                int netns_fd)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+
+	_LOGD ("link: move link %d to network namespace with fd %d", ifindex, netns_fd);
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
+
+	NLA_PUT (nlmsg, IFLA_NET_NS_FD, 4, &netns_fd);
+	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
+}
+
 static NMPlatformError
 link_change_flags (NMPlatform *platform,
                    int ifindex,
@@ -4137,11 +4190,15 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable
 static gboolean
 link_supports_carrier_detect (NMPlatform *platform, int ifindex)
 {
+	nm_auto_pop_netns NMPNetns *netns = NULL;
 	const char *name = nm_platform_link_get_name (platform, ifindex);
 
 	if (!name)
 		return FALSE;
 
+	if (!nm_platform_netns_push (platform, &netns))
+		return FALSE;
+
 	/* We use netlink for the actual carrier detection, but netlink can't tell
 	 * us whether the device actually supports carrier detection in the first
 	 * place. We assume any device that does implements one of these two APIs.
@@ -4152,6 +4209,7 @@ link_supports_carrier_detect (NMPlatform *platform, int ifindex)
 static gboolean
 link_supports_vlans (NMPlatform *platform, int ifindex)
 {
+	nm_auto_pop_netns NMPNetns *netns = NULL;
 	const NMPObject *obj;
 
 	obj = cache_lookup_link (platform, ifindex);
@@ -4160,6 +4218,9 @@ link_supports_vlans (NMPlatform *platform, int ifindex)
 	if (!obj || obj->link.arptype != ARPHRD_ETHER)
 		return FALSE;
 
+	if (!nm_platform_netns_push (platform, &netns))
+		return FALSE;
+
 	return nmp_utils_ethtool_supports_vlans (obj->link.name);
 }
 
@@ -4198,6 +4259,11 @@ link_get_permanent_address (NMPlatform *platform,
                             guint8 *buf,
                             size_t *length)
 {
+	nm_auto_pop_netns NMPNetns *netns = NULL;
+
+	if (!nm_platform_netns_push (platform, &netns))
+		return FALSE;
+
 	return nmp_utils_ethtool_get_permanent_address (nm_platform_link_get_name (platform, ifindex), buf, length);
 }
 
@@ -4234,7 +4300,7 @@ link_get_physical_port_id (NMPlatform *platform, int ifindex)
 	if (!ifname)
 		return NULL;
 
-	ifname = ASSERT_VALID_PATH_COMPONENT (ifname);
+	ifname = NM_ASSERT_VALID_PATH_COMPONENT (ifname);
 
 	path = g_strdup_printf ("/sys/class/net/%s/phys_port_id", ifname);
 	id = sysctl_get (platform, path);
@@ -4254,7 +4320,7 @@ link_get_dev_id (NMPlatform *platform, int ifindex)
 	if (!ifname)
 		return 0;
 
-	ifname = ASSERT_VALID_PATH_COMPONENT (ifname);
+	ifname = NM_ASSERT_VALID_PATH_COMPONENT (ifname);
 
 	path = g_strdup_printf ("/sys/class/net/%s/dev_id", ifname);
 	id = sysctl_get (platform, path);
@@ -4837,7 +4903,7 @@ tun_add (NMPlatform *platform, const char *name, gboolean tap,
 	if (fd < 0)
 		return FALSE;
 
-	strncpy (ifr.ifr_name, name, IFNAMSIZ);
+	nm_utils_ifname_cpy (ifr.ifr_name, name);
 	ifr.ifr_flags = tap ? IFF_TAP : IFF_TUN;
 
 	if (!pi)
@@ -4930,7 +4996,7 @@ infiniband_partition_add (NMPlatform *platform, int parent, int p_key, const NMP
 
 	ifname = g_strdup_printf ("%s.%04x", obj_parent->link.name, p_key);
 
-	path = g_strdup_printf ("/sys/class/net/%s/create_child", ASSERT_VALID_PATH_COMPONENT (obj_parent->link.name));
+	path = g_strdup_printf ("/sys/class/net/%s/create_child", NM_ASSERT_VALID_PATH_COMPONENT (obj_parent->link.name));
 	id = g_strdup_printf ("0x%04x", p_key);
 	if (!nm_platform_sysctl_set (platform, path, id))
 		return FALSE;
@@ -4977,15 +5043,19 @@ wifi_get_wifi_data (NMPlatform *platform, int ifindex)
 
 	return wifi_data;
 }
+#define WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, retval) \
+	nm_auto_pop_netns NMPNetns *netns = NULL; \
+	WifiData *wifi_data; \
+	if (!nm_platform_netns_push (platform, &netns)) \
+		return retval; \
+	wifi_data = wifi_get_wifi_data (platform, ifindex); \
+	if (!wifi_data) \
+		return retval;
 
 static gboolean
 wifi_get_capabilities (NMPlatform *platform, int ifindex, NMDeviceWifiCapabilities *caps)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (!wifi_data)
-		return FALSE;
-
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
 	if (caps)
 		*caps = wifi_utils_get_caps (wifi_data);
 	return TRUE;
@@ -4994,90 +5064,64 @@ wifi_get_capabilities (NMPlatform *platform, int ifindex, NMDeviceWifiCapabiliti
 static gboolean
 wifi_get_bssid (NMPlatform *platform, int ifindex, guint8 *bssid)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (!wifi_data)
-		return FALSE;
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
 	return wifi_utils_get_bssid (wifi_data, bssid);
 }
 
 static guint32
 wifi_get_frequency (NMPlatform *platform, int ifindex)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (!wifi_data)
-		return 0;
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, 0);
 	return wifi_utils_get_freq (wifi_data);
 }
 
 static gboolean
 wifi_get_quality (NMPlatform *platform, int ifindex)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (!wifi_data)
-		return FALSE;
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
 	return wifi_utils_get_qual (wifi_data);
 }
 
 static guint32
 wifi_get_rate (NMPlatform *platform, int ifindex)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (!wifi_data)
-		return FALSE;
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
 	return wifi_utils_get_rate (wifi_data);
 }
 
 static NM80211Mode
 wifi_get_mode (NMPlatform *platform, int ifindex)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (!wifi_data)
-		return NM_802_11_MODE_UNKNOWN;
-
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, NM_802_11_MODE_UNKNOWN);
 	return wifi_utils_get_mode (wifi_data);
 }
 
 static void
 wifi_set_mode (NMPlatform *platform, int ifindex, NM80211Mode mode)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (wifi_data)
-		wifi_utils_set_mode (wifi_data, mode);
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, );
+	wifi_utils_set_mode (wifi_data, mode);
 }
 
 static void
 wifi_set_powersave (NMPlatform *platform, int ifindex, guint32 powersave)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (wifi_data)
-		wifi_utils_set_powersave (wifi_data, powersave);
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, );
+	wifi_utils_set_powersave (wifi_data, powersave);
 }
 
 static guint32
 wifi_find_frequency (NMPlatform *platform, int ifindex, const guint32 *freqs)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (!wifi_data)
-		return 0;
-
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, 0);
 	return wifi_utils_find_freq (wifi_data, freqs);
 }
 
 static void
 wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean running)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (wifi_data)
-		wifi_utils_indicate_addressing_running (wifi_data, running);
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, );
+	wifi_utils_indicate_addressing_running (wifi_data, running);
 }
 
 /******************************************************************/
@@ -5085,33 +5129,21 @@ wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean ru
 static guint32
 mesh_get_channel (NMPlatform *platform, int ifindex)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (!wifi_data)
-		return 0;
-
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, 0);
 	return wifi_utils_get_mesh_channel (wifi_data);
 }
 
 static gboolean
 mesh_set_channel (NMPlatform *platform, int ifindex, guint32 channel)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (!wifi_data)
-		return FALSE;
-
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
 	return wifi_utils_set_mesh_channel (wifi_data, channel);
 }
 
 static gboolean
 mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len)
 {
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (!wifi_data)
-		return FALSE;
-
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
 	return wifi_utils_set_mesh_ssid (wifi_data, ssid, len);
 }
 
@@ -5120,8 +5152,12 @@ mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len)
 static gboolean
 link_get_wake_on_lan (NMPlatform *platform, int ifindex)
 {
+	nm_auto_pop_netns NMPNetns *netns = NULL;
 	NMLinkType type = nm_platform_link_get_type (platform, ifindex);
 
+	if (!nm_platform_netns_push (platform, &netns))
+		return FALSE;
+
 	if (type == NM_LINK_TYPE_ETHERNET)
 		return nmp_utils_ethtool_get_wake_on_lan (nm_platform_link_get_name (platform, ifindex));
 	else if (type == NM_LINK_TYPE_WIFI) {
@@ -5142,6 +5178,11 @@ link_get_driver_info (NMPlatform *platform,
                       char **out_driver_version,
                       char **out_fw_version)
 {
+	nm_auto_pop_netns NMPNetns *netns = NULL;
+
+	if (!nm_platform_netns_push (platform, &netns))
+		return FALSE;
+
 	return nmp_utils_ethtool_get_driver_info (nm_platform_link_get_name (platform, ifindex),
 	                                          out_driver_name,
 	                                          out_driver_version,
@@ -5184,6 +5225,7 @@ ip4_address_add (NMPlatform *platform,
                  in_addr_t peer_addr,
                  guint32 lifetime,
                  guint32 preferred,
+                 guint32 flags,
                  const char *label)
 {
 	NMPObject obj_id;
@@ -5196,8 +5238,8 @@ ip4_address_add (NMPlatform *platform,
 	                             &addr,
 	                             plen,
 	                             &peer_addr,
-	                             0,
-	                             nmp_utils_ip4_address_is_link_local (addr) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE,
+	                             flags,
+	                             nm_utils_ip4_address_is_link_local (addr) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE,
 	                             lifetime,
 	                             preferred,
 	                             label);
@@ -5722,6 +5764,7 @@ out:
 static gboolean
 event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 {
+	nm_auto_pop_netns NMPNetns *netns = NULL;
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	int r, nle;
 	struct pollfd pfd;
@@ -5734,6 +5777,9 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 		gint64 timeout_abs_ns;
 	} data_next;
 
+	if (!nm_platform_netns_push (platform, &netns))
+		return FALSE;
+
 	while (TRUE) {
 
 		while (TRUE) {
@@ -5838,7 +5884,14 @@ cache_update_link_udev (NMPlatform *platform, int ifindex, GUdevDevice *udev_dev
 	NMPCacheOpsType cache_op;
 
 	cache_op = nmp_cache_update_link_udev (priv->cache, ifindex, udev_device, &obj_cache, &was_visible, cache_pre_hook, platform);
-	do_emit_signal (platform, obj_cache, cache_op, was_visible);
+
+	if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+		nm_auto_pop_netns NMPNetns *netns = NULL;
+
+		if (!nm_platform_netns_push (platform, &netns))
+			return;
+		do_emit_signal (platform, obj_cache, cache_op, was_visible);
+	}
 }
 
 static void
@@ -5854,22 +5907,22 @@ udev_device_added (NMPlatform *platform,
 		return;
 	}
 
-	if (g_udev_device_get_property (udev_device, "IFINDEX"))
-		ifindex = g_udev_device_get_property_as_int (udev_device, "IFINDEX");
-	else {
-		_LOGW ("(%s): udev-add: failed to get device's ifindex", ifname);
+	if (!g_udev_device_get_property (udev_device, "IFINDEX")) {
+		_LOGW ("udev-add[%s]failed to get device's ifindex", ifname);
 		return;
 	}
+	ifindex = g_udev_device_get_property_as_int (udev_device, "IFINDEX");
 	if (ifindex <= 0) {
-		_LOGW ("(%s): udev-add: retrieved invalid IFINDEX=%d", ifname, ifindex);
+		_LOGW ("udev-add[%s]: retrieved invalid IFINDEX=%d", ifname, ifindex);
 		return;
 	}
 
 	if (!g_udev_device_get_sysfs_path (udev_device)) {
-		_LOGD ("(%s): udev-add: couldn't determine device path; ignoring...", ifname);
+		_LOGD ("udev-add[%s,%d]: couldn't determine device path; ignoring...", ifname, ifindex);
 		return;
 	}
 
+	_LOGT ("udev-add[%s,%d]: device added", ifname, ifindex);
 	cache_update_link_udev (platform, ifindex, udev_device);
 }
 
@@ -5909,6 +5962,7 @@ handle_udev_event (GUdevClient *client,
                    GUdevDevice *udev_device,
                    gpointer user_data)
 {
+	nm_auto_pop_netns NMPNetns *netns = NULL;
 	NMPlatform *platform = NM_PLATFORM (user_data);
 	const char *subsys;
 	const char *ifindex;
@@ -5916,6 +5970,9 @@ handle_udev_event (GUdevClient *client,
 
 	g_return_if_fail (action != NULL);
 
+	if (!nm_platform_netns_push (platform, &netns))
+		return;
+
 	/* A bit paranoid */
 	subsys = g_udev_device_get_subsystem (udev_device);
 	g_return_if_fail (!g_strcmp0 (subsys, "net"));
@@ -5938,15 +5995,22 @@ static void
 nm_linux_platform_init (NMLinuxPlatform *self)
 {
 	NMLinuxPlatformPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_LINUX_PLATFORM, NMLinuxPlatformPrivate);
+	gboolean use_udev;
+
+	use_udev =    nmp_netns_is_initial ()
+	           && access ("/sys", W_OK) == 0;
 
 	self->priv = priv;
 
 	priv->nlh_seq_next = 1;
-	priv->cache = nmp_cache_new ();
+	priv->cache = nmp_cache_new (use_udev);
 	priv->delayed_action.list_master_connected = g_ptr_array_new ();
 	priv->delayed_action.list_refresh_link = g_ptr_array_new ();
 	priv->delayed_action.list_wait_for_nl_response = g_array_new (FALSE, TRUE, sizeof (DelayedActionWaitForNlResponseData));
 	priv->wifi_data = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) wifi_utils_deinit);
+
+	if (use_udev)
+		priv->udev_client = g_udev_client_new ((const char *[]) { "net", NULL });
 }
 
 static void
@@ -5954,14 +6018,22 @@ constructed (GObject *_object)
 {
 	NMPlatform *platform = NM_PLATFORM (_object);
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	const char *udev_subsys[] = { "net", NULL };
 	int channel_flags;
 	gboolean status;
 	int nle;
-	GUdevEnumerator *enumerator;
-	GList *devices, *iter;
 
-	_LOGD ("create");
+	nm_assert (!platform->_netns || platform->_netns == nmp_netns_get_current ());
+
+	_LOGD ("create (%s netns, %s, %s udev)",
+	       !platform->_netns ? "ignore" : "use",
+	       !platform->_netns && nmp_netns_is_initial ()
+	           ? "initial netns"
+	           : (!nmp_netns_get_current ()
+	                ? "no netns support"
+	                : nm_sprintf_bufa (100, "in netns[%p]%s",
+	                                   nmp_netns_get_current (),
+	                                   nmp_netns_get_current () == nmp_netns_get_initial () ? "/main" : "")),
+	       nmp_cache_use_udev_get (priv->cache) ? "use" : "no");
 
 	priv->nlh = nl_socket_alloc ();
 	g_assert (priv->nlh);
@@ -5993,16 +6065,12 @@ constructed (GObject *_object)
 
 	channel_flags = g_io_channel_get_flags (priv->event_channel);
 	status = g_io_channel_set_flags (priv->event_channel,
-		channel_flags | G_IO_FLAG_NONBLOCK, NULL);
+	                                 channel_flags | G_IO_FLAG_NONBLOCK, NULL);
 	g_assert (status);
 	priv->event_id = g_io_add_watch (priv->event_channel,
 	                                (EVENT_CONDITIONS | ERROR_CONDITIONS | DISCONNECT_CONDITIONS),
 	                                 event_handler, platform);
 
-	/* Set up udev monitoring */
-	priv->udev_client = g_udev_client_new (udev_subsys);
-	g_signal_connect (priv->udev_client, "uevent", G_CALLBACK (handle_udev_event), platform);
-
 	/* complete construction of the GObject instance before populating the cache. */
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->constructed (_object);
 
@@ -6017,19 +6085,27 @@ constructed (GObject *_object)
 
 	delayed_action_handle_all (platform, FALSE);
 
-	/* And read initial device list */
-	enumerator = g_udev_enumerator_new (priv->udev_client);
-	g_udev_enumerator_add_match_subsystem (enumerator, "net");
+	/* Set up udev monitoring */
+	if (priv->udev_client) {
+		GUdevEnumerator *enumerator;
+		GList *devices, *iter;
+
+		g_signal_connect (priv->udev_client, "uevent", G_CALLBACK (handle_udev_event), platform);
+
+		/* And read initial device list */
+		enumerator = g_udev_enumerator_new (priv->udev_client);
+		g_udev_enumerator_add_match_subsystem (enumerator, "net");
 
-	g_udev_enumerator_add_match_is_initialized (enumerator);
+		g_udev_enumerator_add_match_is_initialized (enumerator);
 
-	devices = g_udev_enumerator_execute (enumerator);
-	for (iter = devices; iter; iter = g_list_next (iter)) {
-		udev_device_added (platform, G_UDEV_DEVICE (iter->data));
-		g_object_unref (G_UDEV_DEVICE (iter->data));
+		devices = g_udev_enumerator_execute (enumerator);
+		for (iter = devices; iter; iter = g_list_next (iter)) {
+			udev_device_added (platform, G_UDEV_DEVICE (iter->data));
+			g_object_unref (G_UDEV_DEVICE (iter->data));
+		}
+		g_list_free (devices);
+		g_object_unref (enumerator);
 	}
-	g_list_free (devices);
-	g_object_unref (enumerator);
 }
 
 static void
@@ -6048,6 +6124,11 @@ dispose (GObject *object)
 
 	g_clear_pointer (&priv->prune_candidates, g_hash_table_unref);
 
+	if (priv->udev_client) {
+		g_signal_handlers_disconnect_by_func (priv->udev_client, G_CALLBACK (handle_udev_event), platform);
+		g_clear_object (&priv->udev_client);
+	}
+
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->dispose (object);
 }
 
@@ -6067,7 +6148,6 @@ nm_linux_platform_finalize (GObject *object)
 	g_io_channel_unref (priv->event_channel);
 	nl_socket_free (priv->nlh);
 
-	g_object_unref (priv->udev_client);
 	g_hash_table_unref (priv->wifi_data);
 
 	if (priv->sysctl_get_prev_values) {
@@ -6109,6 +6189,8 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 
 	platform_class->link_refresh = link_refresh;
 
+	platform_class->link_set_netns = link_set_netns;
+
 	platform_class->link_set_up = link_set_up;
 	platform_class->link_set_down = link_set_down;
 	platform_class->link_set_arp = link_set_arp;
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 0f2656f2..d0c92a78 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -56,7 +56,7 @@ ethtool_get (const char *name, gpointer edata)
 	nm_assert (strlen (name) < IFNAMSIZ);
 
 	memset (&ifr, 0, sizeof (ifr));
-	strcpy (ifr.ifr_name, name);
+	nm_utils_ifname_cpy (ifr.ifr_name, name);
 	ifr.ifr_data = edata;
 
 	fd = socket (PF_INET, SOCK_DGRAM, 0);
@@ -344,7 +344,7 @@ nmp_utils_mii_supports_carrier_detect (const char *ifname)
 	}
 
 	memset (&ifr, 0, sizeof (struct ifreq));
-	strncpy (ifr.ifr_name, ifname, IFNAMSIZ);
+	nm_utils_ifname_cpy (ifr.ifr_name, ifname);
 
 	errno = 0;
 	if (ioctl (fd, SIOCGMIIPHY, &ifr) < 0) {
@@ -412,114 +412,18 @@ out:
 	return g_intern_string (driver);
 }
 
-/******************************************************************
- * utils
- ******************************************************************/
-
-#define IPV4LL_NETWORK (htonl (0xA9FE0000L))
-#define IPV4LL_NETMASK (htonl (0xFFFF0000L))
-
-gboolean
-nmp_utils_ip4_address_is_link_local (in_addr_t addr)
-{
-	return (addr & IPV4LL_NETMASK) == IPV4LL_NETWORK;
-}
-
-/**
- * Takes a pair @timestamp and @duration, and returns the remaining duration based
- * on the new timestamp @now.
- */
-guint32
-nmp_utils_lifetime_rebase_relative_time_on_now (guint32 timestamp,
-                                                guint32 duration,
-                                                guint32 now,
-                                                guint32 padding)
-{
-	gint64 t;
-
-	if (duration == NM_PLATFORM_LIFETIME_PERMANENT)
-		return NM_PLATFORM_LIFETIME_PERMANENT;
-
-	if (timestamp == 0) {
-		/* if the @timestamp is zero, assume it was just left unset and that the relative
-		 * @duration starts counting from @now. This is convenient to construct an address
-		 * and print it in nm_platform_ip4_address_to_string().
-		 *
-		 * In general it does not make sense to set the @duration without anchoring at
-		 * @timestamp because you don't know the absolute expiration time when looking
-		 * at the address at a later moment. */
-		timestamp = now;
-	}
-
-	/* For timestamp > now, just accept it and calculate the expected(?) result. */
-	t = (gint64) timestamp + (gint64) duration - (gint64) now;
-
-	/* Optional padding to avoid potential races. */
-	t += (gint64) padding;
-
-	if (t <= 0)
-		return 0;
-	if (t >= NM_PLATFORM_LIFETIME_PERMANENT)
-		return NM_PLATFORM_LIFETIME_PERMANENT - 1;
-	return t;
-}
-
-gboolean
-nmp_utils_lifetime_get (guint32 timestamp,
-                        guint32 lifetime,
-                        guint32 preferred,
-                        guint32 now,
-                        guint32 padding,
-                        guint32 *out_lifetime,
-                        guint32 *out_preferred)
-{
-	guint32 t_lifetime, t_preferred;
-
-	if (lifetime == 0) {
-		*out_lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
-		*out_preferred = NM_PLATFORM_LIFETIME_PERMANENT;
-
-		/* We treat lifetime==0 as permanent addresses to allow easy creation of such addresses
-		 * (without requiring to set the lifetime fields to NM_PLATFORM_LIFETIME_PERMANENT).
-		 * In that case we also expect that the other fields (timestamp and preferred) are left unset. */
-		g_return_val_if_fail (timestamp == 0 && preferred == 0, TRUE);
-	} else {
-		if (!now)
-			now = nm_utils_get_monotonic_timestamp_s ();
-		t_lifetime = nmp_utils_lifetime_rebase_relative_time_on_now (timestamp, lifetime, now, padding);
-		if (!t_lifetime) {
-			*out_lifetime = 0;
-			*out_preferred = 0;
-			return FALSE;
-		}
-		t_preferred = nmp_utils_lifetime_rebase_relative_time_on_now (timestamp, preferred, now, padding);
-
-		*out_lifetime = t_lifetime;
-		*out_preferred = MIN (t_preferred, t_lifetime);
-
-		/* Assert that non-permanent addresses have a (positive) @timestamp. nmp_utils_lifetime_rebase_relative_time_on_now()
-		 * treats addresses with timestamp 0 as *now*. Addresses passed to _address_get_lifetime() always
-		 * should have a valid @timestamp, otherwise on every re-sync, their lifetime will be extended anew.
-		 */
-		g_return_val_if_fail (   timestamp != 0
-		                      || (   lifetime  == NM_PLATFORM_LIFETIME_PERMANENT
-		                          && preferred == NM_PLATFORM_LIFETIME_PERMANENT), TRUE);
-		g_return_val_if_fail (t_preferred <= t_lifetime, TRUE);
-	}
-	return TRUE;
-}
-
 gboolean
 nmp_utils_device_exists (const char *name)
 {
 #define SYS_CLASS_NET "/sys/class/net/"
-	char sysdir[NM_STRLEN (SYS_CLASS_NET) + IFNAMSIZ] = SYS_CLASS_NET;
+	char sysdir[NM_STRLEN (SYS_CLASS_NET) + IFNAMSIZ];
 
 	if (   !name
 	    || strlen (name) >= IFNAMSIZ
 	    || !nm_utils_is_valid_path_component (name))
 		g_return_val_if_reached (FALSE);
 
-	strcpy (&sysdir[NM_STRLEN (SYS_CLASS_NET)], name);
+	memcpy (sysdir, SYS_CLASS_NET, NM_STRLEN (SYS_CLASS_NET));
+	nm_utils_ifname_cpy (&sysdir[NM_STRLEN (SYS_CLASS_NET)], name);
 	return g_file_test (sysdir, G_FILE_TEST_EXISTS);
 }
diff --git a/src/platform/nm-platform-utils.h b/src/platform/nm-platform-utils.h
index a9d29c04..976bd8db 100644
--- a/src/platform/nm-platform-utils.h
+++ b/src/platform/nm-platform-utils.h
@@ -52,21 +52,6 @@ gboolean nmp_utils_mii_supports_carrier_detect (const char *ifname);
 
 const char *nmp_utils_udev_get_driver (GUdevDevice *device);
 
-guint32 nmp_utils_lifetime_rebase_relative_time_on_now (guint32 timestamp,
-                                                        guint32 duration,
-                                                        guint32 now,
-                                                        guint32 padding);
-
-gboolean nmp_utils_lifetime_get (guint32 timestamp,
-                                 guint32 lifetime,
-                                 guint32 preferred,
-                                 guint32 now,
-                                 guint32 padding,
-                                 guint32 *out_lifetime,
-                                 guint32 *out_preferred);
-
 gboolean nmp_utils_device_exists (const char *name);
 
-gboolean nmp_utils_ip4_address_is_link_local (in_addr_t addr);
-
 #endif /* __NM_PLATFORM_UTILS_H__ */
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index e3126442..d5ee48d4 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -41,6 +41,7 @@
 #include "nm-enum-types.h"
 #include "nm-platform-utils.h"
 #include "nmp-object.h"
+#include "nmp-netns.h"
 
 /*****************************************************************************/
 
@@ -48,8 +49,6 @@ const NMIPAddr nm_ip_addr_zero = NMIPAddrInit;
 
 /*****************************************************************************/
 
-#define ADDRESS_LIFETIME_PADDING 5
-
 G_STATIC_ASSERT (sizeof ( ((NMPlatformLink *) NULL)->addr.data ) == NM_UTILS_HWADDR_LEN_MAX);
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Address, address));
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Address, address));
@@ -89,6 +88,7 @@ static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = { 0 };
 
 enum {
 	PROP_0,
+	PROP_NETNS_SUPPORT,
 	PROP_REGISTER_SINGLETON,
 	LAST_PROP,
 };
@@ -666,6 +666,32 @@ nm_platform_link_delete (NMPlatform *self, int ifindex)
 }
 
 /**
+ * nm_platform_link_set_netns:
+ * @self: platform instance
+ * @ifindex: Interface index
+ * @netns_fd: the file descriptor for the new netns.
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+nm_platform_link_set_netns (NMPlatform *self, int ifindex, int netns_fd)
+{
+	const NMPlatformLink *pllink;
+
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (netns_fd > 0, FALSE);
+
+	pllink = nm_platform_link_get (self, ifindex);
+	if (!pllink)
+		return FALSE;
+
+	_LOGD ("link: ifindex %d changing network namespace to %d", ifindex, netns_fd);
+	return klass->link_set_netns (self, ifindex, netns_fd);
+}
+
+/**
  * nm_platform_link_get_index:
  * @self: platform instance
  * @name: Interface name
@@ -1625,9 +1651,9 @@ link_option_path (NMPlatform *self, int master, const char *category, const char
 		return NULL;
 
 	return g_strdup_printf ("/sys/class/net/%s/%s/%s",
-	                        ASSERT_VALID_PATH_COMPONENT (name),
-	                        ASSERT_VALID_PATH_COMPONENT (category),
-	                        ASSERT_VALID_PATH_COMPONENT (option));
+	                        NM_ASSERT_VALID_PATH_COMPONENT (name),
+	                        NM_ASSERT_VALID_PATH_COMPONENT (category),
+	                        NM_ASSERT_VALID_PATH_COMPONENT (option));
 }
 
 static gboolean
@@ -1916,7 +1942,7 @@ nm_platform_link_infiniband_get_properties (NMPlatform *self,
 	/* Could not get the link information via netlink. To support older kernels,
 	 * fallback to reading sysfs. */
 
-	iface = ASSERT_VALID_PATH_COMPONENT (plink->name);
+	iface = NM_ASSERT_VALID_PATH_COMPONENT (plink->name);
 
 	/* Fall back to reading sysfs */
 	path = g_strdup_printf ("/sys/class/net/%s/mode", iface);
@@ -2128,6 +2154,10 @@ nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_pe
 
 	/* Pre-4.1 kernel did not expose the peer_ifindex as IFA_LINK. Lookup via ethtool. */
 	if (out_peer_ifindex) {
+		nm_auto_pop_netns NMPNetns *netns = NULL;
+
+		if (!nm_platform_netns_push (self, &netns))
+			return FALSE;
 		peer_ifindex = nmp_utils_ethtool_get_peer_ifindex (plink->name);
 		if (peer_ifindex <= 0)
 			return FALSE;
@@ -2368,6 +2398,52 @@ _to_string_dev (NMPlatform *self, int ifindex, char *buf, size_t size)
 	return buf;
 }
 
+#define TO_STRING_IFA_FLAGS_BUF_SIZE 256
+
+static const char *
+_to_string_ifa_flags (guint32 ifa_flags, char *buf, gsize size)
+{
+#define S_FLAGS_PREFIX " flags "
+	nm_assert (buf && size >= TO_STRING_IFA_FLAGS_BUF_SIZE && size > NM_STRLEN (S_FLAGS_PREFIX));
+
+	if (!ifa_flags)
+		buf[0] = '\0';
+	else {
+		nm_platform_addr_flags2str (ifa_flags, &buf[NM_STRLEN (S_FLAGS_PREFIX)], size - NM_STRLEN (S_FLAGS_PREFIX));
+		if (buf[NM_STRLEN (S_FLAGS_PREFIX)] == '\0')
+			buf[0] = '\0';
+		else
+			memcpy (buf, S_FLAGS_PREFIX, NM_STRLEN (S_FLAGS_PREFIX));
+	}
+	return buf;
+}
+
+/******************************************************************/
+
+gboolean
+nm_platform_ethtool_set_wake_on_lan (NMPlatform *self, const char *ifname, NMSettingWiredWakeOnLan wol, const char *wol_password)
+{
+	nm_auto_pop_netns NMPNetns *netns = NULL;
+	_CHECK_SELF (self, klass, FALSE);
+
+	if (!nm_platform_netns_push (self, &netns))
+		return FALSE;
+
+	return nmp_utils_ethtool_set_wake_on_lan (ifname, wol, wol_password);
+}
+
+gboolean
+nm_platform_ethtool_get_link_speed (NMPlatform *self, const char *ifname, guint32 *out_speed)
+{
+	nm_auto_pop_netns NMPNetns *netns = NULL;
+	_CHECK_SELF (self, klass, FALSE);
+
+	if (!nm_platform_netns_push (self, &netns))
+		return FALSE;
+
+	return nmp_utils_ethtool_get_link_speed (ifname, out_speed);
+}
+
 /******************************************************************/
 
 void
@@ -2415,6 +2491,7 @@ nm_platform_ip4_address_add (NMPlatform *self,
                              in_addr_t peer_address,
                              guint32 lifetime,
                              guint32 preferred,
+                             guint32 flags,
                              const char *label)
 {
 	_CHECK_SELF (self, klass, FALSE);
@@ -2435,12 +2512,13 @@ nm_platform_ip4_address_add (NMPlatform *self,
 		addr.timestamp = 0; /* set it at zero, which to_string will treat as *now* */
 		addr.lifetime = lifetime;
 		addr.preferred = preferred;
+		addr.n_ifa_flags = flags;
 		if (label)
 			g_strlcpy (addr.label, label, sizeof (addr.label));
 
 		_LOGD ("address: adding or updating IPv4 address: %s", nm_platform_ip4_address_to_string (&addr, NULL, 0));
 	}
-	return klass->ip4_address_add (self, ifindex, address, plen, peer_address, lifetime, preferred, label);
+	return klass->ip4_address_add (self, ifindex, address, plen, peer_address, lifetime, preferred, flags, label);
 }
 
 gboolean
@@ -2535,7 +2613,7 @@ nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr addr
 }
 
 static gboolean
-array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address *address, gint64 now, guint32 padding)
+array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address *address, gint32 now)
 {
 	guint len = addresses ? addresses->len : 0;
 	guint i;
@@ -2548,8 +2626,8 @@ array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address
 		    && ((candidate->peer_address ^ address->peer_address) & nm_utils_ip4_prefix_to_netmask (address->plen)) == 0) {
 			guint32 lifetime, preferred;
 
-			if (nmp_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
-			                            now, padding, &lifetime, &preferred))
+			if (nm_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
+			                           now, &lifetime, &preferred))
 				return TRUE;
 		}
 	}
@@ -2558,7 +2636,7 @@ array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address
 }
 
 static gboolean
-array_contains_ip6_address (const GArray *addresses, const NMPlatformIP6Address *address, gint64 now, guint32 padding)
+array_contains_ip6_address (const GArray *addresses, const NMPlatformIP6Address *address, gint32 now)
 {
 	guint len = addresses ? addresses->len : 0;
 	guint i;
@@ -2569,8 +2647,8 @@ array_contains_ip6_address (const GArray *addresses, const NMPlatformIP6Address
 		if (IN6_ARE_ADDR_EQUAL (&candidate->address, &address->address) && candidate->plen == address->plen) {
 			guint32 lifetime, preferred;
 
-			if (nmp_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
-			                            now, padding, &lifetime, &preferred))
+			if (nm_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
+			                           now, &lifetime, &preferred))
 				return TRUE;
 		}
 	}
@@ -2599,7 +2677,7 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
 {
 	GArray *addresses;
 	NMPlatformIP4Address *address;
-	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_s ();
 	int i;
 
 	_CHECK_SELF (self, klass, FALSE);
@@ -2609,7 +2687,7 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
 	for (i = 0; i < addresses->len; i++) {
 		address = &g_array_index (addresses, NMPlatformIP4Address, i);
 
-		if (!array_contains_ip4_address (known_addresses, address, now, ADDRESS_LIFETIME_PADDING))
+		if (!array_contains_ip4_address (known_addresses, address, now))
 			nm_platform_ip4_address_delete (self, ifindex, address->address, address->plen, address->peer_address);
 	}
 	g_array_free (addresses, TRUE);
@@ -2625,11 +2703,13 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
 		const NMPlatformIP4Address *known_address = &g_array_index (known_addresses, NMPlatformIP4Address, i);
 		guint32 lifetime, preferred;
 
-		if (!nmp_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
-		                             now, ADDRESS_LIFETIME_PADDING, &lifetime, &preferred))
+		if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
+		                            now, &lifetime, &preferred))
 			continue;
 
-		if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->plen, known_address->peer_address, lifetime, preferred, known_address->label))
+		if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->plen,
+		                                  known_address->peer_address, lifetime, preferred,
+		                                  0, known_address->label))
 			return FALSE;
 
 		if (out_added_addresses) {
@@ -2660,7 +2740,7 @@ nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GArray *known
 {
 	GArray *addresses;
 	NMPlatformIP6Address *address;
-	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_s ();
 	int i;
 
 	/* Delete unknown addresses */
@@ -2672,7 +2752,7 @@ nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GArray *known
 		if (keep_link_local && IN6_IS_ADDR_LINKLOCAL (&address->address))
 			continue;
 
-		if (!array_contains_ip6_address (known_addresses, address, now, ADDRESS_LIFETIME_PADDING))
+		if (!array_contains_ip6_address (known_addresses, address, now))
 			nm_platform_ip6_address_delete (self, ifindex, address->address, address->plen);
 	}
 	g_array_free (addresses, TRUE);
@@ -2685,8 +2765,8 @@ nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GArray *known
 		const NMPlatformIP6Address *known_address = &g_array_index (known_addresses, NMPlatformIP6Address, i);
 		guint32 lifetime, preferred;
 
-		if (!nmp_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
-		                             now, ADDRESS_LIFETIME_PADDING, &lifetime, &preferred))
+		if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
+		                            now, &lifetime, &preferred))
 			continue;
 
 		if (!nm_platform_ip6_address_add (self, ifindex, known_address->address,
@@ -2930,7 +3010,7 @@ _lifetime_to_string (guint32 timestamp, guint32 lifetime, gint32 now, char *buf,
 		return "forever";
 
 	g_snprintf (buf, buf_size, "%usec",
-	            nmp_utils_lifetime_rebase_relative_time_on_now (timestamp, lifetime, now, 0));
+	            nm_utils_lifetime_rebase_relative_time_on_now (timestamp, lifetime, now));
 	return buf;
 }
 
@@ -3025,7 +3105,7 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	            str_flags->str,
 	            link->mtu, master,
 	            link->arptype,
-	            str_if_set (str_link_type, "???"),
+	            str_link_type ?: "???",
 	            link->kind ? (g_strcmp0 (str_link_type, link->kind) ? "/" : "*") : "?",
 	            link->kind && g_strcmp0 (str_link_type, link->kind) ? link->kind : "",
 	            link->initialized ? " init" : " not-init",
@@ -3346,6 +3426,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 const char *
 nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *buf, gsize len)
 {
+	char s_flags[TO_STRING_IFA_FLAGS_BUF_SIZE];
 	char s_address[INET_ADDRSTRLEN];
 	char s_peer[INET_ADDRSTRLEN];
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
@@ -3383,10 +3464,11 @@ nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *bu
 	str_time_p = _lifetime_summary_to_string (now, address->timestamp, address->preferred, address->lifetime, str_time, sizeof (str_time));
 
 	g_snprintf (buf, len,
-	            "%s/%d lft %s pref %s%s%s%s%s src %s",
+	            "%s/%d lft %s pref %s%s%s%s%s%s src %s",
 	            s_address, address->plen, str_lft_p, str_pref_p, str_time_p,
 	            str_peer ? str_peer : "",
 	            str_dev,
+	            _to_string_ifa_flags (address->n_ifa_flags, s_flags, sizeof (s_flags)),
 	            str_label,
 	            source_to_string (address->source));
 	g_free (str_peer);
@@ -3457,8 +3539,7 @@ NM_UTILS_ENUM2STR_DEFINE (nm_platform_route_scope2str, int,
 const char *
 nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *buf, gsize len)
 {
-#define S_FLAGS_PREFIX " flags "
-	char s_flags[256];
+	char s_flags[TO_STRING_IFA_FLAGS_BUF_SIZE];
 	char s_address[INET6_ADDRSTRLEN];
 	char s_peer[INET6_ADDRSTRLEN];
 	char str_lft[30], str_pref[30], str_time[50];
@@ -3479,12 +3560,6 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *bu
 
 	_to_string_dev (NULL, address->ifindex, str_dev, sizeof (str_dev));
 
-	nm_platform_addr_flags2str (address->n_ifa_flags, &s_flags[NM_STRLEN (S_FLAGS_PREFIX)], sizeof (s_flags) - NM_STRLEN (S_FLAGS_PREFIX));
-	if (s_flags[NM_STRLEN (S_FLAGS_PREFIX)] == '\0')
-		s_flags[0] = '\0';
-	else
-		memcpy (s_flags, S_FLAGS_PREFIX, NM_STRLEN (S_FLAGS_PREFIX));
-
 	str_lft_p = _lifetime_to_string (address->timestamp,
 	                                 address->lifetime ? address->lifetime : NM_PLATFORM_LIFETIME_PERMANENT,
 	                                 now, str_lft, sizeof (str_lft)),
@@ -3500,7 +3575,7 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *bu
 	            s_address, address->plen, str_lft_p, str_pref_p, str_time_p,
 	            str_peer ? str_peer : "",
 	            str_dev,
-	            s_flags,
+	            _to_string_ifa_flags (address->n_ifa_flags, s_flags, sizeof (s_flags)),
 	            source_to_string (address->source));
 	g_free (str_peer);
 	return buf;
@@ -3825,6 +3900,7 @@ nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4A
 	_CMP_FIELD (a, b, timestamp);
 	_CMP_FIELD (a, b, lifetime);
 	_CMP_FIELD (a, b, preferred);
+	_CMP_FIELD (a, b, n_ifa_flags);
 	_CMP_FIELD_STR (a, b, label);
 	return 0;
 }
@@ -3980,6 +4056,31 @@ log_ip6_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatform
 
 /******************************************************************/
 
+NMPNetns *
+nm_platform_netns_get (NMPlatform *self)
+{
+	_CHECK_SELF (self, klass, NULL);
+
+	return self->_netns;
+}
+
+gboolean
+nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns)
+{
+	g_return_val_if_fail (NM_IS_PLATFORM (platform), FALSE);
+
+	if (   platform->_netns
+	    && !nmp_netns_push (platform->_netns)) {
+		NM_SET_OUT (netns, NULL);
+		return FALSE;
+	}
+
+	NM_SET_OUT (netns, platform->_netns);
+	return TRUE;
+}
+
+/******************************************************************/
+
 static gboolean
 _vtr_v4_route_add (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route, gint64 metric)
 {
@@ -4079,9 +4180,20 @@ static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
-	NMPlatformPrivate *priv =  NM_PLATFORM_GET_PRIVATE (object);
+	NMPlatform *self = NM_PLATFORM (object);
+	NMPlatformPrivate *priv =  NM_PLATFORM_GET_PRIVATE (self);
 
 	switch (prop_id) {
+	case PROP_NETNS_SUPPORT:
+		/* construct-only */
+		if (g_value_get_boolean (value)) {
+			NMPNetns *netns;
+
+			netns = nmp_netns_get_current ();
+			if (netns)
+				self->_netns = g_object_ref (netns);
+		}
+		break;
 	case PROP_REGISTER_SINGLETON:
 		/* construct-only */
 		priv->register_singleton = g_value_get_boolean (value);
@@ -4110,6 +4222,14 @@ nm_platform_init (NMPlatform *object)
 }
 
 static void
+finalize (GObject *object)
+{
+	NMPlatform *self = NM_PLATFORM (object);
+
+	g_clear_object (&self->_netns);
+}
+
+static void
 nm_platform_class_init (NMPlatformClass *platform_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (platform_class);
@@ -4118,10 +4238,19 @@ nm_platform_class_init (NMPlatformClass *platform_class)
 
 	object_class->set_property = set_property;
 	object_class->constructed = constructed;
+	object_class->finalize = finalize;
 
 	platform_class->wifi_set_powersave = wifi_set_powersave;
 
 	g_object_class_install_property
+	 (object_class, PROP_NETNS_SUPPORT,
+	     g_param_spec_boolean (NM_PLATFORM_NETNS_SUPPORT, "", "",
+	                           FALSE,
+	                           G_PARAM_WRITABLE |
+	                           G_PARAM_CONSTRUCT_ONLY |
+	                           G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property
 	 (object_class, PROP_REGISTER_SINGLETON,
 	     g_param_spec_boolean (NM_PLATFORM_REGISTER_SINGLETON, "", "",
 	                           FALSE,
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index b94c440a..8c97f766 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -31,6 +31,7 @@
 
 #include "nm-core-utils.h"
 #include "nm-setting-vlan.h"
+#include "nm-setting-wired.h"
 
 #define NM_TYPE_PLATFORM            (nm_platform_get_type ())
 #define NM_PLATFORM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_PLATFORM, NMPlatform))
@@ -41,6 +42,7 @@
 
 /******************************************************************/
 
+#define NM_PLATFORM_NETNS_SUPPORT      "netns-support"
 #define NM_PLATFORM_REGISTER_SINGLETON "register-singleton"
 
 /******************************************************************/
@@ -177,8 +179,6 @@ typedef enum {
 	NM_PLATFORM_SIGNAL_REMOVED,
 } NMPlatformSignalChangeType;
 
-#define NM_PLATFORM_LIFETIME_PERMANENT G_MAXUINT32
-
 typedef enum { /*< skip >*/
 	NM_PLATFORM_GET_ROUTE_FLAGS_NONE                            = 0,
 
@@ -221,6 +221,11 @@ typedef struct {
 	guint32 timestamp; \
 	guint32 lifetime;   /* seconds since timestamp */ \
 	guint32 preferred;  /* seconds since timestamp */ \
+	\
+	/* ifa_flags in 'struct ifaddrmsg' from <linux/if_addr.h>, extended to 32 bit by
+	 * IFA_FLAGS attribute. */ \
+	guint32 n_ifa_flags; \
+	\
 	int plen; \
 	;
 
@@ -269,7 +274,6 @@ struct _NMPlatformIP6Address {
 	__NMPlatformIPAddress_COMMON;
 	struct in6_addr address;
 	struct in6_addr peer_address;
-	guint32 n_ifa_flags; /* ifa_flags from <linux/if_addr.h>, field type "unsigned int" is as used in rtnl_addr_get_flags. */
 };
 
 typedef union {
@@ -459,6 +463,8 @@ typedef struct {
 
 struct _NMPlatform {
 	GObject parent;
+
+	NMPNetns *_netns;
 };
 
 typedef struct {
@@ -485,6 +491,9 @@ typedef struct {
 	gboolean (*link_get_unmanaged) (NMPlatform *, int ifindex, gboolean *unmanaged);
 
 	gboolean (*link_refresh) (NMPlatform *, int ifindex);
+
+	gboolean (*link_set_netns) (NMPlatform *, int ifindex, int netns_fd);
+
 	void (*process_events) (NMPlatform *self);
 
 	gboolean (*link_set_up) (NMPlatform *, int ifindex, gboolean *out_no_firmware);
@@ -586,6 +595,7 @@ typedef struct {
 	                             in_addr_t peer_address,
 	                             guint32 lifetime,
 	                             guint32 preferred_lft,
+	                             guint32 flags,
 	                             const char *label);
 	gboolean (*ip6_address_add) (NMPlatform *,
 	                             int ifindex,
@@ -665,6 +675,9 @@ _nm_platform_uint8_inv (guint8 scope)
 	return (guint8) ~scope;
 }
 
+NMPNetns *nm_platform_netns_get (NMPlatform *self);
+gboolean nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns);
+
 const char *nm_link_type_to_string (NMLinkType link_type);
 
 const char *_nm_platform_error_to_string (NMPlatformError error);
@@ -688,6 +701,8 @@ NMPlatformError nm_platform_link_bond_add (NMPlatform *self, const char *name, c
 NMPlatformError nm_platform_link_team_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
 gboolean nm_platform_link_delete (NMPlatform *self, int ifindex);
 
+gboolean nm_platform_link_set_netns (NMPlatform *self, int ifindex, int netns_fd);
+
 /* convienience methods to lookup the link and access fields of NMPlatformLink. */
 int nm_platform_link_get_ifindex (NMPlatform *self, const char *name);
 const char *nm_platform_link_get_name (NMPlatform *self, int ifindex);
@@ -853,6 +868,7 @@ gboolean nm_platform_ip4_address_add (NMPlatform *self,
                                       in_addr_t peer_address,
                                       guint32 lifetime,
                                       guint32 preferred_lft,
+                                      guint32 flags,
                                       const char *label);
 gboolean nm_platform_ip6_address_add (NMPlatform *self,
                                       int ifindex,
@@ -925,4 +941,7 @@ const char *nm_platform_route_scope2str (int scope, char *buf, gsize len);
 
 int nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPlatformIPAddress *b);
 
+gboolean nm_platform_ethtool_set_wake_on_lan (NMPlatform *self, const char *ifname, NMSettingWiredWakeOnLan wol, const char *wol_password);
+gboolean nm_platform_ethtool_get_link_speed (NMPlatform *self, const char *ifname, guint32 *out_speed);
+
 #endif /* __NETWORKMANAGER_PLATFORM_H__ */
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
new file mode 100644
index 00000000..26295855
--- /dev/null
+++ b/src/platform/nmp-netns.c
@@ -0,0 +1,732 @@
+/* -*- 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) 2016 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+#include "nmp-netns.h"
+
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "NetworkManagerUtils.h"
+
+#define PROC_SELF_NS_MNT "/proc/self/ns/mnt"
+#define PROC_SELF_NS_NET "/proc/self/ns/net"
+
+#define _CLONE_NS_ALL    ((int) (CLONE_NEWNS | CLONE_NEWNET))
+#define _CLONE_NS_ALL_V          CLONE_NEWNS , CLONE_NEWNET
+
+NM_UTILS_FLAGS2STR_DEFINE_STATIC (_clone_ns_to_str, int,
+	NM_UTILS_FLAGS2STR (CLONE_NEWNS,  "mnt"),
+	NM_UTILS_FLAGS2STR (CLONE_NEWNET, "net"),
+);
+
+static const char *
+__ns_types_to_str (int ns_types, int ns_types_already_set, char *buf, gsize len)
+{
+	const char *b = buf;
+	char bb[200];
+
+	nm_utils_strbuf_append_c (&buf, &len, '[');
+	if (ns_types & ~ns_types_already_set) {
+		nm_utils_strbuf_append_str (&buf, &len,
+		                            _clone_ns_to_str (ns_types & ~ns_types_already_set, bb, sizeof (bb)));
+	}
+	if (ns_types & ns_types_already_set) {
+		if (ns_types & ~ns_types_already_set)
+			nm_utils_strbuf_append_c (&buf, &len, '/');
+		nm_utils_strbuf_append_str (&buf, &len,
+		                            _clone_ns_to_str (ns_types & ns_types_already_set, bb, sizeof (bb)));
+	}
+	nm_utils_strbuf_append_c (&buf, &len, ']');
+	return b;
+}
+#define _ns_types_to_str(ns_types, ns_types_already_set, buf) \
+	__ns_types_to_str (ns_types, ns_types_already_set, buf, sizeof (buf))
+
+/*********************************************************************************************/
+
+#define _NMLOG_DOMAIN        LOGD_PLATFORM
+#define _NMLOG_PREFIX_NAME   "netns"
+#define _NMLOG(level, netns, ...) \
+    G_STMT_START { \
+        NMLogLevel _level = (level); \
+        \
+        if (nm_logging_enabled (_level, _NMLOG_DOMAIN)) { \
+            NMPNetns *_netns = (netns); \
+            char _sbuf[20]; \
+            \
+            _nm_log (_level, _NMLOG_DOMAIN, 0, \
+                     "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME, \
+                     (_netns ? nm_sprintf_buf (_sbuf, "[%p]", _netns) : "") \
+                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*********************************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+	PROP_FD_NET,
+	PROP_FD_MNT,
+);
+
+typedef struct _NMPNetnsPrivate NMPNetnsPrivate;
+
+struct _NMPNetnsPrivate {
+	int fd_net;
+	int fd_mnt;
+};
+
+typedef struct {
+	NMPNetns *netns;
+	int count;
+	int ns_types;
+} NetnsInfo;
+
+static void _stack_push (NMPNetns *netns, int ns_types);
+static NMPNetns *_netns_new (GError **error);
+
+/*********************************************************************************************/
+
+static GArray *netns_stack = NULL;
+
+static void
+_stack_ensure_init_impl (void)
+{
+	NMPNetns *netns;
+	GError *error = NULL;
+
+	nm_assert (!netns_stack);
+
+	netns_stack = g_array_new (FALSE, FALSE, sizeof (NetnsInfo));
+
+	/* at the bottom of the stack we must try to create a netns instance
+	 * that we never pop. It's the base to which we need to return. */
+
+	netns = _netns_new (&error);
+
+	if (!netns) {
+		/* don't know how to recover from this error. Netns are not supported. */
+		_LOGE (NULL, "failed to create initial netns: %s", error->message);
+		g_clear_error (&error);
+		return;
+	}
+
+	_stack_push (netns, _CLONE_NS_ALL);
+
+	/* we leak this instance inside netns_stack. It cannot be popped. */
+	g_object_unref (netns);
+}
+#define _stack_ensure_init() \
+	G_STMT_START { \
+		if (G_UNLIKELY (!netns_stack)) { \
+			_stack_ensure_init_impl (); \
+		} \
+	} G_STMT_END
+
+static NMPNetns *
+_stack_current_netns (int ns_types)
+{
+	guint j;
+
+	nm_assert (netns_stack && netns_stack->len > 0);
+
+	/* we search the stack top-down to find the netns that has
+	 * all @ns_types set. */
+	for (j = netns_stack->len; ns_types && j >= 1; ) {
+		NetnsInfo *info;
+
+		info = &g_array_index (netns_stack, NetnsInfo, --j);
+
+		if (NM_FLAGS_ALL (info->ns_types, ns_types))
+			return info->netns;
+	}
+
+	g_return_val_if_reached (NULL);
+}
+
+static int
+_stack_current_ns_types (NMPNetns *netns, int ns_types)
+{
+	const int ns_types_check[] = { _CLONE_NS_ALL_V };
+	guint i, j;
+	int res = 0;
+
+	nm_assert (netns);
+	nm_assert (netns_stack && netns_stack->len > 0);
+
+	/* we search the stack top-down to check which of @ns_types
+	 * are already set to @netns. */
+	for (j = netns_stack->len; ns_types && j >= 1; ) {
+		NetnsInfo *info;
+
+		info = &g_array_index (netns_stack, NetnsInfo, --j);
+		if (info->netns != netns) {
+			ns_types = NM_FLAGS_UNSET (ns_types, info->ns_types);
+			continue;
+		}
+
+		for (i = 0; i < G_N_ELEMENTS (ns_types_check); i++) {
+			if (   NM_FLAGS_HAS (ns_types, ns_types_check[i])
+			    && NM_FLAGS_HAS (info->ns_types, ns_types_check[i])) {
+				res = NM_FLAGS_SET (res, ns_types_check[i]);
+				ns_types = NM_FLAGS_UNSET (ns_types, ns_types_check[i]);
+			}
+		}
+	}
+
+	return res;
+}
+
+static NetnsInfo *
+_stack_peek (void)
+{
+	nm_assert (netns_stack);
+
+	if (netns_stack->len > 0)
+		return &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1));
+	return NULL;
+}
+
+static NetnsInfo *
+_stack_bottom (void)
+{
+	nm_assert (netns_stack);
+
+	if (netns_stack->len > 0)
+		return &g_array_index (netns_stack, NetnsInfo, 0);
+	return NULL;
+}
+
+static void
+_stack_push (NMPNetns *netns, int ns_types)
+{
+	NetnsInfo *info;
+
+	nm_assert (netns_stack);
+	nm_assert (NMP_IS_NETNS (netns));
+	nm_assert (NM_FLAGS_ANY (ns_types, _CLONE_NS_ALL));
+	nm_assert (!NM_FLAGS_ANY (ns_types, ~_CLONE_NS_ALL));
+
+	g_array_set_size (netns_stack, netns_stack->len + 1);
+
+	info = &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1));
+	info->netns = g_object_ref (netns);
+	info->ns_types = ns_types;
+	info->count = 1;
+}
+
+static void
+_stack_pop (void)
+{
+	NetnsInfo *info;
+
+	nm_assert (netns_stack);
+	nm_assert (netns_stack->len > 1);
+
+	info = &g_array_index (netns_stack, NetnsInfo, (netns_stack->len - 1));
+
+	nm_assert (NMP_IS_NETNS (info->netns));
+	nm_assert (info->count == 1);
+
+	g_object_unref (info->netns);
+
+	g_array_set_size (netns_stack, netns_stack->len - 1);
+}
+
+static guint
+_stack_size (void)
+{
+	nm_assert (netns_stack);
+
+	return netns_stack->len;
+}
+
+/*********************************************************************************************/
+
+G_DEFINE_TYPE (NMPNetns, nmp_netns, G_TYPE_OBJECT);
+
+#define NMP_NETNS_GET_PRIVATE(o) ((o)->priv)
+
+/*********************************************************************************************/
+
+static NMPNetns *
+_netns_new (GError **error)
+{
+	NMPNetns *self;
+	int fd_net, fd_mnt;
+	int errsv;
+
+	fd_net = open (PROC_SELF_NS_NET, O_RDONLY);
+	if (fd_net == -1) {
+		errsv = errno;
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             "Failed opening netns: %s",
+		             g_strerror (errsv));
+		return NULL;
+	}
+
+	fd_mnt = open (PROC_SELF_NS_MNT, O_RDONLY);
+	if (fd_mnt == -1) {
+		errsv = errno;
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             "Failed opening mntns: %s",
+		             g_strerror (errsv));
+		close (fd_net);
+		return NULL;
+	}
+
+	self = g_object_new (NMP_TYPE_NETNS,
+	                     NMP_NETNS_FD_NET, fd_net,
+	                     NMP_NETNS_FD_MNT, fd_mnt,
+	                     NULL);
+
+	_LOGD (self, "new netns (net:%d, mnt:%d)", fd_net, fd_mnt);
+
+	return self;
+}
+
+static int
+_setns (NMPNetns *self, int type)
+{
+	char buf[100];
+	int fd;
+
+	nm_assert (NM_IN_SET (type, _CLONE_NS_ALL_V));
+
+	fd = (type == CLONE_NEWNET) ? self->priv->fd_net : self->priv->fd_mnt;
+
+	_LOGt (self, "set netns(%s, %d)", _ns_types_to_str (type, 0, buf), fd);
+
+	return setns (fd, type);
+}
+
+static gboolean
+_netns_switch_push (NMPNetns *self, int ns_types)
+{
+	int errsv;
+
+	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
+	    && !_stack_current_ns_types (self, CLONE_NEWNET)
+	    && _setns (self, CLONE_NEWNET) != 0) {
+		errsv = errno;
+		_LOGE (self, "failed to switch netns: %s", g_strerror (errsv));
+		return FALSE;
+	}
+	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNS)
+	    && !_stack_current_ns_types (self, CLONE_NEWNS)
+	    && _setns (self, CLONE_NEWNS) != 0) {
+		errsv = errno;
+		_LOGE (self, "failed to switch mntns: %s", g_strerror (errsv));
+
+		/* try to fix the mess by returning to the previous netns. */
+		if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
+	        && !_stack_current_ns_types (self, CLONE_NEWNET)) {
+			self = _stack_current_netns (CLONE_NEWNET);
+			if (   self
+			    && _setns (self, CLONE_NEWNET) != 0) {
+				errsv = errno;
+				_LOGE (self, "failed to restore netns: %s", g_strerror (errsv));
+			}
+		}
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+_netns_switch_pop (NMPNetns *self, int ns_types)
+{
+	int errsv;
+	NMPNetns *current;
+	int success = TRUE;
+
+	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNET)
+	    && (!self || !_stack_current_ns_types (self, CLONE_NEWNET))) {
+		current = _stack_current_netns (CLONE_NEWNET);
+		if (!current) {
+			g_warn_if_reached ();
+			success = FALSE;
+		} else if (_setns (current, CLONE_NEWNET) != 0) {
+			errsv = errno;
+			_LOGE (self, "failed to switch netns: %s", g_strerror (errsv));
+			success = FALSE;
+		}
+	}
+	if (   NM_FLAGS_HAS (ns_types, CLONE_NEWNS)
+	    && (!self || !_stack_current_ns_types (self, CLONE_NEWNS))) {
+		current = _stack_current_netns (CLONE_NEWNS);
+		if (!current) {
+			g_warn_if_reached ();
+			success = FALSE;
+		} else if (_setns (current, CLONE_NEWNS) != 0) {
+			errsv = errno;
+			_LOGE (self, "failed to switch mntns: %s", g_strerror (errsv));
+			success = FALSE;
+		}
+	}
+
+	return success;
+}
+
+/*********************************************************************************************/
+
+int
+nmp_netns_get_fd_net (NMPNetns *self)
+{
+	g_return_val_if_fail (NMP_IS_NETNS (self), 0);
+
+	return self->priv->fd_net;
+}
+
+int
+nmp_netns_get_fd_mnt (NMPNetns *self)
+{
+	g_return_val_if_fail (NMP_IS_NETNS (self), 0);
+
+	return self->priv->fd_mnt;
+}
+
+/*********************************************************************************************/
+
+static gboolean
+_nmp_netns_push_type (NMPNetns *self, int ns_types)
+{
+	NetnsInfo *info;
+	char sbuf[100];
+
+	_stack_ensure_init ();
+
+	info = _stack_peek ();
+	g_return_val_if_fail (info, FALSE);
+
+	if (info->netns == self && info->ns_types == ns_types) {
+		info->count++;
+		_LOGt (self, "push#%u* %s (increase count to %d)",
+		       _stack_size () - 1,
+		       _ns_types_to_str (ns_types, ns_types, sbuf), info->count);
+		return TRUE;
+	}
+
+	_LOGD (self, "push#%u %s",
+	       _stack_size (),
+	       _ns_types_to_str (ns_types,
+	                         _stack_current_ns_types (self, ns_types),
+	                         sbuf));
+
+	if (!_netns_switch_push (self, ns_types))
+		return FALSE;
+
+	_stack_push (self, ns_types);
+	return TRUE;
+}
+
+gboolean
+nmp_netns_push (NMPNetns *self)
+{
+	g_return_val_if_fail (NMP_IS_NETNS (self), FALSE);
+
+	return _nmp_netns_push_type (self, _CLONE_NS_ALL);
+}
+
+gboolean
+nmp_netns_push_type (NMPNetns *self, int ns_types)
+{
+	g_return_val_if_fail (NMP_IS_NETNS (self), FALSE);
+	g_return_val_if_fail (!NM_FLAGS_ANY (ns_types, ~_CLONE_NS_ALL), FALSE);
+
+	return _nmp_netns_push_type (self, ns_types == 0 ? _CLONE_NS_ALL : ns_types);
+}
+
+NMPNetns *
+nmp_netns_new (void)
+{
+	NMPNetns *self;
+	int errsv;
+	GError *error = NULL;
+
+	_stack_ensure_init ();
+
+	if (!_stack_peek ()) {
+		/* there are no netns instances. We cannot create a new one
+		 * (because after unshare we couldn't return to the original one). */
+		return NULL;
+	}
+
+	if (unshare (_CLONE_NS_ALL) != 0) {
+		errsv = errno;
+		_LOGE (NULL, "failed to create new net and mnt namespace: %s", g_strerror (errsv));
+		return NULL;
+	}
+
+	if (mount ("", "/", "none", MS_SLAVE | MS_REC, NULL) != 0) {
+		errsv = errno;
+		_LOGE (NULL, "failed mount --make-rslave: %s", g_strerror (errsv));
+		goto err_out;
+	}
+
+	if (umount2 ("/sys", MNT_DETACH) != 0) {
+		errsv = errno;
+		_LOGE (NULL, "failed umount /sys: %s", g_strerror (errsv));
+		goto err_out;
+	}
+
+	if (mount ("sysfs", "/sys", "sysfs", 0, NULL) != 0) {
+		errsv = errno;
+		_LOGE (NULL, "failed mount /sys: %s", g_strerror (errsv));
+		goto err_out;
+	}
+
+	self = _netns_new (&error);
+	if (!self) {
+		_LOGE (NULL, "failed to create netns after unshare: %s", error->message);
+		g_clear_error (&error);
+		goto err_out;
+	}
+
+	_stack_push (self, _CLONE_NS_ALL);
+
+	return self;
+err_out:
+	_netns_switch_pop (NULL, _CLONE_NS_ALL);
+	return NULL;
+}
+
+gboolean
+nmp_netns_pop (NMPNetns *self)
+{
+	NetnsInfo *info;
+	int ns_types;
+
+	g_return_val_if_fail (NMP_IS_NETNS (self), FALSE);
+
+	_stack_ensure_init ();
+
+	info = _stack_peek ();
+
+	g_return_val_if_fail (info, FALSE);
+	g_return_val_if_fail (info->netns == self, FALSE);
+
+	if (info->count > 1) {
+		info->count--;
+		_LOGt (self, "pop#%u* (decrease count to %d)",
+		       _stack_size () - 1, info->count);
+		return TRUE;
+	}
+	g_return_val_if_fail (info->count == 1, FALSE);
+
+	/* cannot pop the original netns. */
+	g_return_val_if_fail (_stack_size () > 1, FALSE);
+
+	_LOGD (self, "pop#%u", _stack_size () - 1);
+
+	ns_types = info->ns_types;
+
+	_stack_pop ();
+
+	return _netns_switch_pop (self, ns_types);
+}
+
+NMPNetns *
+nmp_netns_get_current (void)
+{
+	NetnsInfo *info;
+
+	_stack_ensure_init ();
+
+	info = _stack_peek ();
+	return info ? info->netns : NULL;
+}
+
+NMPNetns *
+nmp_netns_get_initial (void)
+{
+	NetnsInfo *info;
+
+	_stack_ensure_init ();
+
+	info = _stack_bottom ();
+	return info ? info->netns : NULL;
+}
+
+gboolean
+nmp_netns_is_initial (void)
+{
+	if (G_UNLIKELY (!netns_stack))
+		return TRUE;
+
+	return nmp_netns_get_current () == nmp_netns_get_initial ();
+}
+
+/*********************************************************************************************/
+
+gboolean
+nmp_netns_bind_to_path (NMPNetns *self, const char *filename, int *out_fd)
+{
+	gs_free char *dirname = NULL;
+	int errsv;
+	int fd;
+	nm_auto_pop_netns NMPNetns *netns_pop = NULL;
+
+	g_return_val_if_fail (NMP_IS_NETNS (self), FALSE);
+	g_return_val_if_fail (filename && filename[0] == '/', FALSE);
+
+	if (!nmp_netns_push_type (self, CLONE_NEWNET))
+		return FALSE;
+	netns_pop = self;
+
+	dirname = g_path_get_dirname (filename);
+	if (mkdir (dirname, 0) != 0) {
+		errsv = errno;
+		if (errsv != EEXIST) {
+			_LOGE (self, "bind: failed to create directory %s: %s",
+			       dirname, g_strerror (errsv));
+			return FALSE;
+		}
+	}
+
+	if ((fd = creat (filename, S_IRUSR | S_IRGRP | S_IROTH)) == -1) {
+		errsv = errno;
+		_LOGE (self, "bind: failed to create %s: %s",
+		       filename, g_strerror (errsv));
+		return FALSE;
+	}
+	close (fd);
+
+	if (mount (PROC_SELF_NS_NET, filename, "none", MS_BIND, NULL) != 0) {
+		errsv = errno;
+		_LOGE (self, "bind: failed to mount %s to %s: %s",
+		       PROC_SELF_NS_NET, filename, g_strerror (errsv));
+		unlink (filename);
+		return FALSE;
+	}
+
+	if (out_fd) {
+		if ((fd = open (filename, O_RDONLY)) == -1) {
+			errsv = errno;
+			_LOGE (self, "bind: failed to open %s: %s", filename, g_strerror (errsv));
+			umount2 (filename, MNT_DETACH);
+			unlink (filename);
+			return FALSE;
+		}
+		*out_fd = fd;
+	}
+
+	return TRUE;
+}
+
+gboolean
+nmp_netns_bind_to_path_destroy (NMPNetns *self, const char *filename)
+{
+	int errsv;
+
+	g_return_val_if_fail (NMP_IS_NETNS (self), FALSE);
+	g_return_val_if_fail (filename && filename[0] == '/', FALSE);
+
+	if (umount2 (filename, MNT_DETACH) != 0) {
+		errsv = errno;
+		_LOGE (self, "bind: failed to unmount2 %s: %s", filename, g_strerror (errsv));
+		return FALSE;
+	}
+	if (unlink (filename) != 0) {
+		errsv = errno;
+		_LOGE (self, "bind: failed to unlink %s: %s", filename, g_strerror (errsv));
+		return FALSE;
+	}
+	return TRUE;
+}
+
+/******************************************************************************/
+
+static void
+set_property (GObject *object, guint prop_id,
+              const GValue *value, GParamSpec *pspec)
+{
+	NMPNetns *self = NMP_NETNS (object);
+
+	switch (prop_id) {
+	case PROP_FD_NET:
+		/* construct only */
+		self->priv->fd_net = g_value_get_int (value);
+		g_return_if_fail (self->priv->fd_net > 0);
+		break;
+	case PROP_FD_MNT:
+		/* construct only */
+		self->priv->fd_mnt = g_value_get_int (value);
+		g_return_if_fail (self->priv->fd_mnt > 0);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+nmp_netns_init (NMPNetns *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NMP_TYPE_NETNS, NMPNetnsPrivate);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMPNetns *self = NMP_NETNS (object);
+
+	if (self->priv->fd_net > 0) {
+		close (self->priv->fd_net);
+		self->priv->fd_net = 0;
+	}
+
+	if (self->priv->fd_mnt > 0) {
+		close (self->priv->fd_mnt);
+		self->priv->fd_mnt = 0;
+	}
+
+	G_OBJECT_CLASS (nmp_netns_parent_class)->dispose (object);
+}
+
+static void
+nmp_netns_class_init (NMPNetnsClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (NMPNetnsPrivate));
+
+	object_class->set_property = set_property;
+	object_class->dispose = dispose;
+
+	obj_properties[PROP_FD_NET]
+	    = g_param_spec_int (NMP_NETNS_FD_NET, "", "",
+	                        0, G_MAXINT, 0,
+	                        G_PARAM_WRITABLE |
+	                        G_PARAM_CONSTRUCT_ONLY |
+	                        G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_FD_MNT]
+	    = g_param_spec_int (NMP_NETNS_FD_MNT, "", "",
+	                        0, G_MAXINT, 0,
+	                        G_PARAM_WRITABLE |
+	                        G_PARAM_CONSTRUCT_ONLY |
+	                        G_PARAM_STATIC_STRINGS);
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/src/platform/nmp-netns.h b/src/platform/nmp-netns.h
new file mode 100644
index 00000000..4eac07ac
--- /dev/null
+++ b/src/platform/nmp-netns.h
@@ -0,0 +1,74 @@
+/* -*- 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) 2016 Red Hat, Inc.
+ */
+
+#ifndef __NMP_NETNS_UTILS_H__
+#define __NMP_NETNS_UTILS_H__
+
+/*****************************************************************************/
+
+#define NMP_TYPE_NETNS            (nmp_netns_get_type ())
+#define NMP_NETNS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMP_TYPE_NETNS, NMPNetns))
+#define NMP_NETNS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMP_TYPE_NETNS, NMPNetnsClass))
+#define NMP_IS_NETNS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMP_TYPE_NETNS))
+#define NMP_IS_NETNS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMP_TYPE_NETNS))
+#define NMP_NETNS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMP_TYPE_NETNS, NMPNetnsClass))
+
+#define NMP_NETNS_FD_NET          "fd-net"
+#define NMP_NETNS_FD_MNT          "fd-mnt"
+
+struct _NMPNetnsPrivate;
+
+struct _NMPNetns {
+	GObject parent;
+	struct _NMPNetnsPrivate *priv;
+};
+
+typedef struct {
+	GObjectClass parent;
+} NMPNetnsClass;
+
+GType nmp_netns_get_type (void);
+
+NMPNetns *nmp_netns_new (void);
+
+gboolean nmp_netns_push (NMPNetns *self);
+gboolean nmp_netns_push_type (NMPNetns *self, int ns_types);
+gboolean nmp_netns_pop (NMPNetns *self);
+
+NMPNetns *nmp_netns_get_current (void);
+NMPNetns *nmp_netns_get_initial (void);
+gboolean nmp_netns_is_initial (void);
+
+int nmp_netns_get_fd_net (NMPNetns *self);
+int nmp_netns_get_fd_mnt (NMPNetns *self);
+
+static inline void
+_nm_auto_pop_netns (NMPNetns **p)
+{
+	if (*p)
+		nmp_netns_pop (*p);
+}
+
+#define nm_auto_pop_netns __attribute__((cleanup(_nm_auto_pop_netns)))
+
+gboolean nmp_netns_bind_to_path (NMPNetns *self, const char *filename, int *out_fd);
+gboolean nmp_netns_bind_to_path_destroy (NMPNetns *self, const char *filename);
+
+#endif /* __NMP_NETNS_UTILS_H__ */
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index b7b04490..adeb3aab 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -174,10 +174,6 @@ _nmp_object_fixup_link_udev_fields (NMPObject *obj, gboolean use_udev)
 			 * nmp_cache_use_udev_get(). It is on purpose not to test
 			 * for a writable /sys on every call. A minor reason for that is
 			 * performance, but the real reason is reproducibility.
-			 *
-			 * If you want to support changing of whether udev is enabled,
-			 * reset the value via nmp_cache_use_udev_set() carefully -- and
-			 * possibly update the links in the cache accordingly.
 			 * */
 			initialized = TRUE;
 		}
@@ -1192,12 +1188,6 @@ _vt_cmd_obj_init_cache_id_ipx_route (const NMPObject *obj, NMPCacheIdType id_typ
 /******************************************************************/
 
 gboolean
-nmp_cache_use_udev_detect ()
-{
-	return access ("/sys", W_OK) == 0;
-}
-
-gboolean
 nmp_cache_use_udev_get (const NMPCache *cache)
 {
 	g_return_val_if_fail (cache, TRUE);
@@ -1205,19 +1195,6 @@ nmp_cache_use_udev_get (const NMPCache *cache)
 	return cache->use_udev;
 }
 
-gboolean
-nmp_cache_use_udev_set (NMPCache *cache, gboolean use_udev)
-{
-	g_return_val_if_fail (cache, FALSE);
-
-	use_udev = !!use_udev;
-	if (use_udev == cache->use_udev)
-		return FALSE;
-
-	cache->use_udev = use_udev;
-	return TRUE;
-}
-
 /******************************************************************/
 
 /**
@@ -1858,7 +1835,7 @@ nmp_cache_update_link_master_connected (NMPCache *cache, int ifindex, NMPObject
 /******************************************************************/
 
 NMPCache *
-nmp_cache_new ()
+nmp_cache_new (gboolean use_udev)
 {
 	NMPCache *cache = g_new (NMPCache, 1);
 
@@ -1870,7 +1847,7 @@ nmp_cache_new ()
 	                                       (NMMultiIndexFuncEqual) nmp_cache_id_equal,
 	                                       (NMMultiIndexFuncClone) nmp_cache_id_clone,
 	                                       (NMMultiIndexFuncDestroy) nmp_cache_id_destroy);
-	cache->use_udev = nmp_cache_use_udev_detect ();
+	cache->use_udev = !!use_udev;
 	return cache;
 }
 
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index 7758798f..6d5b9627 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -399,9 +399,7 @@ GHashTable *nmp_cache_lookup_all_to_hash (const NMPCache *cache,
 gboolean nmp_cache_link_connected_needs_toggle (const NMPCache *cache, const NMPObject *master, const NMPObject *potential_slave, const NMPObject *ignore_slave);
 const NMPObject *nmp_cache_link_connected_needs_toggle_by_ifindex (const NMPCache *cache, int master_ifindex, const NMPObject *potential_slave, const NMPObject *ignore_slave);
 
-gboolean nmp_cache_use_udev_detect (void);
 gboolean nmp_cache_use_udev_get (const NMPCache *cache);
-gboolean nmp_cache_use_udev_set (NMPCache *cache, gboolean use_udev);
 
 void ASSERT_nmp_cache_is_consistent (const NMPCache *cache);
 
@@ -411,7 +409,7 @@ NMPCacheOpsType nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPOb
 NMPCacheOpsType nmp_cache_update_link_udev (NMPCache *cache, int ifindex, GUdevDevice *udev_device, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
 NMPCacheOpsType nmp_cache_update_link_master_connected (NMPCache *cache, int ifindex, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
 
-NMPCache *nmp_cache_new (void);
+NMPCache *nmp_cache_new (gboolean use_udev);
 void nmp_cache_free (NMPCache *cache);
 
 #endif /* __NMP_OBJECT_H__ */
diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c
index 3b52a7be..83a0bfd3 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -94,12 +94,12 @@ test_ip4_address_general (void)
 
 	/* Add address */
 	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
-	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, NULL);
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, 0, NULL);
 	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
 	accept_signal (address_added);
 
 	/* Add address again (aka update) */
-	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime + 100, preferred + 50, NULL);
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime + 100, preferred + 50, 0, NULL);
 	accept_signals (address_changed, 0, 1);
 
 	/* Test address listing */
@@ -197,7 +197,7 @@ test_ip4_address_general_2 (void)
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, DEVICE_IFINDEX, NULL));
 
 	/* Add/delete notification */
-	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, NULL);
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, 0, NULL);
 	accept_signal (address_added);
 	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
 	nmtstp_ip4_address_del (EX, ifindex, addr, IP4_PLEN, addr);
@@ -205,7 +205,7 @@ test_ip4_address_general_2 (void)
 	g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
 
 	/* Add/delete conflict */
-	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, NULL);
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, 0, NULL);
 	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr));
 	accept_signal (address_added);
 
@@ -273,7 +273,7 @@ test_ip4_address_peer (void)
 	accept_signals (address_added, 0, G_MAXINT);
 
 	/* Add/delete notification */
-	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr_peer, lifetime, preferred, NULL);
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr_peer, lifetime, preferred, 0, NULL);
 	accept_signal (address_added);
 	a = nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer);
 	g_assert (a);
@@ -281,7 +281,7 @@ test_ip4_address_peer (void)
 
 	nmtstp_ip_address_assert_lifetime ((NMPlatformIPAddress *) a, -1, lifetime, preferred);
 
-	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr_peer2, lifetime, preferred, NULL);
+	nmtstp_ip4_address_add (EX, ifindex, addr, IP4_PLEN, addr_peer2, lifetime, preferred, 0, NULL);
 	accept_signal (address_added);
 	g_assert (nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer));
 	a = nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2);
@@ -328,7 +328,7 @@ test_ip4_address_peer_zero (void)
 	for (i = 0; i < G_N_ELEMENTS (peers); i++) {
 		g_assert (!nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, addr, plen, r_peers[i]));
 
-		nmtstp_ip4_address_add (EX, ifindex, addr, plen, r_peers[i], lifetime, preferred, label);
+		nmtstp_ip4_address_add (EX, ifindex, addr, plen, r_peers[i], lifetime, preferred, 0, label);
 
 		addrs = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
 		g_assert (addrs);
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 6ad24478..4036daad 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -63,7 +63,7 @@ test_cleanup_internal (void)
 	g_assert (ifindex > 0);
 
 	/* Add routes and addresses */
-	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr4, plen4, addr4, lifetime, preferred, NULL));
+	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr4, plen4, addr4, lifetime, preferred, 0, NULL));
 	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr6, plen6, in6addr_any, lifetime, preferred, flags));
 	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, gateway4, 32, INADDR_ANY, 0, metric, mss));
 	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network4, plen4, gateway4, 0, metric, mss));
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 268a5c92..e9fffe90 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -338,30 +338,32 @@ _wait_for_signal_timeout (gpointer user_data)
 }
 
 gboolean
-nmtstp_wait_for_signal (guint timeout_ms)
+nmtstp_wait_for_signal (NMPlatform *platform, guint timeout_ms)
 {
 	WaitForSignalData data = { 0 };
-
 	gulong id_link, id_ip4_address, id_ip6_address, id_ip4_route, id_ip6_route;
 
+	if (!platform)
+		platform = NM_PLATFORM_GET;
+
 	data.loop = g_main_loop_new (NULL, FALSE);
 
-	id_link        = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
-	id_ip4_address = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
-	id_ip6_address = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
-	id_ip4_route   = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
-	id_ip6_route   = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_link        = g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_ip4_address = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_ip6_address = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_ip4_route   = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_ip6_route   = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
 
 	if (timeout_ms != 0)
 		data.id = g_timeout_add (timeout_ms, _wait_for_signal_timeout, &data);
 
 	g_main_loop_run (data.loop);
 
-	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_link));
-	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip4_address));
-	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip6_address));
-	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip4_route));
-	g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip6_route));
+	g_assert (nm_clear_g_signal_handler (platform, &id_link));
+	g_assert (nm_clear_g_signal_handler (platform, &id_ip4_address));
+	g_assert (nm_clear_g_signal_handler (platform, &id_ip6_address));
+	g_assert (nm_clear_g_signal_handler (platform, &id_ip4_route));
+	g_assert (nm_clear_g_signal_handler (platform, &id_ip6_route));
 
 	if (nm_clear_g_source (&data.id))
 		g_assert (timeout_ms != 0 && !data.timeout);
@@ -372,7 +374,7 @@ nmtstp_wait_for_signal (guint timeout_ms)
 }
 
 gboolean
-nmtstp_wait_for_signal_until (gint64 until_ms)
+nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms)
 {
 	gint64 now;
 
@@ -382,19 +384,19 @@ nmtstp_wait_for_signal_until (gint64 until_ms)
 		if (until_ms < now)
 			return FALSE;
 
-		if (nmtstp_wait_for_signal (MAX (1, until_ms - now)))
+		if (nmtstp_wait_for_signal (platform, MAX (1, until_ms - now)))
 			return TRUE;
 	}
 }
 
 const NMPlatformLink *
-nmtstp_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms)
+nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms)
 {
-	return nmtstp_wait_for_link_until (ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
+	return nmtstp_wait_for_link_until (platform, ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
 }
 
 const NMPlatformLink *
-nmtstp_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms)
+nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms)
 {
 	const NMPlatformLink *plink;
 	gint64 now;
@@ -402,7 +404,7 @@ nmtstp_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, g
 	while (TRUE) {
 		now = nm_utils_get_monotonic_timestamp_ms ();
 
-		plink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, ifname);
+		plink = nm_platform_link_get_by_ifname (platform ?: NM_PLATFORM_GET, ifname);
 		if (   plink
 		    && (expected_link_type == NM_LINK_TYPE_NONE || plink->type == expected_link_type))
 			return plink;
@@ -410,22 +412,22 @@ nmtstp_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, g
 		if (until_ms < now)
 			return NULL;
 
-		nmtstp_wait_for_signal (MAX (1, until_ms - now));
+		nmtstp_wait_for_signal (platform, MAX (1, until_ms - now));
 	}
 }
 
 const NMPlatformLink *
-nmtstp_assert_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms)
+nmtstp_assert_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms)
 {
-	return nmtstp_assert_wait_for_link_until (ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
+	return nmtstp_assert_wait_for_link_until (platform, ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
 }
 
 const NMPlatformLink *
-nmtstp_assert_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms)
+nmtstp_assert_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms)
 {
 	const NMPlatformLink *plink;
 
-	plink = nmtstp_wait_for_link_until (ifname, expected_link_type, until_ms);
+	plink = nmtstp_wait_for_link_until (platform, ifname, expected_link_type, until_ms);
 	g_assert (plink);
 	return plink;
 }
@@ -563,8 +565,8 @@ _ip_address_add (gboolean external_command,
                  const NMIPAddr *peer_address,
                  guint32 lifetime,
                  guint32 preferred,
-                 const char *label,
-                 guint32 flags)
+                 guint32 flags,
+                 const char *label)
 {
 	gint64 end_time;
 
@@ -626,7 +628,6 @@ _ip_address_add (gboolean external_command,
 		gboolean success;
 
 		if (is_v4) {
-			g_assert (flags == 0);
 			success = nm_platform_ip4_address_add (NM_PLATFORM_GET,
 			                                       ifindex,
 			                                       address->addr4,
@@ -634,6 +635,7 @@ _ip_address_add (gboolean external_command,
 			                                       peer_address->addr4,
 			                                       lifetime,
 			                                       preferred,
+			                                       flags,
 			                                       label);
 		} else {
 			g_assert (label == NULL);
@@ -686,20 +688,20 @@ _ip_address_add (gboolean external_command,
 		/* for internal command, we expect not to reach this line.*/
 		g_assert (external_command);
 
-		g_assert (nmtstp_wait_for_signal_until (end_time));
+		g_assert (nmtstp_wait_for_signal_until (NM_PLATFORM_GET, end_time));
 	} while (TRUE);
 }
 
-#define _assert_pllink(success, pllink, name, type) \
+#define _assert_pllink(platform, success, pllink, name, type) \
 	G_STMT_START { \
 		const NMPlatformLink *_pllink = (pllink); \
 		\
 		if ((success)) { \
 			g_assert (_pllink); \
-			g_assert (_pllink == nmtstp_link_get_typed (_pllink->ifindex, (name), (type))); \
+			g_assert (_pllink == nmtstp_link_get_typed (platform, _pllink->ifindex, (name), (type))); \
 		} else { \
 			g_assert (!_pllink); \
-			g_assert (!nmtstp_link_get (0, (name))); \
+			g_assert (!nmtstp_link_get (platform, 0, (name))); \
 		} \
 	} G_STMT_END
 
@@ -718,12 +720,12 @@ nmtstp_link_dummy_add (gboolean external_command,
 		success = !nmtstp_run_command ("ip link add %s type dummy",
 		                                name);
 		if (success)
-			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_DUMMY, 100);
+			pllink = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, name, NM_LINK_TYPE_DUMMY, 100);
 	} else
 		success = nm_platform_link_dummy_add (NM_PLATFORM_GET, name, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
 
 	g_assert (success);
-	_assert_pllink (success, pllink, name, NM_LINK_TYPE_DUMMY);
+	_assert_pllink (NM_PLATFORM_GET, success, pllink, name, NM_LINK_TYPE_DUMMY);
 	return pllink;
 }
 
@@ -755,11 +757,11 @@ nmtstp_link_gre_add (gboolean external_command,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
-			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_GRE, 100);
+			pllink = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, name, NM_LINK_TYPE_GRE, 100);
 	} else
 		success = nm_platform_link_gre_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
 
-	_assert_pllink (success, pllink, name, NM_LINK_TYPE_GRE);
+	_assert_pllink (NM_PLATFORM_GET, success, pllink, name, NM_LINK_TYPE_GRE);
 
 	return pllink;
 }
@@ -806,11 +808,11 @@ nmtstp_link_ip6tnl_add (gboolean external_command,
 		                                lnk->encap_limit,
 		                                lnk->flow_label);
 		if (success)
-			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_IP6TNL, 100);
+			pllink = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, name, NM_LINK_TYPE_IP6TNL, 100);
 	} else
 		success = nm_platform_link_ip6tnl_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
 
-	_assert_pllink (success, pllink, name, NM_LINK_TYPE_IP6TNL);
+	_assert_pllink (NM_PLATFORM_GET, success, pllink, name, NM_LINK_TYPE_IP6TNL);
 
 	return pllink;
 }
@@ -843,11 +845,11 @@ nmtstp_link_ipip_add (gboolean external_command,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
-			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_IPIP, 100);
+			pllink = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, name, NM_LINK_TYPE_IPIP, 100);
 	} else
 		success = nm_platform_link_ipip_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
 
-	_assert_pllink (success, pllink, name, NM_LINK_TYPE_IPIP);
+	_assert_pllink (NM_PLATFORM_GET, success, pllink, name, NM_LINK_TYPE_IPIP);
 
 	return pllink;
 }
@@ -888,11 +890,11 @@ nmtstp_link_macvlan_add (gboolean external_command,
 		                                modes[lnk->mode],
 		                                lnk->no_promisc ? "nopromisc" : "");
 		if (success)
-			pllink = nmtstp_assert_wait_for_link (name, link_type, 100);
+			pllink = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, name, link_type, 100);
 	} else
 		success = nm_platform_link_macvlan_add (NM_PLATFORM_GET, name, parent, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
 
-	_assert_pllink (success, pllink, name, link_type);
+	_assert_pllink (NM_PLATFORM_GET, success, pllink, name, link_type);
 
 	return pllink;
 }
@@ -930,11 +932,11 @@ nmtstp_link_sit_add (gboolean external_command,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
-			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_SIT, 100);
+			pllink = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, name, NM_LINK_TYPE_SIT, 100);
 	} else
 		success = nm_platform_link_sit_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
 
-	_assert_pllink (success, pllink, name, NM_LINK_TYPE_SIT);
+	_assert_pllink (NM_PLATFORM_GET, success, pllink, name, NM_LINK_TYPE_SIT);
 
 	return pllink;
 }
@@ -983,7 +985,7 @@ nmtstp_link_vxlan_add (gboolean external_command,
 		/* Older versions of iproute2 don't support adding vxlan devices.
 		 * On failure, fallback to using platform code. */
 		if (err == 0)
-			pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_VXLAN, 100);
+			pllink = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, name, NM_LINK_TYPE_VXLAN, 100);
 		else
 			_LOGI ("Adding vxlan device via iproute2 failed. Assume iproute2 is not up to the task.");
 	}
@@ -1006,6 +1008,7 @@ nmtstp_ip4_address_add (gboolean external_command,
                         in_addr_t peer_address,
                         guint32 lifetime,
                         guint32 preferred,
+                        guint32 flags,
                         const char *label)
 {
 	_ip_address_add (external_command,
@@ -1016,8 +1019,8 @@ nmtstp_ip4_address_add (gboolean external_command,
 	                 (NMIPAddr *) &peer_address,
 	                 lifetime,
 	                 preferred,
-	                 label,
-	                 0);
+	                 flags,
+	                 label);
 }
 
 void
@@ -1038,8 +1041,8 @@ nmtstp_ip6_address_add (gboolean external_command,
 	                 (NMIPAddr *) &peer_address,
 	                 lifetime,
 	                 preferred,
-	                 NULL,
-	                 flags);
+	                 flags,
+	                 NULL);
 }
 
 static void
@@ -1127,7 +1130,7 @@ _ip_address_del (gboolean external_command,
 		/* for internal command, we expect not to reach this line.*/
 		g_assert (external_command);
 
-		g_assert (nmtstp_wait_for_signal_until (end_time));
+		g_assert (nmtstp_wait_for_signal_until (NM_PLATFORM_GET, end_time));
 	} while (TRUE);
 }
 
@@ -1161,14 +1164,18 @@ nmtstp_ip6_address_del (gboolean external_command,
 }
 
 const NMPlatformLink *
-nmtstp_link_get_typed (int ifindex,
+nmtstp_link_get_typed (NMPlatform *platform,
+                       int ifindex,
                        const char *name,
                        NMLinkType link_type)
 {
 	const NMPlatformLink *pllink = NULL;
 
+	if (!platform)
+		platform = NM_PLATFORM_GET;
+
 	if (ifindex > 0) {
-		pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex);
+		pllink = nm_platform_link_get (platform, ifindex);
 
 		if (pllink) {
 			g_assert_cmpint (pllink->ifindex, ==, ifindex);
@@ -1176,12 +1183,12 @@ nmtstp_link_get_typed (int ifindex,
 				g_assert_cmpstr (name, ==, pllink->name);
 		} else {
 			if (name)
-				g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name));
+				g_assert (!nm_platform_link_get_by_ifname (platform, name));
 		}
 	} else {
 		g_assert (name);
 
-		pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name);
+		pllink = nm_platform_link_get_by_ifname (platform, name);
 
 		if (pllink)
 			g_assert_cmpstr (name, ==, pllink->name);
@@ -1196,10 +1203,11 @@ nmtstp_link_get_typed (int ifindex,
 }
 
 const NMPlatformLink *
-nmtstp_link_get (int ifindex,
+nmtstp_link_get (NMPlatform *platform,
+                 int ifindex,
                  const char *name)
 {
-	return nmtstp_link_get_typed (ifindex, name, NM_LINK_TYPE_NONE);
+	return nmtstp_link_get_typed (platform, ifindex, name, NM_LINK_TYPE_NONE);
 }
 
 void
@@ -1212,7 +1220,7 @@ nmtstp_link_del (gboolean external_command,
 	gboolean success;
 	gs_free char *name_copy = NULL;
 
-	pllink = nmtstp_link_get (ifindex, name);
+	pllink = nmtstp_link_get (NM_PLATFORM_GET, ifindex, name);
 
 	g_assert (pllink);
 
@@ -1242,7 +1250,7 @@ nmtstp_link_del (gboolean external_command,
 		/* for internal command, we expect not to reach this line.*/
 		g_assert (external_command);
 
-		g_assert (nmtstp_wait_for_signal_until (end_time));
+		g_assert (nmtstp_wait_for_signal_until (NM_PLATFORM_GET, end_time));
 	} while (TRUE);
 }
 
@@ -1288,7 +1296,7 @@ nmtstp_link_set_updown (gboolean external_command,
 		/* for internal command, we expect not to reach this line.*/
 		g_assert (external_command);
 
-		g_assert (nmtstp_wait_for_signal_until (end_time));
+		g_assert (nmtstp_wait_for_signal_until (NM_PLATFORM_GET, end_time));
 	} while (TRUE);
 }
 
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index 0281d3c5..82561193 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -91,13 +91,13 @@ void link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, N
 int nmtstp_run_command (const char *format, ...) __attribute__((__format__ (__printf__, 1, 2)));
 #define nmtstp_run_command_check(...) do { g_assert_cmpint (nmtstp_run_command (__VA_ARGS__), ==, 0); } while (0)
 
-gboolean nmtstp_wait_for_signal (guint timeout_ms);
-gboolean nmtstp_wait_for_signal_until (gint64 until_ms);
-const NMPlatformLink *nmtstp_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms);
-const NMPlatformLink *nmtstp_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
+gboolean nmtstp_wait_for_signal (NMPlatform *platform, guint timeout_ms);
+gboolean nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms);
+const NMPlatformLink *nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms);
+const NMPlatformLink *nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
 
-const NMPlatformLink *nmtstp_assert_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms);
-const NMPlatformLink *nmtstp_assert_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
+const NMPlatformLink *nmtstp_assert_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms);
+const NMPlatformLink *nmtstp_assert_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
 
 int nmtstp_run_command_check_external_global (void);
 gboolean nmtstp_run_command_check_external (int external_command);
@@ -117,6 +117,7 @@ void nmtstp_ip4_address_add (gboolean external_command,
                              in_addr_t peer_address,
                              guint32 lifetime,
                              guint32 preferred,
+                             guint32 flags,
                              const char *label);
 void nmtstp_ip6_address_add (gboolean external_command,
                              int ifindex,
@@ -125,7 +126,7 @@ void nmtstp_ip6_address_add (gboolean external_command,
                              struct in6_addr peer_address,
                              guint32 lifetime,
                              guint32 preferred,
-                             guint flags);
+                             guint32 flags);
 void nmtstp_ip4_address_del (gboolean external_command,
                              int ifindex,
                              in_addr_t address,
@@ -136,8 +137,8 @@ void nmtstp_ip6_address_del (gboolean external_command,
                              struct in6_addr address,
                              int plen);
 
-const NMPlatformLink *nmtstp_link_get_typed (int ifindex, const char *name, NMLinkType link_type);
-const NMPlatformLink *nmtstp_link_get (int ifindex, const char *name);
+const NMPlatformLink *nmtstp_link_get_typed (NMPlatform *platform, int ifindex, const char *name, NMLinkType link_type);
+const NMPlatformLink *nmtstp_link_get (NMPlatform *platform, int ifindex, const char *name);
 
 void nmtstp_link_set_updown (gboolean external_command,
                              int ifindex,
diff --git a/src/platform/tests/test-general.c b/src/platform/tests/test-general.c
index f342e565..66f72fa5 100644
--- a/src/platform/tests/test-general.c
+++ b/src/platform/tests/test-general.c
@@ -18,12 +18,12 @@
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
-#include "nm-platform-utils.h"
+#include "nm-default.h"
 
 #include <linux/rtnetlink.h>
 
+#include "nm-platform-utils.h"
 #include "nm-linux-platform.h"
-#include "nm-default.h"
 
 #include "nm-test-utils.h"
 
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 3fa5ad50..94695edc 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -21,8 +21,13 @@
 #include "nm-default.h"
 
 #include <sched.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 
 #include "nmp-object.h"
+#include "nmp-netns.h"
+#include "nm-platform-utils.h"
 
 #include "test-common.h"
 #include "nm-test-utils.h"
@@ -40,6 +45,9 @@
 #define VLAN_FLAGS 0
 #define MTU 1357
 
+#define _ADD_DUMMY(platform, name) \
+	g_assert_cmpint (nm_platform_link_dummy_add ((platform), (name), NULL), ==, NM_PLATFORM_ERROR_SUCCESS)
+
 static void
 test_bogus(void)
 {
@@ -52,16 +60,16 @@ test_bogus(void)
 	g_assert (!nm_platform_link_get_type (NM_PLATFORM_GET, BOGUS_IFINDEX));
 	g_assert (!nm_platform_link_get_type_name (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_up (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_down (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_arp (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_noarp (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
 	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, BOGUS_IFINDEX));
@@ -72,7 +80,7 @@ test_bogus(void)
 	g_assert (!addrlen);
 	g_assert (!nm_platform_link_get_address (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_WARNING, "*failure changing link: *");
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE, "*failure changing link: *");
 	g_assert (!nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU));
 
 	g_assert (!nm_platform_link_get_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX));
@@ -690,7 +698,7 @@ test_software_detect (gconstpointer user_data)
 	const gboolean ext = test_data->external_command;
 
 	nmtstp_run_command_check ("ip link add %s type dummy", PARENT_NAME);
-	ifindex_parent = nmtstp_assert_wait_for_link (PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex;
+	ifindex_parent = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex;
 
 	switch (test_data->link_type) {
 	case NM_LINK_TYPE_GRE: {
@@ -852,7 +860,7 @@ test_software_detect (gconstpointer user_data)
 		g_assert_not_reached ();
 	}
 
-	ifindex = nmtstp_assert_wait_for_link (DEVICE_NAME, test_data->link_type, 100)->ifindex;
+	ifindex = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, DEVICE_NAME, test_data->link_type, 100)->ifindex;
 
 	nmtstp_link_set_updown (-1, ifindex_parent, TRUE);
 
@@ -1137,10 +1145,10 @@ test_vlan_set_xgress (void)
 	int ifindex, ifindex_parent;
 
 	nmtstp_run_command_check ("ip link add %s type dummy", PARENT_NAME);
-	ifindex_parent = nmtstp_assert_wait_for_link (PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex;
+	ifindex_parent = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex;
 
 	nmtstp_run_command_check ("ip link add name %s link %s type vlan id 1245", DEVICE_NAME, PARENT_NAME);
-	ifindex = nmtstp_assert_wait_for_link (DEVICE_NAME, NM_LINK_TYPE_VLAN, 100)->ifindex;
+	ifindex = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, DEVICE_NAME, NM_LINK_TYPE_VLAN, 100)->ifindex;
 
 	/* ingress-qos-map */
 
@@ -1662,7 +1670,7 @@ test_create_many_links (gconstpointer user_data)
 	guint n_devices = GPOINTER_TO_UINT (user_data);
 
 	if (n_devices > 100 && nmtst_test_quick ()) {
-		g_print ("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n", str_if_set (g_get_prgname (), "test-link-linux"));
+		g_print ("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n", g_get_prgname () ?: "test-link-linux");
 		g_test_skip ("Skip long running test");
 		return;
 	}
@@ -1685,8 +1693,8 @@ test_nl_bugs_veth (void)
 
 	/* create veth pair. */
 	nmtstp_run_command_check ("ip link add dev %s type veth peer name %s", IFACE_VETH0, IFACE_VETH1);
-	ifindex_veth0 = nmtstp_assert_wait_for_link (IFACE_VETH0, NM_LINK_TYPE_VETH, 100)->ifindex;
-	ifindex_veth1 = nmtstp_assert_wait_for_link (IFACE_VETH1, NM_LINK_TYPE_VETH, 100)->ifindex;
+	ifindex_veth0 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_VETH0, NM_LINK_TYPE_VETH, 100)->ifindex;
+	ifindex_veth1 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_VETH1, NM_LINK_TYPE_VETH, 100)->ifindex;
 
 	/* assert that nm_platform_link_veth_get_properties() returns the expected peer ifindexes. */
 	g_assert (nm_platform_link_veth_get_properties (NM_PLATFORM_GET, ifindex_veth0, &i));
@@ -1722,7 +1730,7 @@ test_nl_bugs_veth (void)
 
 	nmtstp_run_command_check ("ip link set %s netns %ld", IFACE_VETH1, (long) nmtstp_namespace_handle_get_pid (ns_handle));
 	NMTST_WAIT_ASSERT (100, {
-		nmtstp_wait_for_signal (50);
+		nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
 		nm_platform_process_events (NM_PLATFORM_GET);
 
 		pllink_veth1 = nm_platform_link_get (NM_PLATFORM_GET, ifindex_veth1);
@@ -1736,8 +1744,8 @@ test_nl_bugs_veth (void)
 
 out:
 	nmtstp_link_del (-1, ifindex_veth0, IFACE_VETH0);
-	g_assert (!nmtstp_link_get (ifindex_veth0, IFACE_VETH0));
-	g_assert (!nmtstp_link_get (ifindex_veth1, IFACE_VETH1));
+	g_assert (!nmtstp_link_get (NM_PLATFORM_GET, ifindex_veth0, IFACE_VETH0));
+	g_assert (!nmtstp_link_get (NM_PLATFORM_GET, ifindex_veth1, IFACE_VETH1));
 	nmtstp_namespace_handle_release (ns_handle);
 }
 
@@ -1755,16 +1763,16 @@ test_nl_bugs_spuroius_newlink (void)
 	/* see https://bugzilla.redhat.com/show_bug.cgi?id=1285719 */
 
 	nmtstp_run_command_check ("ip link add %s type dummy", IFACE_DUMMY0);
-	ifindex_dummy0 = nmtstp_assert_wait_for_link (IFACE_DUMMY0, NM_LINK_TYPE_DUMMY, 100)->ifindex;
+	ifindex_dummy0 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_DUMMY0, NM_LINK_TYPE_DUMMY, 100)->ifindex;
 
 	nmtstp_run_command_check ("ip link add %s type bond", IFACE_BOND0);
-	ifindex_bond0 = nmtstp_assert_wait_for_link (IFACE_BOND0, NM_LINK_TYPE_BOND, 100)->ifindex;
+	ifindex_bond0 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_BOND0, NM_LINK_TYPE_BOND, 100)->ifindex;
 
 	nmtstp_link_set_updown (-1, ifindex_bond0, TRUE);
 
 	nmtstp_run_command_check ("ip link set %s master %s", IFACE_DUMMY0, IFACE_BOND0);
 	NMTST_WAIT_ASSERT (100, {
-		nmtstp_wait_for_signal (50);
+		nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
 
 		pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_dummy0);
 		g_assert (pllink);
@@ -1775,7 +1783,7 @@ test_nl_bugs_spuroius_newlink (void)
 	nmtstp_run_command_check ("ip link del %s",  IFACE_BOND0);
 
 	wait_for_settle = TRUE;
-	nmtstp_wait_for_signal (50);
+	nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
 again:
 	nm_platform_process_events (NM_PLATFORM_GET);
 	pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_bond0);
@@ -1783,11 +1791,11 @@ again:
 
 	if (wait_for_settle) {
 		wait_for_settle = FALSE;
-		NMTST_WAIT (300, { nmtstp_wait_for_signal (50); });
+		NMTST_WAIT (300, { nmtstp_wait_for_signal (NM_PLATFORM_GET, 50); });
 		goto again;
 	}
 
-	g_assert (!nmtstp_link_get (ifindex_bond0, IFACE_BOND0));
+	g_assert (!nmtstp_link_get (NM_PLATFORM_GET, ifindex_bond0, IFACE_BOND0));
 	nmtstp_link_del (-1, ifindex_dummy0, IFACE_DUMMY0);
 }
 
@@ -1805,16 +1813,16 @@ test_nl_bugs_spuroius_dellink (void)
 	/* see https://bugzilla.redhat.com/show_bug.cgi?id=1285719 */
 
 	nmtstp_run_command_check ("ip link add %s type dummy", IFACE_DUMMY0);
-	ifindex_dummy0 = nmtstp_assert_wait_for_link (IFACE_DUMMY0, NM_LINK_TYPE_DUMMY, 100)->ifindex;
+	ifindex_dummy0 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_DUMMY0, NM_LINK_TYPE_DUMMY, 100)->ifindex;
 
 	nmtstp_run_command_check ("ip link add %s type bridge", IFACE_BRIDGE0);
-	ifindex_bridge0 = nmtstp_assert_wait_for_link (IFACE_BRIDGE0, NM_LINK_TYPE_BRIDGE, 100)->ifindex;
+	ifindex_bridge0 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_BRIDGE0, NM_LINK_TYPE_BRIDGE, 100)->ifindex;
 
 	nmtstp_link_set_updown (-1, ifindex_bridge0, TRUE);
 
 	nmtstp_run_command_check ("ip link set %s master %s", IFACE_DUMMY0, IFACE_BRIDGE0);
 	NMTST_WAIT_ASSERT (100, {
-		nmtstp_wait_for_signal (50);
+		nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
 
 		pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_dummy0);
 		g_assert (pllink);
@@ -1827,7 +1835,7 @@ test_nl_bugs_spuroius_dellink (void)
 	nmtstp_run_command_check ("ip link set %s nomaster",  IFACE_DUMMY0);
 
 	wait_for_settle = TRUE;
-	nmtstp_wait_for_signal (50);
+	nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
 again:
 	nm_platform_process_events (NM_PLATFORM_GET);
 	pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex_bridge0);
@@ -1838,7 +1846,7 @@ again:
 
 	if (wait_for_settle) {
 		wait_for_settle = FALSE;
-		NMTST_WAIT (300, { nmtstp_wait_for_signal (50); });
+		NMTST_WAIT (300, { nmtstp_wait_for_signal (NM_PLATFORM_GET, 50); });
 		goto again;
 	}
 
@@ -1846,6 +1854,442 @@ again:
 	nmtstp_link_del (-1, ifindex_dummy0, IFACE_DUMMY0);
 }
 
+/******************************************************************/
+
+static void
+_test_netns_setup (gpointer fixture, gconstpointer test_data)
+{
+	/* the singleton platform instance has netns support disabled.
+	 * Destroy the instance before the test and re-create it afterwards. */
+	g_object_unref (nm_platform_get ());
+}
+
+static void
+_test_netns_teardown (gpointer fixture, gconstpointer test_data)
+{
+	/* re-create platform instance */
+	SETUP ();
+}
+
+static NMPlatform *
+_test_netns_create_platform (void)
+{
+	NMPNetns *netns;
+	NMPlatform *platform;
+
+	netns = nmp_netns_new ();
+	g_assert (NMP_IS_NETNS (netns));
+
+	platform = g_object_new (NM_TYPE_LINUX_PLATFORM, NM_PLATFORM_NETNS_SUPPORT, TRUE, NULL);
+	g_assert (NM_IS_LINUX_PLATFORM (platform));
+
+	nmp_netns_pop (netns);
+	g_object_unref (netns);
+
+	return platform;
+}
+
+static gboolean
+_test_netns_check_skip (void)
+{
+	static int support = -1;
+	static int support_errsv = 0;
+	NMPNetns *netns;
+
+	netns = nmp_netns_get_current ();
+	if (!netns) {
+		g_test_skip ("No netns support");
+		return TRUE;
+	}
+
+	g_assert (nmp_netns_get_fd_net (netns) > 0);
+
+	if (support == -1) {
+		support = (setns (nmp_netns_get_fd_net (netns), CLONE_NEWNET) == 0);
+		if (!support)
+			support_errsv = errno;
+	}
+	if (!support) {
+			_LOGD ("setns() failed with \"%s\". This indicates missing support (valgrind?)", g_strerror (support_errsv));
+			g_test_skip ("No netns support (setns failed)");
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/******************************************************************/
+
+static void
+test_netns_general (gpointer fixture, gconstpointer test_data)
+{
+	gs_unref_object NMPlatform *platform_1 = NULL;
+	gs_unref_object NMPlatform *platform_2 = NULL;
+	NMPNetns *netns_tmp;
+	char sbuf[100];
+	int i, j, k;
+	gboolean ethtool_support;
+
+	if (_test_netns_check_skip ())
+		return;
+
+	platform_1 = g_object_new (NM_TYPE_LINUX_PLATFORM, NM_PLATFORM_NETNS_SUPPORT, TRUE, NULL);
+	platform_2 = _test_netns_create_platform ();
+
+	/* add some dummy devices. The "other-*" devices are there to bump the ifindex */
+	for (k = 0; k < 2; k++) {
+		NMPlatform *p = (k == 0 ? platform_1 : platform_2);
+		const char *id = (k == 0 ? "a" : "b");
+
+		for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++)
+			_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-a-%s-%02d", id, i));
+
+		_ADD_DUMMY (p, "dummy1_");
+
+		for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++)
+			_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-b-%s-%02d", id, i));
+
+		_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "dummy2%s", id));
+
+		for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++)
+			_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-c-%s-%02d", id, i));
+	}
+
+	g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy1_/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_1, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex));
+	g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy2a/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_1, 0, "dummy2a", NM_LINK_TYPE_DUMMY)->ifindex));
+	g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/sys/devices/virtual/net/dummy2b/ifindex"), ==, NULL);
+
+	g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy1_/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_2, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex));
+	g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy2a/ifindex"), ==, NULL);
+	g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/sys/devices/virtual/net/dummy2b/ifindex"), ==, nm_sprintf_buf (sbuf, "%d", nmtstp_link_get_typed (platform_2, 0, "dummy2b", NM_LINK_TYPE_DUMMY)->ifindex));
+
+	for (i = 0; i < 10; i++) {
+		NMPlatform *pl;
+		const char *path;
+
+		j = nmtst_get_rand_int () % 2;
+
+		if (nmtst_get_rand_int () % 2) {
+			pl = platform_1;
+			if (nmtst_get_rand_int () % 2)
+				path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6";
+			else
+				path = "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6";
+		} else {
+			pl = platform_2;
+			if (nmtst_get_rand_int () % 2)
+				path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6";
+			else
+				path = "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6";
+		}
+		g_assert (nm_platform_sysctl_set (pl, path, nm_sprintf_buf (sbuf, "%d", j)));
+		g_assert_cmpstr (nm_platform_sysctl_get (pl, path), ==, nm_sprintf_buf (sbuf, "%d", j));
+	}
+	g_assert_cmpstr (nm_platform_sysctl_get (platform_1, "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6"), ==, NULL);
+	g_assert_cmpstr (nm_platform_sysctl_get (platform_2, "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6"), ==, NULL);
+
+	/* older kernels (Ubuntu 12.04) don't support ethtool -i for dummy devices. Work around that and
+	 * skip asserts that are known to fail. */
+	ethtool_support = nmtstp_run_command ("ethtool -i dummy1_ > /dev/null") == 0;
+	if (ethtool_support) {
+		g_assert ( nmp_utils_ethtool_get_driver_info ("dummy1_", NULL, NULL, NULL));
+		g_assert ( nmp_utils_ethtool_get_driver_info ("dummy2a", NULL, NULL, NULL));
+		g_assert (!nmp_utils_ethtool_get_driver_info ("dummy2b", NULL, NULL, NULL));
+		g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy1_ > /dev/null"), ==, 0);
+		g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy2a > /dev/null"), ==, 0);
+		g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy2b 2> /dev/null"), !=, 0);
+	}
+
+	g_assert (nm_platform_netns_push (platform_2, &netns_tmp));
+
+	if (ethtool_support) {
+		g_assert ( nmp_utils_ethtool_get_driver_info ("dummy1_", NULL, NULL, NULL));
+		g_assert (!nmp_utils_ethtool_get_driver_info ("dummy2a", NULL, NULL, NULL));
+		g_assert ( nmp_utils_ethtool_get_driver_info ("dummy2b", NULL, NULL, NULL));
+		g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy1_ > /dev/null"), ==, 0);
+		g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy2a 2> /dev/null"), !=, 0);
+		g_assert_cmpint (nmtstp_run_command ("ethtool -i dummy2b > /dev/null"), ==, 0);
+	}
+
+	nmp_netns_pop (netns_tmp);
+}
+
+/*****************************************************************************/
+
+static void
+test_netns_set_netns (gpointer fixture, gconstpointer test_data)
+{
+	NMPlatform *platforms[3];
+	gs_unref_object NMPlatform *platform_0 = NULL;
+	gs_unref_object NMPlatform *platform_1 = NULL;
+	gs_unref_object NMPlatform *platform_2 = NULL;
+	nm_auto_pop_netns NMPNetns *netns_pop = NULL;
+	int i;
+
+	if (_test_netns_check_skip ())
+		return;
+
+	platforms[0] = platform_0 = g_object_new (NM_TYPE_LINUX_PLATFORM, NM_PLATFORM_NETNS_SUPPORT, TRUE, NULL);
+	platforms[1] = platform_1 = _test_netns_create_platform ();
+	platforms[2] = platform_2 = _test_netns_create_platform ();
+
+	i = nmtst_get_rand_int () % 4;
+	if (i != 3)
+		g_assert (nm_platform_netns_push (platforms[i], &netns_pop));
+
+#define LINK_MOVE_NAME "link-move"
+	g_assert (!nm_platform_link_get_by_ifname (platform_1, LINK_MOVE_NAME));
+	g_assert (!nm_platform_link_get_by_ifname (platform_2, LINK_MOVE_NAME));
+	_ADD_DUMMY (platform_1, LINK_MOVE_NAME);
+	g_assert ( nm_platform_link_get_by_ifname (platform_1, LINK_MOVE_NAME));
+	g_assert (!nm_platform_link_get_by_ifname (platform_2, LINK_MOVE_NAME));
+	g_assert (nm_platform_link_set_netns (platform_1,
+	                                      nm_platform_link_get_by_ifname (platform_1, LINK_MOVE_NAME)->ifindex,
+	                                      nmp_netns_get_fd_net (nm_platform_netns_get (platform_2))));
+	g_assert (!nm_platform_link_get_by_ifname (platform_1, LINK_MOVE_NAME));
+	g_assert (!nm_platform_link_get_by_ifname (platform_2, LINK_MOVE_NAME));
+	nmtstp_assert_wait_for_link (platform_2, LINK_MOVE_NAME, NM_LINK_TYPE_DUMMY, 100);
+	g_assert (!nm_platform_link_get_by_ifname (platform_1, LINK_MOVE_NAME));
+	g_assert ( nm_platform_link_get_by_ifname (platform_2, LINK_MOVE_NAME));
+}
+
+/*****************************************************************************/
+
+static char *
+_get_current_namespace_id (int ns_type)
+{
+	const char *p;
+	GError *error = NULL;
+	char *id;
+
+	switch (ns_type) {
+	case CLONE_NEWNET:
+		p = "/proc/self/ns/net";
+		break;
+	case CLONE_NEWNS:
+		p = "/proc/self/ns/mnt";
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+
+	id = g_file_read_link (p, &error);
+	g_assert_no_error (error);
+	g_assert (id);
+	return id;
+}
+
+static char *
+_get_sysctl_value (const char *path)
+{
+	char *data = NULL;
+	gs_free_error GError *error = NULL;
+
+	if (!g_file_get_contents (path, &data, NULL, &error)) {
+		nmtst_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, NULL);
+		g_assert (!data);
+	} else {
+		g_assert_no_error (error);
+		g_assert (data);
+		g_strstrip (data);
+	}
+	return data;
+}
+
+static void
+test_netns_push (gpointer fixture, gconstpointer test_data)
+{
+	gs_unref_object NMPlatform *platform_0 = NULL;
+	gs_unref_object NMPlatform *platform_1 = NULL;
+	gs_unref_object NMPlatform *platform_2 = NULL;
+	nm_auto_pop_netns NMPNetns *netns_pop = NULL;
+	gs_unref_ptrarray GPtrArray *device_names = g_ptr_array_new_with_free_func (g_free);
+	int i, j;
+	const int ns_types_list[] = { CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWNET | CLONE_NEWNS };
+	const int ns_types_test[] = { CLONE_NEWNET, CLONE_NEWNS };
+	typedef struct {
+		NMPlatform *platform;
+		const char *device_name;
+		const char *sysctl_path;
+		const char *sysctl_value;
+		const char *ns_net;
+		const char *ns_mnt;
+	} PlatformData;
+	PlatformData pl[3] = { };
+	PlatformData *pl_base;
+	struct {
+		PlatformData *pl;
+		int ns_types;
+	} stack[6] = { };
+	int nstack;
+
+	if (_test_netns_check_skip ())
+		return;
+
+	pl[0].platform = platform_0 = g_object_new (NM_TYPE_LINUX_PLATFORM, NM_PLATFORM_NETNS_SUPPORT, TRUE, NULL);
+	pl[1].platform = platform_1 = _test_netns_create_platform ();
+	pl[2].platform = platform_2 = _test_netns_create_platform ();
+
+	pl_base = &pl[0];
+	i = nmtst_get_rand_int () % (G_N_ELEMENTS (pl) + 1);
+	if (i < G_N_ELEMENTS (pl)) {
+		pl_base = &pl[i];
+		g_assert (nm_platform_netns_push (pl[i].platform, &netns_pop));
+	}
+
+	for (i = 0; i < G_N_ELEMENTS (pl); i++) {
+		nm_auto_pop_netns NMPNetns *netns_free = NULL;
+		char *tmp;
+
+		g_assert (nm_platform_netns_push (pl[i].platform, &netns_free));
+
+		tmp = g_strdup_printf ("nmtst-dev-%d", i);
+		g_ptr_array_add (device_names, tmp);
+		pl[i].device_name = tmp;
+
+		tmp = g_strdup_printf ("/proc/sys/net/ipv6/conf/%s/disable_ipv6", pl[i].device_name);
+		g_ptr_array_add (device_names, tmp);
+		pl[i].sysctl_path = tmp;
+
+		pl[i].sysctl_value = nmtst_get_rand_int () % 2 ? "1" : "0";
+
+		_ADD_DUMMY (pl[i].platform, pl[i].device_name);
+
+		g_assert (nm_platform_sysctl_set (pl[i].platform, pl[i].sysctl_path, pl[i].sysctl_value));
+
+		tmp = _get_current_namespace_id (CLONE_NEWNET);
+		g_ptr_array_add (device_names, tmp);
+		pl[i].ns_net = tmp;
+
+		tmp = _get_current_namespace_id (CLONE_NEWNS);
+		g_ptr_array_add (device_names, tmp);
+		pl[i].ns_mnt = tmp;
+	}
+
+	nstack = nmtst_get_rand_int () % (G_N_ELEMENTS (stack) + 1);
+	for (i = 0; i < nstack; i++) {
+		stack[i].pl = &pl[nmtst_get_rand_int () % G_N_ELEMENTS (pl)];
+		stack[i].ns_types = ns_types_list[nmtst_get_rand_int () % G_N_ELEMENTS (ns_types_list)];
+
+		nmp_netns_push_type (nm_platform_netns_get (stack[i].pl->platform), stack[i].ns_types);
+	}
+
+	/* pop some again. */
+	for (i = nmtst_get_rand_int () % (nstack + 1); i > 0; i--) {
+		g_assert (nstack > 0);
+		nstack--;
+		nmp_netns_pop (nm_platform_netns_get (stack[nstack].pl->platform));
+	}
+
+	for (i = 0; i < G_N_ELEMENTS (ns_types_test); i++) {
+		int ns_type = ns_types_test[i];
+		PlatformData *p;
+		gs_free char *current_namespace_id = NULL;
+
+		p = pl_base;
+		for (j = nstack; j >= 1; ) {
+			j--;
+			if (NM_FLAGS_HAS (stack[j].ns_types, ns_type)) {
+				p = stack[j].pl;
+				break;
+			}
+		}
+
+		current_namespace_id = _get_current_namespace_id (ns_type);
+
+		if (ns_type == CLONE_NEWNET) {
+			g_assert_cmpstr (current_namespace_id, ==, p->ns_net);
+			for (j = 0; j < G_N_ELEMENTS (pl); j++) {
+				gs_free char *data = NULL;
+
+				if (p == &pl[j])
+					g_assert_cmpint (nmtstp_run_command ("ip link show %s 1>/dev/null", pl[j].device_name), ==, 0);
+				else
+					g_assert_cmpint (nmtstp_run_command ("ip link show %s 2>/dev/null", pl[j].device_name), !=, 0);
+
+				data = _get_sysctl_value (pl[j].sysctl_path);
+				if (p == &pl[j])
+					g_assert_cmpstr (data, ==, pl[j].sysctl_value);
+				else
+					g_assert (!data);
+			}
+		} else if (ns_type == CLONE_NEWNS) {
+			g_assert_cmpstr (current_namespace_id, ==, p->ns_mnt);
+			for (j = 0; j < G_N_ELEMENTS (pl); j++) {
+				char path[600];
+				gs_free char *data = NULL;
+
+				nm_sprintf_buf (path, "/sys/devices/virtual/net/%s/ifindex", pl[j].device_name);
+
+				data = _get_sysctl_value (path);
+				if (p == &pl[j])
+					g_assert_cmpstr (data, ==, nm_sprintf_buf (path, "%d", nmtstp_link_get_typed (p->platform, 0, p->device_name, NM_LINK_TYPE_DUMMY)->ifindex));
+				else
+					g_assert (!data);
+			}
+		} else
+			g_assert_not_reached ();
+	}
+
+
+	for (i = nstack; i >= 1; ) {
+		i--;
+		nmp_netns_pop (nm_platform_netns_get (stack[i].pl->platform));
+	}
+}
+
+/*****************************************************************************/
+
+static void
+test_netns_bind_to_path (gpointer fixture, gconstpointer test_data)
+{
+#define P_VAR_RUN                "/var/run"
+#define P_VAR_RUN_NETNS          "/var/run/netns"
+#define P_VAR_RUN_NETNS_BINDNAME "/var/run/netns/"P_NETNS_BINDNAME
+#define P_NETNS_BINDNAME         "nmtst-iproute2-netns"
+	gs_unref_object NMPlatform *platform_0 = NULL;
+	gs_unref_object NMPlatform *platform_1 = NULL;
+	gs_unref_object NMPlatform *platform_2 = NULL;
+	nm_auto_pop_netns NMPNetns *netns_pop = NULL;
+	NMPlatform *platforms[3];
+	NMPNetns *netns;
+	int i;
+
+	if (_test_netns_check_skip ())
+		return;
+
+	platforms[0] = platform_0 = g_object_new (NM_TYPE_LINUX_PLATFORM, NM_PLATFORM_NETNS_SUPPORT, TRUE, NULL);
+	platforms[1] = platform_1 = _test_netns_create_platform ();
+	platforms[2] = platform_2 = _test_netns_create_platform ();
+
+	i = nmtst_get_rand_int () % 4;
+	if (i != 3)
+		g_assert (nm_platform_netns_push (platforms[i], &netns_pop));
+
+	g_assert_cmpint (mount ("tmpfs", P_VAR_RUN, "tmpfs", MS_NOATIME | MS_NODEV | MS_NOSUID, "mode=0755,size=32K"), ==, 0);
+	g_assert_cmpint (mkdir (P_VAR_RUN_NETNS, 755), ==, 0);
+
+	i = (nmtst_get_rand_int () % 2) + 1;
+	netns = nm_platform_netns_get (platforms[i]);
+
+	_ADD_DUMMY (platforms[i], "dummy2b");
+
+	g_assert (!g_file_test (P_VAR_RUN_NETNS_BINDNAME, G_FILE_TEST_EXISTS));
+	g_assert_cmpint (nmtstp_run_command ("ip netns exec "P_NETNS_BINDNAME" true 2>/dev/null"), !=, 0);
+
+	g_assert (nmp_netns_bind_to_path (netns, P_VAR_RUN_NETNS_BINDNAME, NULL));
+
+	g_assert (g_file_test (P_VAR_RUN_NETNS_BINDNAME, G_FILE_TEST_EXISTS));
+	g_assert_cmpint (nmtstp_run_command ("ip netns exec "P_NETNS_BINDNAME" true"), ==, 0);
+	g_assert_cmpint (nmtstp_run_command ("ip netns exec "P_NETNS_BINDNAME" ip link show dummy2b 1>/dev/null"), ==, 0);
+
+	g_assert (nmp_netns_bind_to_path_destroy (netns, P_VAR_RUN_NETNS_BINDNAME));
+
+	g_assert (!g_file_test (P_VAR_RUN_NETNS_BINDNAME, G_FILE_TEST_EXISTS));
+	g_assert_cmpint (nmtstp_run_command ("ip netns exec "P_NETNS_BINDNAME" true 2>/dev/null"), !=, 0);
+
+	g_assert_cmpint (umount (P_VAR_RUN), ==, 0);
+}
+
 /*****************************************************************************/
 
 void
@@ -1894,5 +2338,10 @@ setup_tests (void)
 		g_test_add_func ("/link/nl-bugs/veth", test_nl_bugs_veth);
 		g_test_add_func ("/link/nl-bugs/spurious-newlink", test_nl_bugs_spuroius_newlink);
 		g_test_add_func ("/link/nl-bugs/spurious-dellink", test_nl_bugs_spuroius_dellink);
+
+		g_test_add_vtable ("/general/netns/general", 0, NULL, _test_netns_setup, test_netns_general, _test_netns_teardown);
+		g_test_add_vtable ("/general/netns/set-netns", 0, NULL, _test_netns_setup, test_netns_set_netns, _test_netns_teardown);
+		g_test_add_vtable ("/general/netns/push", 0, NULL, _test_netns_setup, test_netns_push, _test_netns_teardown);
+		g_test_add_vtable ("/general/netns/bind-to-path", 0, NULL, _test_netns_setup, test_netns_bind_to_path, _test_netns_teardown);
 	}
 }
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index 3b44abb8..d77170b3 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -223,9 +223,7 @@ test_cache_link (void)
 	GUdevDevice *udev_device_3 = g_list_nth_data (global.udev_devices, 0);
 	NMPCacheOpsType ops_type;
 
-	cache = nmp_cache_new ();
-
-	nmp_cache_use_udev_set (cache, g_rand_int_range (nmtst_get_rand (), 0, 2));
+	cache = nmp_cache_new (nmtst_get_rand_int () % 2);
 
 	/* if we have a link, and don't set is_in_netlink, adding it has no effect. */
 	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index f5fa024b..85851854 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -314,7 +314,7 @@ test_ip4_zero_gateway (void)
 	nmtstp_run_command_check ("ip route add 1.2.3.2/32 dev %s", DEVICE_NAME);
 
 	NMTST_WAIT_ASSERT (100, {
-		nmtstp_wait_for_signal (10);
+		nmtstp_wait_for_signal (NM_PLATFORM_GET, 10);
 		if (   nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.1"), 32, 0)
 		    && nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.2"), 32, 0))
 			break;
@@ -322,7 +322,7 @@ test_ip4_zero_gateway (void)
 
 	nmtstp_run_command_check ("ip route flush dev %s", DEVICE_NAME);
 
-	nmtstp_wait_for_signal (50);
+	nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
 	nm_platform_process_events (NM_PLATFORM_GET);
 }
 
diff --git a/src/platform/wifi/wifi-utils-wext.c b/src/platform/wifi/wifi-utils-wext.c
index 029601e4..af285b45 100644
--- a/src/platform/wifi/wifi-utils-wext.c
+++ b/src/platform/wifi/wifi-utils-wext.c
@@ -101,7 +101,7 @@ wifi_wext_get_mode (WifiData *data)
 	struct iwreq wrq;
 
 	memset (&wrq, 0, sizeof (struct iwreq));
-	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
+	nm_utils_ifname_cpy (wrq.ifr_name, wext->parent.iface);
 
 	if (ioctl (wext->fd, SIOCGIWMODE, &wrq) < 0) {
 		if (errno != ENODEV) {
@@ -118,6 +118,7 @@ wifi_wext_get_mode (WifiData *data)
 	case IW_MODE_MASTER:
 		return NM_802_11_MODE_AP;
 	case IW_MODE_INFRA:
+	case IW_MODE_AUTO: /* hack for WEXT devices reporting IW_MODE_AUTO */
 		return NM_802_11_MODE_INFRA;
 	default:
 		break;
@@ -150,7 +151,7 @@ wifi_wext_set_mode (WifiData *data, const NM80211Mode mode)
 		return FALSE;
 	}
 
-	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
+	nm_utils_ifname_cpy (wrq.ifr_name, wext->parent.iface);
 	if (ioctl (wext->fd, SIOCSIWMODE, &wrq) < 0) {
 		if (errno != ENODEV) {
 			nm_log_err (LOGD_HW | LOGD_WIFI, "(%s): error setting mode %d",
@@ -174,7 +175,7 @@ wifi_wext_set_powersave (WifiData *data, guint32 powersave)
 	} else
 		wrq.u.power.disabled = 1;
 
-	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
+	nm_utils_ifname_cpy (wrq.ifr_name, wext->parent.iface);
 	if (ioctl (wext->fd, SIOCSIWPOWER, &wrq) < 0) {
 		if (errno != ENODEV) {
 			nm_log_err (LOGD_HW | LOGD_WIFI, "(%s): error setting powersave %" G_GUINT32_FORMAT,
@@ -193,7 +194,7 @@ wifi_wext_get_freq (WifiData *data)
 	struct iwreq wrq;
 
 	memset (&wrq, 0, sizeof (struct iwreq));
-	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
+	nm_utils_ifname_cpy (wrq.ifr_name, wext->parent.iface);
 	if (ioctl (wext->fd, SIOCGIWFREQ, &wrq) < 0) {
 		nm_log_warn (LOGD_HW | LOGD_WIFI,
 		             "(%s): error getting frequency: %s",
@@ -227,7 +228,7 @@ wifi_wext_get_bssid (WifiData *data, guint8 *out_bssid)
 	struct iwreq wrq;
 
 	memset (&wrq, 0, sizeof (wrq));
-	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
+	nm_utils_ifname_cpy (wrq.ifr_name, wext->parent.iface);
 	if (ioctl (wext->fd, SIOCGIWAP, &wrq) < 0) {
 		nm_log_warn (LOGD_HW | LOGD_WIFI,
 		             "(%s): error getting associated BSSID: %s",
@@ -246,7 +247,7 @@ wifi_wext_get_rate (WifiData *data)
 	int err;
 
 	memset (&wrq, 0, sizeof (wrq));
-	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
+	nm_utils_ifname_cpy (wrq.ifr_name, wext->parent.iface);
 	err = ioctl (wext->fd, SIOCGIWRATE, &wrq);
 	return ((err == 0) ? wrq.u.bitrate.value / 1000 : 0);
 }
@@ -356,7 +357,7 @@ wifi_wext_get_qual (WifiData *data)
 	wrq.u.data.pointer = &stats;
 	wrq.u.data.length = sizeof (stats);
 	wrq.u.data.flags = 1;  /* Clear updated flag */
-	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
+	nm_utils_ifname_cpy (wrq.ifr_name, wext->parent.iface);
 
 	if (ioctl (wext->fd, SIOCGIWSTATS, &wrq) < 0) {
 		nm_log_warn (LOGD_HW | LOGD_WIFI,
@@ -393,7 +394,7 @@ wifi_wext_set_mesh_channel (WifiData *data, guint32 channel)
 	struct iwreq wrq;
 
 	memset (&wrq, 0, sizeof (struct iwreq));
-	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
+	nm_utils_ifname_cpy (wrq.ifr_name, wext->parent.iface);
 
 	if (channel > 0) {
 		wrq.u.freq.flags = IW_FREQ_FIXED;
@@ -425,7 +426,7 @@ wifi_wext_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len)
 	wrq.u.essid.length = len;
 	wrq.u.essid.flags = (len > 0) ? 1 : 0; /* 1=enable SSID, 0=disable/any */
 
-	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
+	nm_utils_ifname_cpy (wrq.ifr_name, wext->parent.iface);
 	if (ioctl (wext->fd, SIOCSIWESSID, &wrq) == 0)
 		return TRUE;
 
@@ -448,7 +449,7 @@ wext_can_scan (WifiDataWext *wext)
 	struct iwreq wrq;
 
 	memset (&wrq, 0, sizeof (struct iwreq));
-	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
+	nm_utils_ifname_cpy (wrq.ifr_name, wext->parent.iface);
 	if (ioctl (wext->fd, SIOCSIWSCAN, &wrq) < 0) {
 		if (errno == EOPNOTSUPP)
 			return FALSE;
@@ -466,7 +467,7 @@ wext_get_range (WifiDataWext *wext,
 	struct iwreq wrq;
 
 	memset (&wrq, 0, sizeof (struct iwreq));
-	strncpy (wrq.ifr_name, wext->parent.iface, IFNAMSIZ);
+	nm_utils_ifname_cpy (wrq.ifr_name, wext->parent.iface);
 	wrq.u.data.pointer = (caddr_t) range;
 	wrq.u.data.length = sizeof (struct iw_range);
 
@@ -666,7 +667,7 @@ wifi_wext_is_wifi (const char *iface)
 
 	fd = socket (PF_INET, SOCK_DGRAM, 0);
 	if (fd >= 0) {
-		strncpy (iwr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
+		nm_utils_ifname_cpy (iwr.ifr_ifrn.ifrn_name, iface);
 		if (ioctl (fd, SIOCGIWNAME, &iwr) == 0)
 			is_wifi = TRUE;
 		close (fd);