about summary refs log tree commit diff
path: root/src/devices
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/adsl/meson.build6
-rw-r--r--src/devices/adsl/nm-atm-manager.c1
-rw-r--r--src/devices/adsl/nm-device-adsl.c41
-rw-r--r--src/devices/bluetooth/meson.build6
-rw-r--r--src/devices/bluetooth/nm-bluez-device.c182
-rw-r--r--src/devices/bluetooth/nm-bluez-device.h15
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c1
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.c2
-rw-r--r--src/devices/bluetooth/nm-bluez4-manager.c1
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.c40
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.h3
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c1
-rw-r--r--src/devices/bluetooth/nm-device-bt.c116
-rw-r--r--src/devices/nm-acd-manager.c419
-rw-r--r--src/devices/nm-acd-manager.h24
-rw-r--r--src/devices/nm-device-6lowpan.c8
-rw-r--r--src/devices/nm-device-bond.c17
-rw-r--r--src/devices/nm-device-bridge.c16
-rw-r--r--src/devices/nm-device-dummy.c9
-rw-r--r--src/devices/nm-device-ethernet.c75
-rw-r--r--src/devices/nm-device-factory.c2
-rw-r--r--src/devices/nm-device-infiniband.c19
-rw-r--r--src/devices/nm-device-ip-tunnel.c51
-rw-r--r--src/devices/nm-device-macsec.c25
-rw-r--r--src/devices/nm-device-macvlan.c9
-rw-r--r--src/devices/nm-device-ppp.c47
-rw-r--r--src/devices/nm-device-private.h90
-rw-r--r--src/devices/nm-device-tun.c20
-rw-r--r--src/devices/nm-device-veth.c2
-rw-r--r--src/devices/nm-device-vlan.c20
-rw-r--r--src/devices/nm-device-vxlan.c28
-rw-r--r--src/devices/nm-device-wireguard.c1501
-rw-r--r--src/devices/nm-device-wpan.c19
-rw-r--r--src/devices/nm-device.c2559
-rw-r--r--src/devices/nm-device.h73
-rw-r--r--src/devices/nm-lldp-listener.c13
-rw-r--r--src/devices/ovs/meson.build6
-rw-r--r--src/devices/ovs/nm-device-ovs-bridge.c18
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c39
-rw-r--r--src/devices/ovs/nm-device-ovs-port.c18
-rw-r--r--src/devices/ovs/nm-ovsdb.c3
-rw-r--r--src/devices/team/meson.build6
-rw-r--r--src/devices/team/nm-device-team.c34
-rw-r--r--src/devices/team/nm-team-factory.c1
-rw-r--r--src/devices/tests/meson.build7
-rw-r--r--src/devices/tests/test-acd.c88
-rw-r--r--src/devices/wifi/meson.build14
-rw-r--r--src/devices/wifi/nm-device-iwd.c1139
-rw-r--r--src/devices/wifi/nm-device-iwd.h2
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c11
-rw-r--r--src/devices/wifi/nm-device-wifi-p2p.c1316
-rw-r--r--src/devices/wifi/nm-device-wifi-p2p.h50
-rw-r--r--src/devices/wifi/nm-device-wifi.c346
-rw-r--r--src/devices/wifi/nm-device-wifi.h1
-rw-r--r--src/devices/wifi/nm-iwd-manager.c134
-rw-r--r--src/devices/wifi/nm-wifi-ap.c6
-rw-r--r--src/devices/wifi/nm-wifi-factory.c69
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.c796
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.h114
-rw-r--r--src/devices/wifi/nm-wifi-utils.c7
-rw-r--r--src/devices/wifi/tests/meson.build5
-rw-r--r--src/devices/wifi/tests/test-general.c3
-rw-r--r--src/devices/wwan/libnm-wwan.ver1
-rw-r--r--src/devices/wwan/meson.build14
-rw-r--r--src/devices/wwan/nm-device-modem.c115
-rw-r--r--src/devices/wwan/nm-modem-broadband.c118
-rw-r--r--src/devices/wwan/nm-modem-manager.c1
-rw-r--r--src/devices/wwan/nm-modem-ofono.c102
-rw-r--r--src/devices/wwan/nm-modem.c232
-rw-r--r--src/devices/wwan/nm-modem.h22
-rw-r--r--src/devices/wwan/nm-wwan-factory.c1
71 files changed, 7409 insertions, 2891 deletions
diff --git a/src/devices/adsl/meson.build b/src/devices/adsl/meson.build
index 7ac0e123..f92e809c 100644
--- a/src/devices/adsl/meson.build
+++ b/src/devices/adsl/meson.build
@@ -1,11 +1,11 @@
 sources = files(
   'nm-atm-manager.c',
-  'nm-device-adsl.c'
+  'nm-device-adsl.c',
 )
 
 deps = [
   libudev_dep,
-  nm_dep
+  nm_dep,
 ]
 
 libnm_device_plugin_adsl = shared_module(
@@ -15,7 +15,7 @@ libnm_device_plugin_adsl = shared_module(
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_adsl
diff --git a/src/devices/adsl/nm-atm-manager.c b/src/devices/adsl/nm-atm-manager.c
index 0ff4603d..dddb8342 100644
--- a/src/devices/adsl/nm-atm-manager.c
+++ b/src/devices/adsl/nm-atm-manager.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <gmodule.h>
 #include <libudev.h>
 
diff --git a/src/devices/adsl/nm-device-adsl.c b/src/devices/adsl/nm-device-adsl.c
index c9984f51..b3b87dc7 100644
--- a/src/devices/adsl/nm-device-adsl.c
+++ b/src/devices/adsl/nm-device-adsl.c
@@ -25,13 +25,12 @@
 #include <sys/socket.h>
 #include <linux/atmdev.h>
 #include <linux/atmbr2684.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <unistd.h>
 #include <stdlib.h>
-#include <string.h>
 
+#include "nm-ip4-config.h"
 #include "devices/nm-device-private.h"
 #include "platform/nm-platform.h"
 #include "ppp/nm-ppp-manager-call.h"
@@ -259,8 +258,9 @@ pppoe_vcc_config (NMDeviceAdsl *self)
 	NMDevice *device = NM_DEVICE (self);
 	NMSettingAdsl *s_adsl;
 
-	s_adsl = nm_connection_get_setting_adsl (nm_device_get_applied_connection (device));
-	g_assert (s_adsl);
+	s_adsl = nm_device_get_applied_setting (device, NM_TYPE_SETTING_ADSL);
+
+	g_return_val_if_fail (s_adsl, FALSE);
 
 	/* Set up the VCC */
 	if (!br2684_assign_vcc (self, s_adsl))
@@ -370,8 +370,8 @@ br2684_create_iface (NMDeviceAdsl *self,
 			priv->nas_update_id = g_timeout_add (100, nas_update_cb, self);
 			return NM_ACT_STAGE_RETURN_POSTPONE;
 		}
-		if (errno != EEXIST) {
-			errsv = errno;
+		errsv = errno;
+		if (errsv != EEXIST) {
 			_LOGW (LOGD_ADSL, "failed to create br2684 interface (%d)", errsv);
 			break;
 		}
@@ -389,7 +389,8 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMSettingAdsl *s_adsl;
 	const char *protocol;
 
-	s_adsl = nm_connection_get_setting_adsl (nm_device_get_applied_connection (device));
+	s_adsl = nm_device_get_applied_setting (device, NM_TYPE_SETTING_ADSL);
+
 	g_return_val_if_fail (s_adsl, NM_ACT_STAGE_RETURN_FAILURE);
 
 	protocol = nm_setting_adsl_get_protocol (s_adsl);
@@ -447,9 +448,8 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 	NMDevice *device = NM_DEVICE (user_data);
 
 	/* Ignore PPP IP4 events that come in after initial configuration */
-	if (nm_device_activate_ip4_state_in_conf (device)) {
-		nm_device_activate_schedule_ip4_config_result (device, config);
-	}
+	if (nm_device_activate_ip4_state_in_conf (device))
+		nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 }
 
 static NMActStageReturn
@@ -465,8 +465,11 @@ act_stage3_ip4_config_start (NMDevice *device,
 	const char *ppp_iface;
 
 	req = nm_device_get_act_request (device);
+
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
-	s_adsl = (NMSettingAdsl *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_ADSL);
+
+	s_adsl = nm_device_get_applied_setting (device, NM_TYPE_SETTING_ADSL);
+
 	g_return_val_if_fail (s_adsl, NM_ACT_STAGE_RETURN_FAILURE);
 
 	/* PPPoE uses the NAS interface, not the ATM interface */
@@ -515,6 +518,18 @@ act_stage3_ip4_config_start (NMDevice *device,
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
+static NMActStageReturn
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
+{
+	if (addr_family == AF_INET)
+		return act_stage3_ip4_config_start (device, (NMIP4Config **) out_config, out_failure_reason);
+
+	return NM_DEVICE_CLASS (nm_device_adsl_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
+}
+
 static void
 adsl_cleanup (NMDeviceAdsl *self)
 {
@@ -523,7 +538,7 @@ adsl_cleanup (NMDeviceAdsl *self)
 	if (priv->ppp_manager) {
 		g_signal_handlers_disconnect_by_func (priv->ppp_manager, G_CALLBACK (ppp_state_changed), self);
 		g_signal_handlers_disconnect_by_func (priv->ppp_manager, G_CALLBACK (ppp_ip4_config), self);
-		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL);
+		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL, NULL);
 		g_clear_object (&priv->ppp_manager);
 	}
 
@@ -684,7 +699,7 @@ nm_device_adsl_class_init (NMDeviceAdslClass *klass)
 	device_class->complete_connection = complete_connection;
 
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->deactivate = deactivate;
 
 	obj_properties[PROP_ATM_INDEX] =
diff --git a/src/devices/bluetooth/meson.build b/src/devices/bluetooth/meson.build
index 628a3bc8..b2f67ceb 100644
--- a/src/devices/bluetooth/meson.build
+++ b/src/devices/bluetooth/meson.build
@@ -5,12 +5,12 @@ sources = files(
   'nm-bluez4-manager.c',
   'nm-bluez5-manager.c',
   'nm-bt-error.c',
-  'nm-device-bt.c'
+  'nm-device-bt.c',
 )
 
 deps = [
   libnm_wwan_dep,
-  nm_dep
+  nm_dep,
 ]
 
 if enable_bluez5_dun
@@ -27,7 +27,7 @@ libnm_device_plugin_bluetooth = shared_module(
   link_depends: linker_script_devices,
   install: true,
   install_dir: nm_plugindir,
-  install_rpath: nm_plugindir
+  install_rpath: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_bluetooth
diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c
index b722f692..377ee478 100644
--- a/src/devices/bluetooth/nm-bluez-device.c
+++ b/src/devices/bluetooth/nm-bluez-device.c
@@ -23,8 +23,6 @@
 
 #include "nm-bluez-device.h"
 
-#include <string.h>
-
 #include "nm-core-internal.h"
 #include "nm-bt-error.h"
 #include "nm-bluez-common.h"
@@ -451,6 +449,9 @@ nm_bluez_device_disconnect (NMBluezDevice *self)
 
 	g_return_if_fail (priv->dbus_connection);
 
+	/* FIXME: if we are in the process of connecting and cancel the
+	 * connection attempt, we must complete the pending connect request.
+	 * However, we must also ensure that we don't leave a connected device. */
 	if (priv->connection_bt_type == NM_BT_CAPABILITY_DUN) {
 		if (priv->bluez_version == 4) {
 			/* Can't pass a NULL interface name through dbus to bluez, so just
@@ -496,76 +497,109 @@ out:
 }
 
 static void
-bluez_connect_cb (GDBusConnection *dbus_connection,
+_connect_complete (NMBluezDevice *self,
+                   const char *device,
+                   NMBluezDeviceConnectCallback callback,
+                   gpointer callback_user_data,
+                   GError *error)
+{
+	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
+
+	nm_assert ((device || error) && !(device && error));
+
+	if (   device
+	    && priv->bluez_version == 5) {
+		priv->connected = TRUE;
+		_notify (self, PROP_CONNECTED);
+	}
+
+	if (callback)
+		callback (self, device, error, callback_user_data);
+}
+
+static void
+_connect_cb (GObject *source_object,
                   GAsyncResult *res,
                   gpointer user_data)
 {
-	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data);
-	GObject *result_object = g_async_result_get_source_object (G_ASYNC_RESULT (result));
-	NMBluezDevice *self = NM_BLUEZ_DEVICE (result_object);
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GError *error = NULL;
-	char *device;
-	GVariant *variant;
+	gs_unref_object NMBluezDevice *self = NULL;
+	NMBluezDevicePrivate *priv;
+	NMBluezDeviceConnectCallback callback;
+	gpointer callback_user_data;
+	gs_free_error GError *error = NULL;
+	char *device = NULL;
+	gs_unref_variant GVariant *variant = NULL;
 
-	variant = g_dbus_connection_call_finish (dbus_connection, res, &error);
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
 
-	if (!variant) {
-		g_simple_async_result_take_error (result, error);
-	} else {
-		g_variant_get (variant, "(s)", &device);
+	priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
 
-		g_simple_async_result_set_op_res_gpointer (result,
-		                                           g_strdup (device),
-		                                           g_free);
+	variant = _nm_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, G_VARIANT_TYPE ("(s)"), &error);
+	if (variant) {
+		g_variant_get (variant, "(s)", &device);
 		priv->b4_iface = device;
-		g_variant_unref (variant);
 	}
 
-	g_simple_async_result_complete (result);
-	g_object_unref (result);
-	g_object_unref (result_object);
+	_connect_complete (self, device, callback, callback_user_data, error);
 }
 
 #if WITH_BLUEZ5_DUN
 static void
-bluez5_dun_connect_cb (NMBluez5DunContext *context,
-                   const char *device,
-                   GError *error,
-                   gpointer user_data)
+_connect_cb_bluez5_dun (NMBluez5DunContext *context,
+                        const char *device,
+                        GError *error,
+                        gpointer user_data)
 {
-	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data);
+	gs_unref_object NMBluezDevice *self = NULL;
+	gs_unref_object GCancellable *cancellable = NULL;
+	NMBluezDeviceConnectCallback callback;
+	gpointer callback_user_data;
+	gs_free_error GError *cancelled_error = NULL;
 
-	if (error) {
-		g_simple_async_result_take_error (result, error);
-	} else {
-		g_simple_async_result_set_op_res_gpointer (result,
-		                                           g_strdup (device),
-		                                           g_free);
-	}
+	nm_utils_user_data_unpack (user_data, &self, &cancellable, &callback, &callback_user_data);
+
+	/* FIXME(shutdown): the async operation nm_bluez5_dun_connect() should be cancellable.
+	 * Fake it here. */
+	if (g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error))
+		error = cancelled_error;
 
-	g_simple_async_result_complete (result);
-	g_object_unref (result);
+	_connect_complete (self, device, callback, callback_user_data, error);
 }
-#endif
+#else /* WITH_BLUEZ5_DUN */
+static void
+_connect_cb_bluez5_dun_idle_no_b5 (gpointer user_data,
+                                   GCancellable *cancellable)
+{
+	gs_unref_object NMBluezDevice *self = NULL;
+	NMBluezDeviceConnectCallback callback;
+	gpointer callback_user_data;
+	gs_free_error GError *error = NULL;
+
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
+
+	if (!g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+		g_set_error (&error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "NetworkManager built without support for Bluez 5");
+	}
+	callback (self, NULL, error, callback_user_data);
+}
+#endif /* WITH_BLUEZ5_DUN */
 
 void
 nm_bluez_device_connect_async (NMBluezDevice *self,
                                NMBluetoothCapabilities connection_bt_type,
-                               GAsyncReadyCallback callback,
-                               gpointer user_data)
+                               GCancellable *cancellable,
+                               NMBluezDeviceConnectCallback callback,
+                               gpointer callback_user_data)
 {
-	GSimpleAsyncResult *simple;
 	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
 	const char *dbus_iface = NULL;
 	const char *connect_type = NULL;
 
 	g_return_if_fail (priv->capabilities & connection_bt_type & (NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP));
 
-	simple = g_simple_async_result_new (G_OBJECT (self),
-	                                    callback,
-	                                    user_data,
-	                                    nm_bluez_device_connect_async);
 	priv->connection_bt_type = connection_bt_type;
 
 	if (connection_bt_type == NM_BT_CAPABILITY_NAP) {
@@ -582,19 +616,29 @@ nm_bluez_device_connect_async (NMBluezDevice *self,
 #if WITH_BLUEZ5_DUN
 			if (priv->b5_dun_context == NULL)
 				priv->b5_dun_context = nm_bluez5_dun_new (priv->adapter_address, priv->address);
-			nm_bluez5_dun_connect (priv->b5_dun_context, bluez5_dun_connect_cb, simple);
+			nm_bluez5_dun_connect (priv->b5_dun_context,
+			                       _connect_cb_bluez5_dun,
+			                       nm_utils_user_data_pack (g_object_ref (self),
+			                                                nm_g_object_ref (cancellable),
+			                                                callback,
+			                                                callback_user_data));
 #else
-			g_simple_async_result_set_error (simple,
-							 NM_BT_ERROR,
-							 NM_BT_ERROR_DUN_CONNECT_FAILED,
-							 "NetworkManager built without support for Bluez 5");
-			g_simple_async_result_complete (simple);
+			if (callback) {
+				nm_utils_invoke_on_idle (_connect_cb_bluez5_dun_idle_no_b5,
+				                         nm_utils_user_data_pack (g_object_ref (self),
+				                                                  callback,
+				                                                  callback_user_data),
+				                         cancellable);
+			}
 #endif
 			return;
 		}
 	} else
-		g_assert_not_reached ();
+		g_return_if_reached ();
 
+	/* FIXME: we need to remember that a connect is in progress.
+	 * So, if the request gets cancelled, that we disconnect the
+	 * connection that was established in the meantime. */
 	g_dbus_connection_call (priv->dbus_connection,
 	                        NM_BLUEZ_SERVICE,
 	                        priv->path,
@@ -604,37 +648,11 @@ nm_bluez_device_connect_async (NMBluezDevice *self,
 	                        NULL,
 	                        G_DBUS_CALL_FLAGS_NONE,
 	                        20000,
-	                        NULL,
-	                        (GAsyncReadyCallback) bluez_connect_cb,
-	                        simple);
-}
-
-const char *
-nm_bluez_device_connect_finish (NMBluezDevice *self,
-                                GAsyncResult *result,
-                                GError **error)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GSimpleAsyncResult *simple;
-	const char *device;
-
-	g_return_val_if_fail (g_simple_async_result_is_valid (result,
-	                                                      G_OBJECT (self),
-	                                                      nm_bluez_device_connect_async),
-	                      NULL);
-
-	simple = (GSimpleAsyncResult *) result;
-
-	if (g_simple_async_result_propagate_error (simple, error))
-		return NULL;
-
-	device = (const char *) g_simple_async_result_get_op_res_gpointer (simple);
-	if (device && priv->bluez_version == 5) {
-		priv->connected = TRUE;
-		_notify (self, PROP_CONNECTED);
-	}
-
-	return device;
+	                        cancellable,
+	                        _connect_cb,
+	                        nm_utils_user_data_pack (g_object_ref (self),
+	                                                 callback,
+	                                                 callback_user_data));
 }
 
 /*****************************************************************************/
diff --git a/src/devices/bluetooth/nm-bluez-device.h b/src/devices/bluetooth/nm-bluez-device.h
index f8a1872f..d2d0beb0 100644
--- a/src/devices/bluetooth/nm-bluez-device.h
+++ b/src/devices/bluetooth/nm-bluez-device.h
@@ -66,16 +66,17 @@ guint32 nm_bluez_device_get_capabilities (NMBluezDevice *self);
 
 gboolean nm_bluez_device_get_connected (NMBluezDevice *self);
 
+typedef void (*NMBluezDeviceConnectCallback) (NMBluezDevice *self,
+                                              const char *device,
+                                              GError *error,
+                                              gpointer user_data);
+
 void
 nm_bluez_device_connect_async (NMBluezDevice *self,
                                NMBluetoothCapabilities connection_bt_type,
-                               GAsyncReadyCallback callback,
-                               gpointer user_data);
-
-const char *
-nm_bluez_device_connect_finish (NMBluezDevice *self,
-                                GAsyncResult *result,
-                                GError **error);
+                               GCancellable *cancellable,
+                               NMBluezDeviceConnectCallback callback,
+                               gpointer callback_user_data);
 
 void
 nm_bluez_device_disconnect (NMBluezDevice *self);
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index 711f2e57..dc85a8b2 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -21,7 +21,6 @@
 #include "nm-default.h"
 
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
 #include <gmodule.h>
 
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.c b/src/devices/bluetooth/nm-bluez4-adapter.c
index c8ef7a27..3a456deb 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.c
+++ b/src/devices/bluetooth/nm-bluez4-adapter.c
@@ -22,8 +22,6 @@
 
 #include "nm-bluez4-adapter.h"
 
-#include <string.h>
-
 #include "nm-dbus-interface.h"
 #include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
diff --git a/src/devices/bluetooth/nm-bluez4-manager.c b/src/devices/bluetooth/nm-bluez4-manager.c
index 1fe02f18..82d995be 100644
--- a/src/devices/bluetooth/nm-bluez4-manager.c
+++ b/src/devices/bluetooth/nm-bluez4-manager.c
@@ -24,7 +24,6 @@
 #include "nm-bluez4-manager.h"
 
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-bluez4-adapter.h"
diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/devices/bluetooth/nm-bluez5-dun.c
index ca09b276..ff3a0da9 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/devices/bluetooth/nm-bluez5-dun.c
@@ -27,7 +27,6 @@
 #include <net/ethernet.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
-#include <errno.h>
 #include <fcntl.h>
 
 #include "nm-bluez5-dun.h"
@@ -57,6 +56,7 @@ dun_connect (NMBluez5DunContext *context)
 	char tty[100];
 	const int ttylen = sizeof (tty) - 1;
 	GError *error = NULL;
+	int errsv;
 
 	struct rfcomm_dev_req req = {
 		.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP),
@@ -66,10 +66,10 @@ dun_connect (NMBluez5DunContext *context)
 
 	context->rfcomm_fd = socket (AF_BLUETOOTH, SOCK_STREAM | SOCK_CLOEXEC, BTPROTO_RFCOMM);
 	if (context->rfcomm_fd < 0) {
-		int errsv = errno;
+		errsv = errno;
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to create RFCOMM socket: (%d) %s",
-		                     errsv, strerror (errsv));
+		                     errsv, nm_strerror_native (errsv));
 		goto done;
 	}
 
@@ -78,20 +78,20 @@ dun_connect (NMBluez5DunContext *context)
 	sa.rc_channel = 0;
 	memcpy (&sa.rc_bdaddr, &context->src, ETH_ALEN);
 	if (bind (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof(sa))) {
-		int errsv = errno;
+		errsv = errno;
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to bind socket: (%d) %s",
-		                     errsv, strerror (errsv));
+		                     errsv, nm_strerror_native (errsv));
 		goto done;
 	}
 
 	sa.rc_channel = context->rfcomm_channel;
 	memcpy (&sa.rc_bdaddr, &context->dst, ETH_ALEN);
 	if (connect (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof (sa)) ) {
-		int errsv = errno;
+		errsv = errno;
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to connect to remote device: (%d) %s",
-		                     errsv, strerror (errsv));
+		                     errsv, nm_strerror_native (errsv));
 		goto done;
 	}
 
@@ -103,10 +103,10 @@ dun_connect (NMBluez5DunContext *context)
 	memcpy (&req.dst, &context->dst, ETH_ALEN);
 	devid = ioctl (context->rfcomm_fd, RFCOMMCREATEDEV, &req);
 	if (devid < 0) {
-		int errsv = errno;
+		errsv = errno;
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to create rfcomm device: (%d) %s",
-		                     errsv, strerror (errsv));
+		                     errsv, nm_strerror_native (errsv));
 		goto done;
 	}
 	context->rfcomm_id = devid;
@@ -250,7 +250,8 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da
 	sdp_list_t *search, *attrs;
 	uuid_t svclass;
 	uint16_t attr;
-	int fd, err, fd_err = 0;
+	int fd, fd_err = 0;
+	int err;
 	socklen_t len = sizeof (fd_err);
 	GError *error = NULL;
 
@@ -258,19 +259,19 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da
 
 	fd = g_io_channel_unix_get_fd (channel);
 	if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &fd_err, &len) < 0) {
-		nm_log_dbg (LOGD_BT, "(%s -> %s): getsockopt error=%d",
-		            context->src_str, context->dst_str, errno);
 		err = errno;
+		nm_log_dbg (LOGD_BT, "(%s -> %s): getsockopt error=%d",
+		            context->src_str, context->dst_str, err);
 	} else {
+		err = fd_err;
 		nm_log_dbg (LOGD_BT, "(%s -> %s): SO_ERROR error=%d",
 		            context->src_str, context->dst_str, fd_err);
-		err = fd_err;
 	}
 
 	if (err != 0) {
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Error on Service Discovery socket: (%d) %s",
-		                     err, strerror (err));
+		                     err, nm_strerror_native (err));
 		goto done;
 	}
 
@@ -297,7 +298,7 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da
 		error = g_error_new (NM_BT_ERROR,
 		                     NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Error starting Service Discovery: (%d) %s",
-		                     err, strerror (err));
+		                     err, nm_strerror_native (err));
 	}
 
 	sdp_list_free (attrs, NULL);
@@ -342,13 +343,14 @@ nm_bluez5_dun_connect (NMBluez5DunContext *context,
 
 	if (context->rfcomm_channel != -1) {
 		nm_log_dbg (LOGD_BT, "(%s): channel number on device %s cached: %d",
-			    context->src_str, context->dst_str, context->rfcomm_channel);
+		            context->src_str, context->dst_str, context->rfcomm_channel);
+		/* FIXME: don't invoke the callback synchronously. */
 		dun_connect (context);
 		return;
 	}
 
 	nm_log_dbg (LOGD_BT, "(%s): starting channel number discovery for device %s",
-		    context->src_str, context->dst_str);
+	            context->src_str, context->dst_str);
 
 	context->sdp_session = sdp_connect (&context->src, &context->dst, SDP_NON_BLOCKING);
 	if (!context->sdp_session) {
@@ -357,11 +359,13 @@ nm_bluez5_dun_connect (NMBluez5DunContext *context,
 
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Failed to connect to the SDP server: (%d) %s",
-		                      err, strerror (err));
+		                      err, nm_strerror_native (err));
+		/* FIXME: don't invoke the callback synchronously. */
 		context->callback (context, NULL, error, context->user_data);
 		return;
 	}
 
+	/* FIXME(shutdown): make connect cancellable. */
 	channel = g_io_channel_unix_new (sdp_get_socket (context->sdp_session));
 	context->sdp_watch_id = g_io_add_watch (channel,
 	                                        G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
diff --git a/src/devices/bluetooth/nm-bluez5-dun.h b/src/devices/bluetooth/nm-bluez5-dun.h
index 124c1a05..b75e4399 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.h
+++ b/src/devices/bluetooth/nm-bluez5-dun.h
@@ -32,7 +32,8 @@ NMBluez5DunContext *nm_bluez5_dun_new (const char *adapter,
                                        const char *remote);
 
 void nm_bluez5_dun_connect (NMBluez5DunContext *context,
-                            NMBluez5DunFunc callback, gpointer user_data);
+                            NMBluez5DunFunc callback,
+                            gpointer user_data);
 
 /* Clean up connection resources */
 void nm_bluez5_dun_cleanup (NMBluez5DunContext *context);
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
index e984212b..be15d824 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ b/src/devices/bluetooth/nm-bluez5-manager.c
@@ -25,7 +25,6 @@
 #include "nm-bluez5-manager.h"
 
 #include <signal.h>
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-core-internal.h"
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index f4a1b709..e79251ce 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -23,7 +23,6 @@
 #include "nm-device-bt.h"
 
 #include <stdio.h>
-#include <string.h>
 
 #include "nm-bluez-common.h"
 #include "nm-bluez-device.h"
@@ -39,6 +38,7 @@
 #include "settings/nm-settings-connection.h"
 #include "nm-utils.h"
 #include "nm-bt-error.h"
+#include "nm-ip4-config.h"
 #include "platform/nm-platform.h"
 
 #include "devices/wwan/nm-modem-manager.h"
@@ -78,7 +78,9 @@ typedef struct {
 
 	char *rfcomm_iface;
 	NMModem *modem;
-	guint32 timeout_id;
+	guint timeout_id;
+
+	GCancellable *cancellable;
 
 	guint32 bt_type;  /* BT type of the current connection */
 } NMDeviceBtPrivate;
@@ -318,8 +320,6 @@ complete_connection (NMDevice *device,
 
 		if (s_gsm) {
 			fallback_prefix = _("GSM connection");
-			if (!nm_setting_gsm_get_number (s_gsm))
-				g_object_set (G_OBJECT (s_gsm), NM_SETTING_GSM_NUMBER, "*99#", NULL);
 		} else {
 			fallback_prefix = _("CDMA connection");
 			if (!nm_setting_cdma_get_number (s_cdma))
@@ -398,9 +398,9 @@ ppp_failed (NMModem *modem,
 	case NM_DEVICE_STATE_SECONDARIES:
 	case NM_DEVICE_STATE_ACTIVATED:
 		if (nm_device_activate_ip4_state_in_conf (device))
-			nm_device_activate_schedule_ip4_config_timeout (device);
+			nm_device_activate_schedule_ip_config_timeout (device, AF_INET);
 		else if (nm_device_activate_ip6_state_in_conf (device))
-			nm_device_activate_schedule_ip6_config_timeout (device);
+			nm_device_activate_schedule_ip_config_timeout (device, AF_INET6);
 		else if (nm_device_activate_ip4_state_done (device)) {
 			nm_device_ip_method_failed (device,
 			                            AF_INET,
@@ -542,7 +542,7 @@ modem_ip4_config_result (NMModem *modem,
 		                            AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 	} else
-		nm_device_activate_schedule_ip4_config_result (device, config);
+		nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 }
 
 static void
@@ -672,6 +672,7 @@ component_added (NMDevice *device, GObject *component)
 
 	/* Got the modem */
 	nm_clear_g_source (&priv->timeout_id);
+	nm_clear_g_cancellable (&priv->cancellable);
 
 	/* Can only accept the modem in stage2, but since the interface matched
 	 * what we were expecting, don't let anything else claim the modem either.
@@ -715,8 +716,11 @@ static gboolean
 modem_find_timeout (gpointer user_data)
 {
 	NMDeviceBt *self = NM_DEVICE_BT (user_data);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+
+	priv->timeout_id = 0;
+	nm_clear_g_cancellable (&priv->cancellable);
 
-	NM_DEVICE_BT_GET_PRIVATE (self)->timeout_id = 0;
 	nm_device_state_changed (NM_DEVICE (self),
 	                         NM_DEVICE_STATE_FAILED,
 	                         NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND);
@@ -738,8 +742,8 @@ check_connect_continue (NMDeviceBt *self)
 	       "Activation: (bluetooth) Stage 2 of 5 (Device Configure) successful. Will connect via %s.",
 	       dun ? "DUN" : (pan ? "PAN" : "unknown"));
 
-	/* Kill the connect timeout since we're connected now */
 	nm_clear_g_source (&priv->timeout_id);
+	nm_clear_g_cancellable (&priv->cancellable);
 
 	if (pan) {
 		/* Bluez says we're connected now.  Start IP config. */
@@ -755,25 +759,25 @@ check_connect_continue (NMDeviceBt *self)
 }
 
 static void
-bluez_connect_cb (GObject *object,
-                  GAsyncResult *res,
-                  void *user_data)
+bluez_connect_cb (NMBluezDevice *bt_device,
+                  const char *device_name,
+                  GError *error,
+                  gpointer user_data)
 {
-	gs_unref_object NMDeviceBt *self = NM_DEVICE_BT (user_data);
+	gs_unref_object NMDeviceBt *self = user_data;
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-	GError *error = NULL;
-	const char *device;
 
-	device = nm_bluez_device_connect_finish (NM_BLUEZ_DEVICE (object),
-	                                         res, &error);
+	if (nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	nm_clear_g_source (&priv->timeout_id);
+	g_clear_object (&priv->cancellable);
 
 	if (!nm_device_is_activating (NM_DEVICE (self)))
 		return;
 
-	if (!device) {
+	if (!device_name) {
 		_LOGW (LOGD_BT, "Error connecting with bluez: %s", error->message);
-		g_clear_error (&error);
-
 		nm_device_state_changed (NM_DEVICE (self),
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_BT_FAILED);
@@ -782,10 +786,10 @@ bluez_connect_cb (GObject *object,
 
 	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
 		g_free (priv->rfcomm_iface);
-		priv->rfcomm_iface = g_strdup (device);
+		priv->rfcomm_iface = g_strdup (device_name);
 	} else if (priv->bt_type == NM_BT_CAPABILITY_NAP) {
-		if (!nm_device_set_ip_iface (NM_DEVICE (self), device)) {
-			_LOGW (LOGD_BT, "Error connecting with bluez: cannot find device %s", device);
+		if (!nm_device_set_ip_iface (NM_DEVICE (self), device_name)) {
+			_LOGW (LOGD_BT, "Error connecting with bluez: cannot find device %s", device_name);
 			nm_device_state_changed (NM_DEVICE (self),
 			                         NM_DEVICE_STATE_FAILED,
 			                         NM_DEVICE_STATE_REASON_BT_FAILED);
@@ -843,10 +847,13 @@ static gboolean
 bt_connect_timeout (gpointer user_data)
 {
 	NMDeviceBt *self = NM_DEVICE_BT (user_data);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 
 	_LOGD (LOGD_BT, "initial connection timed out");
 
-	NM_DEVICE_BT_GET_PRIVATE (self)->timeout_id = 0;
+	priv->timeout_id = 0;
+	nm_clear_g_cancellable (&priv->cancellable);
+
 	nm_device_state_changed (NM_DEVICE (self),
 	                         NM_DEVICE_STATE_FAILED,
 	                         NM_DEVICE_STATE_REASON_BT_FAILED);
@@ -876,45 +883,45 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	_LOGD (LOGD_BT, "requesting connection to the device");
 
-	/* Connect to the BT device */
-	nm_bluez_device_connect_async (priv->bt_device,
-	                               priv->bt_type & (NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP),
-	                               bluez_connect_cb, g_object_ref (device));
-
 	nm_clear_g_source (&priv->timeout_id);
+	nm_clear_g_cancellable (&priv->cancellable);
+
 	priv->timeout_id = g_timeout_add_seconds (30, bt_connect_timeout, device);
+	priv->cancellable = g_cancellable_new ();
+
+	nm_bluez_device_connect_async (priv->bt_device,
+	                               priv->bt_type & (NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP),
+	                               priv->cancellable,
+	                               bluez_connect_cb,
+	                               g_object_ref (self));
 
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
 
+	nm_assert_addr_family (addr_family);
+
 	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
-		return nm_modem_stage3_ip4_config_start (priv->modem,
-		                                         device,
-		                                         NM_DEVICE_CLASS (nm_device_bt_parent_class),
-		                                         out_failure_reason);
+		if (addr_family == AF_INET) {
+			return nm_modem_stage3_ip4_config_start (priv->modem,
+			                                         device,
+			                                         NM_DEVICE_CLASS (nm_device_bt_parent_class),
+			                                         out_failure_reason);
+		} else {
+			return nm_modem_stage3_ip6_config_start (priv->modem,
+			                                         device,
+			                                         out_failure_reason);
+		}
 	}
 
-	return NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
-
-	if (priv->bt_type == NM_BT_CAPABILITY_DUN)
-		return nm_modem_stage3_ip6_config_start (priv->modem, device, out_failure_reason);
-
-	return NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_bt_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static void
@@ -925,6 +932,9 @@ deactivate (NMDevice *device)
 	priv->have_iface = FALSE;
 	priv->connected = FALSE;
 
+	nm_clear_g_source (&priv->timeout_id);
+	nm_clear_g_cancellable (&priv->cancellable);
+
 	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
 		if (priv->modem) {
 			nm_modem_deactivate (priv->modem, device);
@@ -942,8 +952,6 @@ deactivate (NMDevice *device)
 	if (priv->bt_type != NM_BT_CAPABILITY_NONE)
 		nm_bluez_device_disconnect (priv->bt_device);
 
-	nm_clear_g_source (&priv->timeout_id);
-
 	priv->bt_type = NM_BT_CAPABILITY_NONE;
 
 	g_free (priv->rfcomm_iface);
@@ -1128,6 +1136,7 @@ dispose (GObject *object)
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
 
 	nm_clear_g_source (&priv->timeout_id);
+	nm_clear_g_cancellable (&priv->cancellable);
 
 	g_signal_handlers_disconnect_matched (priv->bt_device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object);
 
@@ -1191,8 +1200,7 @@ nm_device_bt_class_init (NMDeviceBtClass *klass)
 	device_class->can_auto_connect = can_auto_connect;
 	device_class->deactivate = deactivate;
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->check_connection_available = check_connection_available;
 	device_class->complete_connection = complete_connection;
diff --git a/src/devices/nm-acd-manager.c b/src/devices/nm-acd-manager.c
index 035487a3..a8f7a63a 100644
--- a/src/devices/nm-acd-manager.c
+++ b/src/devices/nm-acd-manager.c
@@ -39,40 +39,23 @@ typedef enum {
 typedef struct {
 	in_addr_t address;
 	gboolean duplicate;
-	NMAcdManager *manager;
-	NAcd *acd;
-	GIOChannel *channel;
-	guint event_id;
+	NAcdProbe *probe;
 } AddressInfo;
 
-enum {
-	PROBE_TERMINATED,
-	LAST_SIGNAL,
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
+struct _NMAcdManager {
 	int            ifindex;
 	guint8         hwaddr[ETH_ALEN];
 	State          state;
 	GHashTable    *addresses;
 	guint          completed;
-} NMAcdManagerPrivate;
+	NAcd          *acd;
+	GIOChannel    *channel;
+	guint          event_id;
 
-struct _NMAcdManager {
-	GObject parent;
-	NMAcdManagerPrivate _priv;
+	NMAcdCallbacks callbacks;
+	gpointer user_data;
 };
 
-struct _NMAcdManagerClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMAcdManager, nm_acd_manager, G_TYPE_OBJECT)
-
-#define NM_ACD_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMAcdManager, NM_IS_ACD_MANAGER)
-
 /*****************************************************************************/
 
 #define _NMLOG_DOMAIN         LOGD_IP4
@@ -80,14 +63,13 @@ G_DEFINE_TYPE (NMAcdManager, nm_acd_manager, G_TYPE_OBJECT)
 #define _NMLOG(level, ...) \
     G_STMT_START { \
         char _sbuf[64]; \
-        int _ifindex = (self) ? NM_ACD_MANAGER_GET_PRIVATE (self)->ifindex : 0; \
         \
         nm_log ((level), _NMLOG_DOMAIN, \
-                nm_platform_link_get_name (NM_PLATFORM_GET, _ifindex), \
+                self && self->ifindex > 0 ? nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex) : NULL, \
                 NULL, \
                 "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                 _NMLOG_PREFIX_NAME, \
-                self ? nm_sprintf_buf (_sbuf, "[%p,%d]", self, _ifindex) : "" \
+                self ? nm_sprintf_buf (_sbuf, "[%p,%d]", self, self->ifindex) : "" \
                 _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
     } G_STMT_END
 
@@ -111,32 +93,25 @@ _acd_event_to_string (unsigned int event)
 	return NULL;
 }
 
-#define acd_event_to_string(event) NM_UTILS_LOOKUP_STR (_acd_event_to_string, event)
+#define acd_event_to_string_a(event) NM_UTILS_LOOKUP_STR_A (_acd_event_to_string, event)
 
 static const char *
-_acd_error_to_string (int error)
+acd_error_to_string (int error)
 {
 	if (error < 0)
-		return strerror(-error);
+		return nm_strerror_native (-error);
 
 	switch (error) {
 	case _N_ACD_E_SUCCESS:
 		return "success";
-	case N_ACD_E_DONE:
-		return "no more events (engine running)";
-	case N_ACD_E_STOPPED:
-		return "no more events (engine stopped)";
 	case N_ACD_E_PREEMPTED:
 		return "preempted";
 	case N_ACD_E_INVALID_ARGUMENT:
 		return "invalid argument";
-	case N_ACD_E_BUSY:
-		return "busy";
 	}
-	return NULL;
-}
 
-#define acd_error_to_string(error) NM_UTILS_LOOKUP_STR (_acd_error_to_string, error)
+	g_return_val_if_reached (NULL);
+}
 
 /*****************************************************************************/
 
@@ -152,21 +127,18 @@ _acd_error_to_string (int error)
 gboolean
 nm_acd_manager_add_address (NMAcdManager *self, in_addr_t address)
 {
-	NMAcdManagerPrivate *priv;
 	AddressInfo *info;
 
-	g_return_val_if_fail (NM_IS_ACD_MANAGER (self), FALSE);
-	priv = NM_ACD_MANAGER_GET_PRIVATE (self);
-	g_return_val_if_fail (priv->state == STATE_INIT, FALSE);
+	g_return_val_if_fail (self, FALSE);
+	g_return_val_if_fail (self->state == STATE_INIT, FALSE);
 
-	if (g_hash_table_lookup (priv->addresses, GUINT_TO_POINTER (address)))
+	if (g_hash_table_lookup (self->addresses, GUINT_TO_POINTER (address)))
 		return FALSE;
 
 	info = g_slice_new0 (AddressInfo);
 	info->address = address;
-	info->manager = self;
 
-	g_hash_table_insert (priv->addresses, GUINT_TO_POINTER (address), info);
+	g_hash_table_insert (self->addresses, GUINT_TO_POINTER (address), info);
 
 	return TRUE;
 }
@@ -174,115 +146,142 @@ nm_acd_manager_add_address (NMAcdManager *self, in_addr_t address)
 static gboolean
 acd_event (GIOChannel *source, GIOCondition condition, gpointer data)
 {
-	AddressInfo *info = data;
-	NMAcdManager *self = info->manager;
-	NMAcdManagerPrivate *priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+	NMAcdManager *self = data;
 	NAcdEvent *event;
+	AddressInfo *info;
+	gboolean emit_probe_terminated = FALSE;
 	char address_str[INET_ADDRSTRLEN];
 	gs_free char *hwaddr_str = NULL;
 	int r;
 
-	if (   n_acd_dispatch (info->acd)
-	    || n_acd_pop_event (info->acd, &event))
+	if (n_acd_dispatch (self->acd))
 		return G_SOURCE_CONTINUE;
 
-	switch (event->event) {
-	case N_ACD_EVENT_READY:
-		info->duplicate = FALSE;
-		if (priv->state == STATE_ANNOUNCING) {
-			r = n_acd_announce (info->acd, N_ACD_DEFEND_ONCE);
-			if (r) {
-				_LOGW ("couldn't announce address %s on interface '%s': %s",
-				       nm_utils_inet4_ntop (info->address, address_str),
-				       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex),
-				       acd_error_to_string (r));
-			} else {
-				_LOGD ("announcing address %s",
-				       nm_utils_inet4_ntop (info->address, address_str));
+	while (   !n_acd_pop_event (self->acd, &event)
+	       && event) {
+		gboolean check_probing_done = FALSE;
+
+		switch (event->event) {
+		case N_ACD_EVENT_READY:
+			n_acd_probe_get_userdata (event->ready.probe, (void **) &info);
+			info->duplicate = FALSE;
+			if (self->state == STATE_ANNOUNCING) {
+				/* fake probe ended, start announcing */
+				r = n_acd_probe_announce (info->probe, N_ACD_DEFEND_ONCE);
+				if (r) {
+					_LOGW ("couldn't announce address %s on interface '%s': %s",
+					       nm_utils_inet4_ntop (info->address, address_str),
+					       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
+					       acd_error_to_string (r));
+				} else {
+					_LOGD ("announcing address %s",
+					       nm_utils_inet4_ntop (info->address, address_str));
+				}
 			}
+			check_probing_done = TRUE;
+			break;
+		case N_ACD_EVENT_USED:
+			n_acd_probe_get_userdata (event->used.probe, (void **) &info);
+			info->duplicate = TRUE;
+			check_probing_done = TRUE;
+			break;
+		case N_ACD_EVENT_DEFENDED:
+			n_acd_probe_get_userdata (event->defended.probe, (void **) &info);
+			_LOGD ("defended address %s from host %s",
+			       nm_utils_inet4_ntop (info->address, address_str),
+			       (hwaddr_str = nm_utils_hwaddr_ntoa (event->defended.sender,
+			                                           event->defended.n_sender)));
+			break;
+		case N_ACD_EVENT_CONFLICT:
+			n_acd_probe_get_userdata (event->conflict.probe, (void **) &info);
+			_LOGW ("conflict for address %s detected with host %s on interface '%s'",
+			       nm_utils_inet4_ntop (info->address, address_str),
+			       (hwaddr_str = nm_utils_hwaddr_ntoa (event->defended.sender,
+			                                           event->defended.n_sender)),
+			       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex));
+			break;
+		default:
+			_LOGD ("unhandled event '%s'", acd_event_to_string_a (event->event));
+			break;
+		}
+
+		if (   check_probing_done
+		    && self->state == STATE_PROBING
+		    && ++self->completed == g_hash_table_size (self->addresses)) {
+			self->state = STATE_PROBE_DONE;
+			emit_probe_terminated = TRUE;
 		}
-		break;
-	case N_ACD_EVENT_USED:
-		info->duplicate = TRUE;
-		break;
-	case N_ACD_EVENT_DEFENDED:
-		_LOGD ("defended address %s from host %s",
-		       nm_utils_inet4_ntop (info->address, address_str),
-		       (hwaddr_str = nm_utils_hwaddr_ntoa (event->defended.sender,
-		                                           event->defended.n_sender)));
-		break;
-	case N_ACD_EVENT_CONFLICT:
-		_LOGW ("conflict for address %s detected with host %s on interface '%s'",
-		       nm_utils_inet4_ntop (info->address, address_str),
-		       (hwaddr_str = nm_utils_hwaddr_ntoa (event->defended.sender,
-		                                           event->defended.n_sender)),
-		       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex));
-		break;
-	default:
-		_LOGD ("event '%s' for address %s",
-		       acd_event_to_string (event->event),
-		       nm_utils_inet4_ntop (info->address, address_str));
-		return G_SOURCE_CONTINUE;
 	}
 
-	if (   priv->state == STATE_PROBING
-	    && ++priv->completed == g_hash_table_size (priv->addresses)) {
-		priv->state = STATE_PROBE_DONE;
-		g_signal_emit (self, signals[PROBE_TERMINATED], 0);
+	if (emit_probe_terminated) {
+		if (self->callbacks.probe_terminated_callback) {
+			self->callbacks.probe_terminated_callback (self,
+			                                           self->user_data);
+		}
 	}
 
 	return G_SOURCE_CONTINUE;
 }
 
 static gboolean
-acd_probe_start (NMAcdManager *self,
-                 AddressInfo *info,
-                 guint64 timeout)
+acd_probe_add (NMAcdManager *self,
+               AddressInfo *info,
+               guint64 timeout)
 {
-	NMAcdManagerPrivate *priv = NM_ACD_MANAGER_GET_PRIVATE (self);
-	NAcdConfig *config;
-	int r, fd;
+	NAcdProbeConfig *probe_config;
+	int r;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
-	r = n_acd_new (&info->acd);
+	r = n_acd_probe_config_new (&probe_config);
 	if (r) {
-		_LOGW ("could not create ACD for %s on interface '%s': %s",
-		       nm_utils_inet4_ntop (info->address, NULL),
-		       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex),
+		_LOGW ("could not create probe config for %s on interface '%s': %s",
+		       nm_utils_inet4_ntop (info->address, sbuf),
+		       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
 		       acd_error_to_string (r));
 		return FALSE;
 	}
 
-	n_acd_get_fd (info->acd, &fd);
-	info->channel = g_io_channel_unix_new (fd);
-	info->event_id = g_io_add_watch (info->channel, G_IO_IN, acd_event, info);
-
-	config = &(NAcdConfig) {
-		.ifindex = priv->ifindex,
-		.mac = priv->hwaddr,
-		.n_mac = ETH_ALEN,
-		.ip = info->address,
-		.timeout_msec = timeout,
-		.transport = N_ACD_TRANSPORT_ETHERNET,
-	};
+	n_acd_probe_config_set_ip (probe_config, (struct in_addr) { info->address });
+	n_acd_probe_config_set_timeout (probe_config, timeout);
 
-	r = n_acd_start (info->acd, config);
+	r = n_acd_probe (self->acd, &info->probe, probe_config);
 	if (r) {
 		_LOGW ("could not start probe for %s on interface '%s': %s",
-		       nm_utils_inet4_ntop (info->address, NULL),
-		       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex),
+		       nm_utils_inet4_ntop (info->address, sbuf),
+		       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
 		       acd_error_to_string (r));
+		n_acd_probe_config_free (probe_config);
 		return FALSE;
 	}
 
-	if (timeout) {
-		_LOGD ("started probe for %s with timeout %llu",
-		       nm_utils_inet4_ntop (info->address, NULL),
-		       (unsigned long long) timeout);
-	}
+	n_acd_probe_set_userdata (info->probe, info);
+	n_acd_probe_config_free (probe_config);
 
 	return TRUE;
 }
 
+static int
+acd_init (NMAcdManager *self)
+{
+	NAcdConfig *config;
+	int r;
+
+	if (self->acd)
+		return 0;
+
+	r = n_acd_config_new (&config);
+	if (r)
+		return r;
+
+	n_acd_config_set_ifindex (config, self->ifindex);
+	n_acd_config_set_transport (config, N_ACD_TRANSPORT_ETHERNET);
+	n_acd_config_set_mac (config, self->hwaddr, ETH_ALEN);
+
+	r = n_acd_new (&self->acd, config);
+	n_acd_config_free (config);
+	return r;
+}
+
 /**
  * nm_acd_manager_start_probe:
  * @self: a #NMAcdManager
@@ -297,59 +296,36 @@ acd_probe_start (NMAcdManager *self,
 gboolean
 nm_acd_manager_start_probe (NMAcdManager *self, guint timeout)
 {
-	NMAcdManagerPrivate *priv;
 	GHashTableIter iter;
 	AddressInfo *info;
 	gboolean success = FALSE;
+	int fd, r;
 
-	g_return_val_if_fail (NM_IS_ACD_MANAGER (self), FALSE);
-	priv = NM_ACD_MANAGER_GET_PRIVATE (self);
-	g_return_val_if_fail (priv->state == STATE_INIT, FALSE);
+	g_return_val_if_fail (self, FALSE);
+	g_return_val_if_fail (self->state == STATE_INIT, FALSE);
+
+	r = acd_init (self);
+	if (r) {
+		_LOGW ("couldn't init ACD for probing on interface '%s': %s",
+		       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
+		       acd_error_to_string (r));
+		return FALSE;
+	}
 
-	priv->completed = 0;
+	self->completed = 0;
 
-	g_hash_table_iter_init (&iter, priv->addresses);
+	g_hash_table_iter_init (&iter, self->addresses);
 	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info))
-		success |= acd_probe_start (self, info, timeout);
+		success |= acd_probe_add (self, info, timeout);
 
 	if (success)
-		priv->state = STATE_PROBING;
+		self->state = STATE_PROBING;
 
-	return success;
-}
-
-/**
- * nm_acd_manager_reset:
- * @self: a #NMAcdManager
- *
- * Stop any operation in progress and reset @self to the initial state.
- */
-void
-nm_acd_manager_reset (NMAcdManager *self)
-{
-	NMAcdManagerPrivate *priv;
-
-	g_return_if_fail (NM_IS_ACD_MANAGER (self));
-	priv = NM_ACD_MANAGER_GET_PRIVATE (self);
-
-	g_hash_table_remove_all (priv->addresses);
+	n_acd_get_fd (self->acd, &fd);
+	self->channel = g_io_channel_unix_new (fd);
+	self->event_id = g_io_add_watch (self->channel, G_IO_IN, acd_event, self);
 
-	priv->state = STATE_INIT;
-}
-
-/**
- * nm_acd_manager_destroy:
- * @self: the #NMAcdManager
- *
- * Calls nm_acd_manager_reset() and unrefs @self.
- */
-void
-nm_acd_manager_destroy (NMAcdManager *self)
-{
-	g_return_if_fail (NM_IS_ACD_MANAGER (self));
-
-	nm_acd_manager_reset (self);
-	g_object_unref (self);
+	return success;
 }
 
 /**
@@ -365,15 +341,12 @@ nm_acd_manager_destroy (NMAcdManager *self)
 gboolean
 nm_acd_manager_check_address (NMAcdManager *self, in_addr_t address)
 {
-	NMAcdManagerPrivate *priv;
 	AddressInfo *info;
 
-	g_return_val_if_fail (NM_IS_ACD_MANAGER (self), FALSE);
-	priv = NM_ACD_MANAGER_GET_PRIVATE (self);
-	g_return_val_if_fail (   priv->state == STATE_INIT
-	                      || priv->state == STATE_PROBE_DONE, FALSE);
+	g_return_val_if_fail (self, FALSE);
+	g_return_val_if_fail (NM_IN_SET (self->state, STATE_INIT, STATE_PROBE_DONE), FALSE);
 
-	info = g_hash_table_lookup (priv->addresses, GUINT_TO_POINTER (address));
+	info = g_hash_table_lookup (self->addresses, GUINT_TO_POINTER (address));
 	g_return_val_if_fail (info, FALSE);
 
 	return !info->duplicate;
@@ -388,41 +361,43 @@ nm_acd_manager_check_address (NMAcdManager *self, in_addr_t address)
 void
 nm_acd_manager_announce_addresses (NMAcdManager *self)
 {
-	NMAcdManagerPrivate *priv = NM_ACD_MANAGER_GET_PRIVATE (self);
 	GHashTableIter iter;
 	AddressInfo *info;
 	int r;
 
-	if (priv->state == STATE_INIT) {
+	r = acd_init (self);
+	if (r) {
+		_LOGW ("couldn't init ACD for announcing addresses on interface '%s': %s",
+		       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
+		       acd_error_to_string (r));
+		return;
+	}
+
+	if (self->state == STATE_INIT) {
 		/* n-acd can't announce without probing, therefore let's
 		 * start a fake probe with zero timeout and then perform
-		 * the announce. */
-		priv->state = STATE_ANNOUNCING;
-		g_hash_table_iter_init (&iter, priv->addresses);
-		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info)) {
-			if (!acd_probe_start (self, info, 0)) {
-				_LOGW ("couldn't announce address %s on interface '%s'",
-				       nm_utils_inet4_ntop (info->address, NULL),
-				       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex));
-			}
-		}
-	} else if (priv->state == STATE_PROBE_DONE) {
-		priv->state = STATE_ANNOUNCING;
-		g_hash_table_iter_init (&iter, priv->addresses);
+		 * the announcement. */
+		g_hash_table_iter_init (&iter, self->addresses);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info))
+			acd_probe_add (self, info, 0);
+		self->state = STATE_ANNOUNCING;
+	} else if (self->state == STATE_ANNOUNCING) {
+		char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
+		g_hash_table_iter_init (&iter, self->addresses);
 		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info)) {
 			if (info->duplicate)
 				continue;
-			r = n_acd_announce (info->acd, N_ACD_DEFEND_ONCE);
+			r = n_acd_probe_announce (info->probe, N_ACD_DEFEND_ONCE);
 			if (r) {
 				_LOGW ("couldn't announce address %s on interface '%s': %s",
-				       nm_utils_inet4_ntop (info->address, NULL),
-				       nm_platform_link_get_name (NM_PLATFORM_GET, priv->ifindex),
+				       nm_utils_inet4_ntop (info->address, sbuf),
+				       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
 				       acd_error_to_string (r));
 			} else
-				_LOGD ("announcing address %s", nm_utils_inet4_ntop (info->address, NULL));
+				_LOGD ("announcing address %s", nm_utils_inet4_ntop (info->address, sbuf));
 		}
-	} else
-		nm_assert_not_reached ();
+	}
 }
 
 static void
@@ -430,64 +405,52 @@ destroy_address_info (gpointer data)
 {
 	AddressInfo *info = (AddressInfo *) data;
 
-	g_clear_pointer (&info->channel, g_io_channel_unref);
-	g_clear_pointer (&info->acd, n_acd_free);
-	nm_clear_g_source (&info->event_id);
+	n_acd_probe_free (info->probe);
 
 	g_slice_free (AddressInfo, info);
 }
 
 /*****************************************************************************/
 
-static void
-nm_acd_manager_init (NMAcdManager *self)
-{
-	NMAcdManagerPrivate *priv = NM_ACD_MANAGER_GET_PRIVATE (self);
-
-	priv->addresses = g_hash_table_new_full (nm_direct_hash, NULL,
-	                                         NULL, destroy_address_info);
-	priv->state = STATE_INIT;
-}
-
 NMAcdManager *
-nm_acd_manager_new (int ifindex, const guint8 *hwaddr, size_t hwaddr_len)
+nm_acd_manager_new (int ifindex,
+                    const guint8 *hwaddr,
+                    guint hwaddr_len,
+                    const NMAcdCallbacks *callbacks,
+                    gpointer user_data)
 {
 	NMAcdManager *self;
-	NMAcdManagerPrivate *priv;
 
+	g_return_val_if_fail (ifindex > 0, NULL);
 	g_return_val_if_fail (hwaddr, NULL);
 	g_return_val_if_fail (hwaddr_len == ETH_ALEN, NULL);
 
-	self = g_object_new (NM_TYPE_ACD_MANAGER, NULL);
-	priv = NM_ACD_MANAGER_GET_PRIVATE (self);
-	priv->ifindex = ifindex;
-	memcpy (priv->hwaddr, hwaddr, ETH_ALEN);
+	self = g_slice_new0 (NMAcdManager);
+
+	if (callbacks)
+		self->callbacks = *callbacks;
+	self->user_data = user_data;
 
+	self->addresses = g_hash_table_new_full (nm_direct_hash, NULL,
+	                                         NULL, destroy_address_info);
+	self->state = STATE_INIT;
+	self->ifindex = ifindex;
+	memcpy (self->hwaddr, hwaddr, ETH_ALEN);
 	return self;
 }
 
-static void
-dispose (GObject *object)
+void
+nm_acd_manager_free (NMAcdManager *self)
 {
-	NMAcdManager *self = NM_ACD_MANAGER (object);
-	NMAcdManagerPrivate *priv = NM_ACD_MANAGER_GET_PRIVATE (self);
+	g_return_if_fail (self);
 
-	g_clear_pointer (&priv->addresses, g_hash_table_destroy);
-
-	G_OBJECT_CLASS (nm_acd_manager_parent_class)->dispose (object);
-}
-
-static void
-nm_acd_manager_class_init (NMAcdManagerClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	if (self->callbacks.user_data_destroy)
+		self->callbacks.user_data_destroy (self->user_data);
 
-	object_class->dispose = dispose;
+	nm_clear_pointer (&self->addresses, g_hash_table_destroy);
+	nm_clear_pointer (&self->channel, g_io_channel_unref);
+	nm_clear_g_source (&self->event_id);
+	nm_clear_pointer (&self->acd, n_acd_unref);
 
-	signals[PROBE_TERMINATED] =
-	    g_signal_new (NM_ACD_MANAGER_PROBE_TERMINATED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 0);
+	g_slice_free (NMAcdManager, self);
 }
diff --git a/src/devices/nm-acd-manager.h b/src/devices/nm-acd-manager.h
index eeede5da..75884846 100644
--- a/src/devices/nm-acd-manager.h
+++ b/src/devices/nm-acd-manager.h
@@ -19,25 +19,25 @@
 
 #include <netinet/in.h>
 
-#define NM_TYPE_ACD_MANAGER            (nm_acd_manager_get_type ())
-#define NM_ACD_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_ACD_MANAGER, NMAcdManager))
-#define NM_ACD_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_ACD_MANAGER, NMAcdManagerClass))
-#define NM_IS_ACD_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_ACD_MANAGER))
-#define NM_IS_ACD_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_ACD_MANAGER))
-#define NM_ACD_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_ACD_MANAGER, NMAcdManagerClass))
+typedef struct _NMAcdManager NMAcdManager;
 
-#define NM_ACD_MANAGER_PROBE_TERMINATED  "probe-terminated"
+typedef struct {
+	void (*probe_terminated_callback) (NMAcdManager *self,
+	                                   gpointer user_data);
+	GDestroyNotify user_data_destroy;
+} NMAcdCallbacks;
 
-typedef struct _NMAcdManagerClass NMAcdManagerClass;
+NMAcdManager *nm_acd_manager_new (int ifindex,
+                                  const guint8 *hwaddr,
+                                  guint hwaddr_len,
+                                  const NMAcdCallbacks *callbacks,
+                                  gpointer user_data);
 
-GType nm_acd_manager_get_type (void);
+void nm_acd_manager_free (NMAcdManager *self);
 
-NMAcdManager *nm_acd_manager_new (int ifindex, const guint8 *hwaddr, size_t hwaddr_len);
-void nm_acd_manager_destroy (NMAcdManager *self);
 gboolean nm_acd_manager_add_address (NMAcdManager *self, in_addr_t address);
 gboolean nm_acd_manager_start_probe (NMAcdManager *self, guint timeout);
 gboolean nm_acd_manager_check_address (NMAcdManager *self, in_addr_t address);
 void nm_acd_manager_announce_addresses (NMAcdManager *self);
-void nm_acd_manager_reset (NMAcdManager *self);
 
 #endif /* __NM_ACD_MANAGER__ */
diff --git a/src/devices/nm-device-6lowpan.c b/src/devices/nm-device-6lowpan.c
index b6b9157c..40103747 100644
--- a/src/devices/nm-device-6lowpan.c
+++ b/src/devices/nm-device-6lowpan.c
@@ -110,9 +110,9 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
 	NMSetting6Lowpan *s_6lowpan;
 	int parent_ifindex;
+	int r;
 
 	s_6lowpan = NM_SETTING_6LOWPAN (nm_connection_get_setting (connection, NM_TYPE_SETTING_6LOWPAN));
 	g_return_val_if_fail (s_6lowpan, FALSE);
@@ -126,13 +126,13 @@ create_and_realize (NMDevice *device,
 		return FALSE;
 	}
 
-	plerr = nm_platform_link_6lowpan_add (nm_device_get_platform (device), iface, parent_ifindex, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_6lowpan_add (nm_device_get_platform (device), iface, parent_ifindex, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create 6lowpan interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index 6e7e6ffc..6dabdfe8 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -22,7 +22,6 @@
 
 #include "nm-device-bond.h"
 
-#include <errno.h>
 #include <stdlib.h>
 
 #include "NetworkManagerUtils.h"
@@ -220,7 +219,6 @@ static NMActStageReturn
 apply_bonding_config (NMDevice *device)
 {
 	NMDeviceBond *self = NM_DEVICE_BOND (device);
-	NMConnection *connection;
 	NMSettingBond *s_bond;
 	int ifindex = nm_device_get_ifindex (device);
 	const char *mode_str, *value;
@@ -241,10 +239,9 @@ apply_bonding_config (NMDevice *device)
 	 *     arp_interval doesn't require miimon to be 0
 	 */
 
-	connection = nm_device_get_applied_connection (device);
-	g_assert (connection);
-	s_bond = nm_connection_get_setting_bond (connection);
-	g_assert (s_bond);
+	s_bond = nm_device_get_applied_setting (device, NM_TYPE_SETTING_BOND);
+
+	g_return_val_if_fail (s_bond, NM_ACT_STAGE_RETURN_FAILURE);
 
 	mode_str = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MODE);
 	if (!mode_str)
@@ -461,17 +458,17 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
+	int r;
 
 	g_assert (iface);
 
-	plerr = nm_platform_link_bond_add (nm_device_get_platform (device), iface, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_bond_add (nm_device_get_platform (device), iface, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create bond interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 	return TRUE;
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index 68e1ac28..4c8921c0 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -512,7 +512,7 @@ create_and_realize (NMDevice *device,
 	const char *hwaddr;
 	gs_free char *hwaddr_cloned = NULL;
 	guint8 mac_address[NM_UTILS_HWADDR_LEN_MAX];
-	NMPlatformError plerr;
+	int r;
 
 	nm_assert (iface);
 
@@ -539,17 +539,17 @@ create_and_realize (NMDevice *device,
 		}
 	}
 
-	plerr = nm_platform_link_bridge_add (nm_device_get_platform (device),
-	                                     iface,
-	                                     hwaddr ? mac_address : NULL,
-	                                     hwaddr ? ETH_ALEN : 0,
-	                                     out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_bridge_add (nm_device_get_platform (device),
+	                                 iface,
+	                                 hwaddr ? mac_address : NULL,
+	                                 hwaddr ? ETH_ALEN : 0,
+	                                 out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create bridge interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-dummy.c b/src/devices/nm-device-dummy.c
index a9059383..47a45342 100644
--- a/src/devices/nm-device-dummy.c
+++ b/src/devices/nm-device-dummy.c
@@ -17,7 +17,6 @@
 #include "nm-device-dummy.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 
 #include "nm-act-request.h"
@@ -98,19 +97,19 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
 	NMSettingDummy *s_dummy;
+	int r;
 
 	s_dummy = nm_connection_get_setting_dummy (connection);
 	g_assert (s_dummy);
 
-	plerr = nm_platform_link_dummy_add (nm_device_get_platform (device), iface, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_dummy_add (nm_device_get_platform (device), iface, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create dummy interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index e7262683..24c99f76 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -24,10 +24,8 @@
 #include "nm-device-ethernet.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <stdlib.h>
 #include <unistd.h>
-#include <errno.h>
 #include <libudev.h>
 
 #include "nm-device-private.h"
@@ -556,7 +554,9 @@ build_supplicant_config (NMDeviceEthernet *self,
 	guint32 mtu;
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
-	g_assert (connection);
+
+	g_return_val_if_fail (connection, NULL);
+
 	con_uuid = nm_connection_get_uuid (connection);
 	mtu = nm_platform_link_get_mtu (nm_device_get_platform (NM_DEVICE (self)),
 	                                nm_device_get_ifindex (NM_DEVICE (self)));
@@ -790,7 +790,7 @@ link_negotiation_set (NMDevice *device)
 	guint32 speed = 0;
 	guint32 link_speed;
 
-	s_wired = (NMSettingWired *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
+	s_wired = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
 	if (s_wired) {
 		autoneg = nm_setting_wired_get_auto_negotiate (s_wired);
 		speed = nm_setting_wired_get_speed (s_wired);
@@ -880,8 +880,8 @@ act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *out_failure_reason)
 			       delay);
 			g_assert (!priv->pppoe_wait_id);
 			priv->pppoe_wait_id = g_timeout_add_seconds (delay,
-								     pppoe_reconnect_delay,
-								     self);
+			                                             pppoe_reconnect_delay,
+			                                             self);
 			return NM_ACT_STAGE_RETURN_POSTPONE;
 		}
 		priv->last_pppoe_time = 0;
@@ -900,6 +900,7 @@ nm_8021x_stage2_config (NMDeviceEthernet *self, NMDeviceStateReason *out_failure
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
 	security = nm_connection_get_setting_802_1x (connection);
@@ -981,9 +982,8 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 	NMDevice *device = NM_DEVICE (user_data);
 
 	/* Ignore PPP IP4 events that come in after initial configuration */
-	if (nm_device_activate_ip4_state_in_conf (device)) {
-		nm_device_activate_schedule_ip4_config_result (device, config);
-	}
+	if (nm_device_activate_ip4_state_in_conf (device))
+		nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 }
 
 static NMActStageReturn
@@ -995,10 +995,12 @@ pppoe_stage3_ip4_config_start (NMDeviceEthernet *self, NMDeviceStateReason *out_
 	NMActRequest *req;
 	GError *err = NULL;
 
-	req = nm_device_get_act_request (NM_DEVICE (self));
+	req = nm_device_get_act_request (device);
+
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
 
-	s_pppoe = (NMSettingPppoe *) nm_device_get_applied_setting ((NMDevice *) self, NM_TYPE_SETTING_PPPOE);
+	s_pppoe = nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPPOE);
+
 	g_return_val_if_fail (s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
 
 	priv->ppp_manager = nm_ppp_manager_create (nm_device_get_iface (device),
@@ -1069,8 +1071,10 @@ dcb_configure (NMDevice *device)
 
 	nm_clear_g_source (&priv->dcb_timeout_id);
 
-	s_dcb = (NMSettingDcb *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
-	g_assert (s_dcb);
+	s_dcb = nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
+
+	g_return_val_if_fail (s_dcb, FALSE);
+
 	if (!nm_dcb_setup (nm_device_get_iface (device), s_dcb, &error)) {
 		_LOGW (LOGD_DCB, "Activation: (ethernet) failed to enable DCB/FCoE: %s",
 		       error->message);
@@ -1199,7 +1203,8 @@ wake_on_lan_enable (NMDevice *device)
 	NMSettingWired *s_wired;
 	const char *password = NULL;
 
-	s_wired = (NMSettingWired *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
+	s_wired = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
+
 	if (s_wired) {
 		wol = nm_setting_wired_get_wake_on_lan (s_wired);
 		password = nm_setting_wired_get_wake_on_lan_password (s_wired);
@@ -1208,7 +1213,7 @@ wake_on_lan_enable (NMDevice *device)
 	}
 
 	wol = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                   "ethernet.wake-on-lan",
+	                                                   NM_CON_DEFAULT ("ethernet.wake-on-lan"),
 	                                                   device,
 	                                                   NM_SETTING_WIRED_WAKE_ON_LAN_NONE,
 	                                                   G_MAXINT32,
@@ -1240,8 +1245,8 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
 	NMSettingDcb *s_dcb;
 
-	s_con = NM_SETTING_CONNECTION (nm_device_get_applied_setting (device,
-	                                                              NM_TYPE_SETTING_CONNECTION));
+	s_con = nm_device_get_applied_setting (device, NM_TYPE_SETTING_CONNECTION);
+
 	g_return_val_if_fail (s_con, NM_ACT_STAGE_RETURN_FAILURE);
 
 	nm_clear_g_source (&priv->dcb_timeout_id);
@@ -1254,8 +1259,8 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	if (!strcmp (connection_type, NM_SETTING_WIRED_SETTING_NAME)) {
 		NMSetting8021x *security;
 
-		security = (NMSetting8021x *) nm_device_get_applied_setting (device,
-		                                                             NM_TYPE_SETTING_802_1X);
+		security = nm_device_get_applied_setting (device, NM_TYPE_SETTING_802_1X);
+
 		if (security) {
 			/* FIXME: for now 802.1x is mutually exclusive with DCB */
 			return nm_8021x_stage2_config (self, out_failure_reason);
@@ -1265,7 +1270,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	wake_on_lan_enable (device);
 
 	/* DCB and FCoE setup */
-	s_dcb = (NMSettingDcb *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
+	s_dcb = nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
 	if (s_dcb) {
 		/* lldpad really really wants the carrier to be up */
 		if (nm_platform_link_is_connected (nm_device_get_platform (device), nm_device_get_ifindex (device))) {
@@ -1288,7 +1293,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	                           NM_SETTING_PPPOE_SETTING_NAME)) {
 		NMSettingPpp *s_ppp;
 
-		s_ppp = (NMSettingPpp *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPP);
+		s_ppp = nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPP);
 		if (s_ppp) {
 			guint32 mtu = 0, mru = 0, mxu;
 
@@ -1309,21 +1314,25 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	NMSettingConnection *s_con;
 	const char *connection_type;
 
-	s_con = NM_SETTING_CONNECTION (nm_device_get_applied_setting (device, NM_TYPE_SETTING_CONNECTION));
-	g_return_val_if_fail (s_con, NM_ACT_STAGE_RETURN_FAILURE);
+	if (addr_family == AF_INET) {
+		s_con = nm_device_get_applied_setting (device, NM_TYPE_SETTING_CONNECTION);
 
-	connection_type = nm_setting_connection_get_connection_type (s_con);
-	if (!strcmp (connection_type, NM_SETTING_PPPOE_SETTING_NAME))
-		return pppoe_stage3_ip4_config_start (NM_DEVICE_ETHERNET (device), out_failure_reason);
+		g_return_val_if_fail (s_con, NM_ACT_STAGE_RETURN_FAILURE);
+
+		connection_type = nm_setting_connection_get_connection_type (s_con);
+		if (!strcmp (connection_type, NM_SETTING_PPPOE_SETTING_NAME))
+			return pppoe_stage3_ip4_config_start (NM_DEVICE_ETHERNET (device), out_failure_reason);
+	}
 
-	return NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static guint32
@@ -1347,7 +1356,7 @@ deactivate (NMDevice *device)
 	nm_clear_g_source (&priv->pppoe_wait_id);
 
 	if (priv->ppp_manager) {
-		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL);
+		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL, NULL);
 		g_clear_object (&priv->ppp_manager);
 	}
 
@@ -1358,7 +1367,7 @@ deactivate (NMDevice *device)
 	priv->dcb_handle_carrier_changes = FALSE;
 
 	/* Tear down DCB/FCoE if it was enabled */
-	s_dcb = (NMSettingDcb *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
+	s_dcb = nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
 	if (s_dcb) {
 		if (!nm_dcb_cleanup (nm_device_get_iface (device), &error)) {
 			_LOGW (LOGD_DEVICE | LOGD_PLATFORM, "failed to disable DCB/FCoE: %s",
@@ -1785,7 +1794,7 @@ nm_device_ethernet_class_init (NMDeviceEthernetClass *klass)
 
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->get_configured_mtu = get_configured_mtu;
 	device_class->deactivate = deactivate;
 	device_class->get_s390_subchannels = get_s390_subchannels;
diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c
index a11ead6e..30aca038 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/devices/nm-device-factory.c
@@ -24,8 +24,6 @@
 
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <errno.h>
-#include <string.h>
 #include <gmodule.h>
 
 #include "platform/nm-platform.h"
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index 5138b684..4db7d8a7 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -87,7 +87,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
 		return ret;
 
-	s_infiniband = (NMSettingInfiniband *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_INFINIBAND);
+	s_infiniband = nm_device_get_applied_setting (device, NM_TYPE_SETTING_INFINIBAND);
+
 	g_return_val_if_fail (s_infiniband, NM_ACT_STAGE_RETURN_FAILURE);
 
 	transport_mode = nm_setting_infiniband_get_transport_mode (s_infiniband);
@@ -234,7 +235,7 @@ create_and_realize (NMDevice *device,
 {
 	NMDeviceInfinibandPrivate *priv = NM_DEVICE_INFINIBAND_GET_PRIVATE ((NMDeviceInfiniband *) device);
 	NMSettingInfiniband *s_infiniband;
-	NMPlatformError plerr;
+	int r;
 
 	s_infiniband = nm_connection_get_setting_infiniband (connection);
 	g_assert (s_infiniband);
@@ -268,13 +269,13 @@ create_and_realize (NMDevice *device,
 		return FALSE;
 	}
 
-	plerr = nm_platform_link_infiniband_add (nm_device_get_platform (device), priv->parent_ifindex, priv->p_key, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_infiniband_add (nm_device_get_platform (device), priv->parent_ifindex, priv->p_key, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create InfiniBand P_Key interface '%s' for '%s': %s",
 		             nm_device_get_iface (device),
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
@@ -286,7 +287,7 @@ static gboolean
 unrealize (NMDevice *device, GError **error)
 {
 	NMDeviceInfinibandPrivate *priv;
-	NMPlatformError plerr;
+	int r;
 
 	g_return_val_if_fail (NM_IS_DEVICE_INFINIBAND (device), FALSE);
 
@@ -298,12 +299,12 @@ unrealize (NMDevice *device, GError **error)
 		return FALSE;
 	}
 
-	plerr = nm_platform_link_infiniband_delete (nm_device_get_platform (device), priv->parent_ifindex, priv->p_key);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_infiniband_delete (nm_device_get_platform (device), priv->parent_ifindex, priv->p_key);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to remove InfiniBand P_Key interface '%s': %s",
 		             nm_device_get_iface (device),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index 1c7e6d51..96275947 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -22,7 +22,6 @@
 
 #include "nm-device-ip-tunnel.h"
 
-#include <string.h>
 #include <netinet/in.h>
 #include <linux/if.h>
 #include <linux/ip.h>
@@ -149,9 +148,13 @@ update_properties_from_ifindex (NMDevice *device, int ifindex)
 	NMDeviceIPTunnel *self = NM_DEVICE_IP_TUNNEL (device);
 	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE (self);
 	int parent_ifindex = 0;
-	in_addr_t local4 = 0, remote4 = 0;
-	struct in6_addr local6 = { 0 }, remote6 = { 0 };
-	guint8 ttl = 0, tos = 0, encap_limit = 0;
+	in_addr_t local4 = 0;
+	in_addr_t remote4 = 0;
+	struct in6_addr local6 = IN6ADDR_ANY_INIT;
+	struct in6_addr remote6 = IN6ADDR_ANY_INIT;
+	guint8 ttl = 0;
+	guint8 tos = 0;
+	guint8 encap_limit = 0;
 	gboolean pmtud = FALSE;
 	guint32 flow_label = 0;
 	NMIPTunnelFlags flags = NM_IP_TUNNEL_FLAG_NONE;
@@ -329,28 +332,28 @@ clear:
 		if (!address_equal_pn (AF_INET, priv->local, &local4)) {
 			g_clear_pointer (&priv->local, g_free);
 			if (local4)
-				priv->local = g_strdup (nm_utils_inet4_ntop (local4, NULL));
+				priv->local = nm_utils_inet4_ntop_dup (local4);
 			_notify (self, PROP_LOCAL);
 		}
 
 		if (!address_equal_pn (AF_INET, priv->remote, &remote4)) {
 			g_clear_pointer (&priv->remote, g_free);
 			if (remote4)
-				priv->remote = g_strdup (nm_utils_inet4_ntop (remote4, NULL));
+				priv->remote = nm_utils_inet4_ntop_dup (remote4);
 			_notify (self, PROP_REMOTE);
 		}
 	} else {
 		if (!address_equal_pn (AF_INET6, priv->local, &local6)) {
 			g_clear_pointer (&priv->local, g_free);
 			if (memcmp (&local6, &in6addr_any, sizeof (in6addr_any)))
-				priv->local = g_strdup (nm_utils_inet6_ntop (&local6, NULL));
+				priv->local = nm_utils_inet6_ntop_dup (&local6);
 			_notify (self, PROP_LOCAL);
 		}
 
 		if (!address_equal_pn (AF_INET6, priv->remote, &remote6)) {
 			g_clear_pointer (&priv->remote, g_free);
 			if (memcmp (&remote6, &in6addr_any, sizeof (in6addr_any)))
-				priv->remote = g_strdup (nm_utils_inet6_ntop (&remote6, NULL));
+				priv->remote = nm_utils_inet6_ntop_dup (&remote6);
 			_notify (self, PROP_REMOTE);
 		}
 	}
@@ -660,7 +663,6 @@ create_and_realize (NMDevice *device,
 {
 	const char *iface = nm_device_get_iface (device);
 	NMSettingIPTunnel *s_ip_tunnel;
-	NMPlatformError plerr;
 	NMPlatformLnkGre lnk_gre = { };
 	NMPlatformLnkSit lnk_sit = { };
 	NMPlatformLnkIpIp lnk_ipip = { };
@@ -668,6 +670,7 @@ create_and_realize (NMDevice *device,
 	const char *str;
 	gint64 val;
 	NMIPTunnelMode mode;
+	int r;
 
 	s_ip_tunnel = nm_connection_get_setting_ip_tunnel (connection);
 	g_assert (s_ip_tunnel);
@@ -713,13 +716,13 @@ create_and_realize (NMDevice *device,
 			lnk_gre.output_flags = NM_GRE_KEY;
 		}
 
-		plerr = nm_platform_link_gre_add (nm_device_get_platform (device), iface, &lnk_gre, out_plink);
-		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+		r = nm_platform_link_gre_add (nm_device_get_platform (device), iface, &lnk_gre, out_plink);
+		if (r < 0) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 			             "Failed to create GRE interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
-			             nm_platform_error_to_string_a (plerr));
+			             nm_strerror (r));
 			return FALSE;
 		}
 		break;
@@ -739,13 +742,13 @@ create_and_realize (NMDevice *device,
 		lnk_sit.tos = nm_setting_ip_tunnel_get_tos (s_ip_tunnel);
 		lnk_sit.path_mtu_discovery = nm_setting_ip_tunnel_get_path_mtu_discovery (s_ip_tunnel);
 
-		plerr = nm_platform_link_sit_add (nm_device_get_platform (device), iface, &lnk_sit, out_plink);
-		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+		r = nm_platform_link_sit_add (nm_device_get_platform (device), iface, &lnk_sit, out_plink);
+		if (r < 0) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 			             "Failed to create SIT interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
-			             nm_platform_error_to_string_a (plerr));
+			             nm_strerror (r));
 			return FALSE;
 		}
 		break;
@@ -765,13 +768,13 @@ create_and_realize (NMDevice *device,
 		lnk_ipip.tos = nm_setting_ip_tunnel_get_tos (s_ip_tunnel);
 		lnk_ipip.path_mtu_discovery = nm_setting_ip_tunnel_get_path_mtu_discovery (s_ip_tunnel);
 
-		plerr = nm_platform_link_ipip_add (nm_device_get_platform (device), iface, &lnk_ipip, out_plink);
-		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+		r = nm_platform_link_ipip_add (nm_device_get_platform (device), iface, &lnk_ipip, out_plink);
+		if (r < 0) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 			             "Failed to create IPIP interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
-			             nm_platform_error_to_string_a (plerr));
+			             nm_strerror (r));
 			return FALSE;
 		}
 		break;
@@ -820,21 +823,21 @@ create_and_realize (NMDevice *device,
 			lnk_ip6tnl.is_gre = TRUE;
 			lnk_ip6tnl.is_tap = (mode == NM_IP_TUNNEL_MODE_IP6GRETAP);
 
-			plerr = nm_platform_link_ip6gre_add (nm_device_get_platform (device),
-			                                     iface, &lnk_ip6tnl, out_plink);
+			r = nm_platform_link_ip6gre_add (nm_device_get_platform (device),
+			                                 iface, &lnk_ip6tnl, out_plink);
 		} else {
 			lnk_ip6tnl.proto = nm_setting_ip_tunnel_get_mode (s_ip_tunnel) == NM_IP_TUNNEL_MODE_IPIP6
 			                       ? IPPROTO_IPIP
 			                       : IPPROTO_IPV6;
-			plerr = nm_platform_link_ip6tnl_add (nm_device_get_platform (device),
-			                                     iface, &lnk_ip6tnl, out_plink);
+			r = nm_platform_link_ip6tnl_add (nm_device_get_platform (device),
+			                                 iface, &lnk_ip6tnl, out_plink);
 		}
-		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+		if (r < 0) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 			             "Failed to create IPv6 tunnel interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
-			             nm_platform_error_to_string_a (plerr));
+			             nm_strerror (r));
 			return FALSE;
 		}
 		break;
diff --git a/src/devices/nm-device-macsec.c b/src/devices/nm-device-macsec.c
index 8ea4c8b5..1a6b64a4 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/devices/nm-device-macsec.c
@@ -210,7 +210,7 @@ update_properties (NMDevice *device)
 static NMSupplicantConfig *
 build_supplicant_config (NMDeviceMacsec *self, GError **error)
 {
-	NMSupplicantConfig *config = NULL;
+	gs_unref_object NMSupplicantConfig *config = NULL;
 	NMSettingMacsec *s_macsec;
 	NMSetting8021x *s_8021x;
 	NMConnection *connection;
@@ -218,19 +218,21 @@ build_supplicant_config (NMDeviceMacsec *self, GError **error)
 	guint32 mtu;
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
-	g_assert (connection);
+
+	g_return_val_if_fail (connection, NULL);
+
 	con_uuid = nm_connection_get_uuid (connection);
 	mtu = nm_platform_link_get_mtu (nm_device_get_platform (NM_DEVICE (self)),
 	                                nm_device_get_ifindex (NM_DEVICE (self)));
 
 	config = nm_supplicant_config_new (FALSE, FALSE);
 
-	s_macsec = (NMSettingMacsec *)
-		nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_MACSEC);
+	s_macsec = nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_MACSEC);
+
+	g_return_val_if_fail (s_macsec, NULL);
 
 	if (!nm_supplicant_config_add_setting_macsec (config, s_macsec, error)) {
 		g_prefix_error (error, "macsec-setting: ");
-		g_object_unref (config);
 		return NULL;
 	}
 
@@ -238,11 +240,11 @@ build_supplicant_config (NMDeviceMacsec *self, GError **error)
 		s_8021x = nm_connection_get_setting_802_1x (connection);
 		if (!nm_supplicant_config_add_setting_8021x (config, s_8021x, con_uuid, mtu, TRUE, error)) {
 			g_prefix_error (error, "802-1x-setting: ");
-			g_clear_object (&config);
+			return NULL;
 		}
 	}
 
-	return config;
+	return g_steal_pointer (&config);
 }
 
 static void
@@ -588,6 +590,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	const char *setting_name;
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
 	if (!priv->supplicant.mgr)
@@ -654,7 +657,6 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
 	NMSettingMacsec *s_macsec;
 	NMPlatformLnkMacsec lnk = { };
 	int parent_ifindex;
@@ -666,6 +668,7 @@ create_and_realize (NMDevice *device,
 		} s;
 		guint64 u;
 	} sci;
+	int r;
 
 	s_macsec = nm_connection_get_setting_macsec (connection);
 	g_assert (s_macsec);
@@ -694,13 +697,13 @@ create_and_realize (NMDevice *device,
 	parent_ifindex = nm_device_get_ifindex (parent);
 	g_warn_if_fail (parent_ifindex > 0);
 
-	plerr = nm_platform_link_macsec_add (nm_device_get_platform (device), iface, parent_ifindex, &lnk, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_macsec_add (nm_device_get_platform (device), iface, parent_ifindex, &lnk, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create macsec interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index 2b212154..aa2a0ac0 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -22,7 +22,6 @@
 
 #include "nm-device-macvlan.h"
 
-#include <string.h>
 #include <linux/if_link.h>
 
 #include "nm-device-private.h"
@@ -227,10 +226,10 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
 	NMSettingMacvlan *s_macvlan;
 	NMPlatformLnkMacvlan lnk = { };
 	int parent_ifindex;
+	int r;
 
 	s_macvlan = nm_connection_get_setting_macvlan (connection);
 	g_return_val_if_fail (s_macvlan, FALSE);
@@ -255,14 +254,14 @@ create_and_realize (NMDevice *device,
 	lnk.no_promisc = !nm_setting_macvlan_get_promiscuous (s_macvlan);
 	lnk.tap = nm_setting_macvlan_get_tap (s_macvlan);
 
-	plerr = nm_platform_link_macvlan_add (nm_device_get_platform (device), iface, parent_ifindex, &lnk, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_macvlan_add (nm_device_get_platform (device), iface, parent_ifindex, &lnk, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create %s interface '%s' for '%s': %s",
 		             lnk.tap ? "macvtap" : "macvlan",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-ppp.c b/src/devices/nm-device-ppp.c
index 74b4d710..3c310146 100644
--- a/src/devices/nm-device-ppp.c
+++ b/src/devices/nm-device-ppp.c
@@ -16,6 +16,7 @@
 
 #include "nm-device-ppp.h"
 
+#include "nm-ip4-config.h"
 #include "nm-act-request.h"
 #include "nm-device-factory.h"
 #include "nm-device-private.h"
@@ -106,7 +107,7 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 
 	if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
 		if (nm_device_activate_ip4_state_in_conf (device)) {
-			nm_device_activate_schedule_ip4_config_result (device, config);
+			nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 			return;
 		}
 	} else {
@@ -125,10 +126,12 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMActRequest *req;
 	GError *error = NULL;
 
-	req = nm_device_get_act_request (NM_DEVICE (self));
+	req = nm_device_get_act_request (device);
+
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
 
-	s_pppoe = (NMSettingPppoe *) nm_device_get_applied_setting ((NMDevice *) self, NM_TYPE_SETTING_PPPOE);
+	s_pppoe = nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPPOE);
+
 	g_return_val_if_fail (s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
 
 	g_clear_object (&priv->ip4_config);
@@ -170,23 +173,31 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
-	NMDevicePpp *self = NM_DEVICE_PPP (device);
-	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+	if (addr_family == AF_INET) {
+		NMDevicePpp *self = NM_DEVICE_PPP (device);
+		NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+
+		if (priv->ip4_config) {
+			if (out_config)
+				*out_config = g_steal_pointer (&priv->ip4_config);
+			else
+				g_clear_object (&priv->ip4_config);
+			return NM_ACT_STAGE_RETURN_SUCCESS;
+		}
 
-	if (priv->ip4_config) {
-		if (out_config)
-			*out_config = g_steal_pointer (&priv->ip4_config);
-		else
-			g_clear_object (&priv->ip4_config);
-		return NM_ACT_STAGE_RETURN_SUCCESS;
+		/* Wait IPCP termination */
+		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
-	/* Wait IPCP termination */
-	return NM_ACT_STAGE_RETURN_POSTPONE;
+	return NM_DEVICE_CLASS (nm_device_ppp_parent_class)->act_stage3_ip_config_start (device,
+	                                                                                 addr_family,
+	                                                                                 out_config,
+	                                                                                 out_failure_reason);
 }
 
 static gboolean
@@ -221,7 +232,7 @@ deactivate (NMDevice *device)
 	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
 
 	if (priv->ppp_manager) {
-		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL);
+		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL, NULL);
 		g_clear_object (&priv->ppp_manager);
 	}
 }
@@ -268,7 +279,7 @@ nm_device_ppp_class_init (NMDevicePppClass *klass)
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_PPP);
 
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->create_and_realize = create_and_realize;
 	device_class->deactivate = deactivate;
 	device_class->get_generic_capabilities = get_generic_capabilities;
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 66c715de..6e2372ab 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -26,6 +26,14 @@
 
 /* This file should only be used by subclasses of NMDevice */
 
+typedef enum {
+	NM_DEVICE_IP_STATE_NONE,
+	NM_DEVICE_IP_STATE_WAIT,
+	NM_DEVICE_IP_STATE_CONF,
+	NM_DEVICE_IP_STATE_DONE,
+	NM_DEVICE_IP_STATE_FAIL,
+} NMDeviceIPState;
+
 enum NMActStageReturn {
 	NM_ACT_STAGE_RETURN_FAILURE = 0, /* Hard failure of activation */
 	NM_ACT_STAGE_RETURN_SUCCESS,     /* Activation stage done */
@@ -34,7 +42,7 @@ enum NMActStageReturn {
 	NM_ACT_STAGE_RETURN_IP_DONE,     /* IP config stage is done (state IP_DONE),
 	                                    For the ip-config stage, this is similar to
 	                                    NM_ACT_STAGE_RETURN_SUCCESS, except that no
-	                                    IP config should be commited. */
+	                                    IP config should be committed. */
 	NM_ACT_STAGE_RETURN_IP_FAIL,     /* IP config stage failed (state IP_FAIL), activation may proceed */
 };
 
@@ -75,19 +83,51 @@ void nm_device_set_firmware_missing (NMDevice *self, gboolean missing);
 void nm_device_activate_schedule_stage1_device_prepare (NMDevice *device);
 void nm_device_activate_schedule_stage2_device_config (NMDevice *device);
 
-void nm_device_activate_schedule_ip4_config_result(NMDevice *device, NMIP4Config *config);
-void nm_device_activate_schedule_ip4_config_timeout (NMDevice *device);
-
-void nm_device_activate_schedule_ip6_config_result (NMDevice *device);
-void nm_device_activate_schedule_ip6_config_timeout (NMDevice *device);
-
-gboolean nm_device_activate_ip4_state_in_conf (NMDevice *device);
-gboolean nm_device_activate_ip4_state_in_wait (NMDevice *device);
-gboolean nm_device_activate_ip4_state_done (NMDevice *device);
-
-gboolean nm_device_activate_ip6_state_in_conf (NMDevice *device);
-gboolean nm_device_activate_ip6_state_in_wait (NMDevice *device);
-gboolean nm_device_activate_ip6_state_done (NMDevice *device);
+void nm_device_activate_schedule_ip_config_result (NMDevice *device,
+                                                   int addr_family,
+                                                   NMIPConfig *config);
+
+void nm_device_activate_schedule_ip_config_timeout (NMDevice *device,
+                                                    int addr_family);
+
+NMDeviceIPState nm_device_activate_get_ip_state (NMDevice *self,
+                                                 int addr_family);
+
+static inline gboolean
+nm_device_activate_ip4_state_in_conf (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET) == NM_DEVICE_IP_STATE_CONF;
+}
+
+static inline gboolean
+nm_device_activate_ip4_state_in_wait (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET) == NM_DEVICE_IP_STATE_WAIT;
+}
+
+static inline gboolean
+nm_device_activate_ip4_state_done (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET) == NM_DEVICE_IP_STATE_DONE;
+}
+
+static inline gboolean
+nm_device_activate_ip6_state_in_conf (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET6) == NM_DEVICE_IP_STATE_CONF;
+}
+
+static inline gboolean
+nm_device_activate_ip6_state_in_wait (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET6) == NM_DEVICE_IP_STATE_WAIT;
+}
+
+static inline gboolean
+nm_device_activate_ip6_state_done (NMDevice *self)
+{
+	return nm_device_activate_get_ip_state (self, AF_INET6) == NM_DEVICE_IP_STATE_DONE;
+}
 
 void nm_device_set_dhcp_anycast_address (NMDevice *device, const char *addr);
 
@@ -106,14 +146,24 @@ void nm_device_queue_recheck_available (NMDevice *device,
                                         NMDeviceStateReason available_reason,
                                         NMDeviceStateReason unavailable_reason);
 
-void nm_device_set_wwan_ip4_config (NMDevice *device, NMIP4Config *config);
-void nm_device_set_wwan_ip6_config (NMDevice *device, NMIP6Config *config);
+void nm_device_set_dev2_ip_config (NMDevice *device,
+                                   int addr_family,
+                                   NMIPConfig *config);
 
 gboolean nm_device_hw_addr_is_explict (NMDevice *device);
 
-void nm_device_ip_method_failed (NMDevice *self, int family, NMDeviceStateReason reason);
+void nm_device_ip_method_failed (NMDevice *self, int addr_family, NMDeviceStateReason reason);
+
+gboolean nm_device_sysctl_ip_conf_set (NMDevice *self,
+                                       int addr_family,
+                                       const char *property,
+                                       const char *value);
+
+NMIP4Config *nm_device_ip4_config_new (NMDevice *self);
+
+NMIP6Config *nm_device_ip6_config_new (NMDevice *self);
 
-gboolean nm_device_ipv6_sysctl_set (NMDevice *self, const char *property, const char *value);
+NMIPConfig *nm_device_ip_config_new (NMDevice *self, int addr_family);
 
 /*****************************************************************************/
 
@@ -147,9 +197,9 @@ void nm_device_commit_mtu (NMDevice *self);
 	)
 
 gboolean _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
-                                             GError **error, const char **whitelist);
+                                             GError **error, const char *const*whitelist);
 #define nm_device_hash_check_invalid_keys(hash, setting_name, error, ...) \
-	_nm_device_hash_check_invalid_keys (hash, setting_name, error, ((const char *[]) { __VA_ARGS__, NULL }))
+	_nm_device_hash_check_invalid_keys (hash, setting_name, error, NM_MAKE_STRV (__VA_ARGS__))
 
 gboolean nm_device_match_parent (NMDevice *device, const char *parent);
 gboolean nm_device_match_parent_hwaddr (NMDevice *device,
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index 0f76b23a..90360c9e 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -23,7 +23,6 @@
 #include "nm-device-tun.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <linux/if_tun.h>
 
@@ -231,9 +230,10 @@ create_and_realize (NMDevice *device,
 {
 	const char *iface = nm_device_get_iface (device);
 	NMPlatformLnkTun props = { };
-	NMPlatformError plerr;
 	NMSettingTun *s_tun;
-	gint64 owner, group;
+	gint64 owner;
+	gint64 group;
+	int r;
 
 	s_tun = nm_connection_get_setting_tun (connection);
 	g_return_val_if_fail (s_tun, FALSE);
@@ -261,17 +261,17 @@ create_and_realize (NMDevice *device,
 	props.multi_queue = nm_setting_tun_get_multi_queue (s_tun);
 	props.persist = TRUE;
 
-	plerr = nm_platform_link_tun_add (nm_device_get_platform (device),
-	                                  iface,
-	                                  &props,
-	                                  out_plink,
-	                                  NULL);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_tun_add (nm_device_get_platform (device),
+	                              iface,
+	                              &props,
+	                              out_plink,
+	                              NULL);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create TUN/TAP interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-veth.c b/src/devices/nm-device-veth.c
index 6f90758c..d7a59bae 100644
--- a/src/devices/nm-device-veth.c
+++ b/src/devices/nm-device-veth.c
@@ -20,9 +20,7 @@
 
 #include "nm-default.h"
 
-#include <errno.h>
 #include <stdlib.h>
-#include <string.h>
 
 #include "nm-device-veth.h"
 #include "nm-device-private.h"
diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c
index b7f0c4e7..ace6a24b 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/devices/nm-device-vlan.c
@@ -241,7 +241,7 @@ create_and_realize (NMDevice *device,
 	NMSettingVlan *s_vlan;
 	int parent_ifindex;
 	guint vlan_id;
-	NMPlatformError plerr;
+	int r;
 
 	s_vlan = nm_connection_get_setting_vlan (connection);
 	g_assert (s_vlan);
@@ -271,18 +271,18 @@ create_and_realize (NMDevice *device,
 
 	vlan_id = nm_setting_vlan_get_id (s_vlan);
 
-	plerr = nm_platform_link_vlan_add (nm_device_get_platform (device),
-	                                   iface,
-	                                   parent_ifindex,
-	                                   vlan_id,
-	                                   nm_setting_vlan_get_flags (s_vlan),
-	                                   out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_vlan_add (nm_device_get_platform (device),
+	                               iface,
+	                               parent_ifindex,
+	                               vlan_id,
+	                               nm_setting_vlan_get_flags (s_vlan),
+	                               out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create VLAN interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
@@ -493,7 +493,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		parent_mtu_maybe_changed (parent_device, NULL, device);
 	}
 
-	s_vlan = (NMSettingVlan *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_VLAN);
+	s_vlan = nm_device_get_applied_setting (device, NM_TYPE_SETTING_VLAN);
 	if (s_vlan) {
 		gs_free NMVlanQosMapping *ingress_map = NULL;
 		gs_free NMVlanQosMapping *egress_map = NULL;
diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c
index c34f4142..fc73c099 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/devices/nm-device-vxlan.c
@@ -22,8 +22,6 @@
 
 #include "nm-device-vxlan.h"
 
-#include <string.h>
-
 #include "nm-device-private.h"
 #include "nm-manager.h"
 #include "platform/nm-platform.h"
@@ -34,6 +32,7 @@
 #include "settings/nm-settings.h"
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
+#include "nm-core-internal.h"
 
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceVxlan);
@@ -170,11 +169,11 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
 	NMPlatformLnkVxlan props = { };
 	NMSettingVxlan *s_vxlan;
 	const char *str;
 	int ret;
+	int r;
 
 	s_vxlan = nm_connection_get_setting_vxlan (connection);
 	g_assert (s_vxlan);
@@ -213,13 +212,13 @@ create_and_realize (NMDevice *device,
 	props.l2miss = nm_setting_vxlan_get_l2_miss (s_vxlan);
 	props.l3miss = nm_setting_vxlan_get_l3_miss (s_vxlan);
 
-	plerr = nm_platform_link_vxlan_add (nm_device_get_platform (device), iface, &props, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_vxlan_add (nm_device_get_platform (device), iface, &props, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create VXLAN interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
@@ -386,6 +385,7 @@ update_connection (NMDevice *device, NMConnection *connection)
 {
 	NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE ((NMDeviceVxlan *) device);
 	NMSettingVxlan *s_vxlan = nm_connection_get_setting_vxlan (connection);
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	if (!s_vxlan) {
 		s_vxlan = (NMSettingVxlan *) nm_setting_vxlan_new ();
@@ -404,11 +404,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 	if (!address_matches (nm_setting_vxlan_get_remote (s_vxlan), priv->props.group, &priv->props.group6)) {
 		if (priv->props.group) {
 			g_object_set (s_vxlan, NM_SETTING_VXLAN_REMOTE,
-			              nm_utils_inet4_ntop (priv->props.group, NULL),
+			              nm_utils_inet4_ntop (priv->props.group, sbuf),
 			              NULL);
 		} else {
 			g_object_set (s_vxlan, NM_SETTING_VXLAN_REMOTE,
-			              nm_utils_inet6_ntop (&priv->props.group6, NULL),
+			              nm_utils_inet6_ntop (&priv->props.group6, sbuf),
 			              NULL);
 		}
 	}
@@ -416,11 +416,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 	if (!address_matches (nm_setting_vxlan_get_local (s_vxlan), priv->props.local, &priv->props.local6)) {
 		if (priv->props.local) {
 			g_object_set (s_vxlan, NM_SETTING_VXLAN_LOCAL,
-			              nm_utils_inet4_ntop (priv->props.local, NULL),
+			              nm_utils_inet4_ntop (priv->props.local, sbuf),
 			              NULL);
 		} else if (memcmp (&priv->props.local6, &in6addr_any, sizeof (in6addr_any))) {
 			g_object_set (s_vxlan, NM_SETTING_VXLAN_LOCAL,
-			              nm_utils_inet6_ntop (&priv->props.local6, NULL),
+			              nm_utils_inet6_ntop (&priv->props.local6, sbuf),
 			              NULL);
 		}
 	}
@@ -510,15 +510,15 @@ get_property (GObject *object, guint prop_id,
 		break;
 	case PROP_GROUP:
 		if (priv->props.group)
-			g_value_set_string (value, nm_utils_inet4_ntop (priv->props.group, NULL));
+			g_value_take_string (value, nm_utils_inet4_ntop_dup (priv->props.group));
 		else if (!IN6_IS_ADDR_UNSPECIFIED (&priv->props.group6))
-			g_value_set_string (value, nm_utils_inet6_ntop (&priv->props.group6, NULL));
+			g_value_take_string (value, nm_utils_inet6_ntop_dup (&priv->props.group6));
 		break;
 	case PROP_LOCAL:
 		if (priv->props.local)
-			g_value_set_string (value, nm_utils_inet4_ntop (priv->props.local, NULL));
+			g_value_take_string (value, nm_utils_inet4_ntop_dup (priv->props.local));
 		else if (!IN6_IS_ADDR_UNSPECIFIED (&priv->props.local6))
-			g_value_set_string (value, nm_utils_inet6_ntop (&priv->props.local6, NULL));
+			g_value_take_string (value, nm_utils_inet6_ntop_dup (&priv->props.local6));
 		break;
 	case PROP_TOS:
 		g_value_set_uchar (value, priv->props.tos);
diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c
index 62ec0274..a9eb1ab4 100644
--- a/src/devices/nm-device-wireguard.c
+++ b/src/devices/nm-device-wireguard.c
@@ -21,24 +21,128 @@
 
 #include "nm-device-wireguard.h"
 
+#include "nm-setting-wireguard.h"
+#include "nm-core-internal.h"
+#include "nm-utils/nm-secret-utils.h"
 #include "nm-device-private.h"
 #include "platform/nm-platform.h"
+#include "platform/nmp-object.h"
 #include "nm-device-factory.h"
+#include "nm-active-connection.h"
+#include "nm-act-request.h"
+#include "dns/nm-dns-manager.h"
 
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceWireGuard);
 
 /*****************************************************************************/
 
+/* TODO: activate profile with peer preshared-key-flags=2. On first activation, the secret is
+ *   requested (good). Enter it and connect. Reactivate the profile, now there is no password
+ *   prompt, as the secret is cached (good??). */
+
+/* TODO: unlike for other VPNs, we don't inject a direct route to the peers. That means,
+ *   you might get a routing sceneraio where the peer (VPN server) is reachable via the VPN.
+ *   How we handle adding routes to external gateway for other peers, has severe issues
+ *   as well. We may use policy-routing like wg-quick does. See also disussions at
+ *   https://www.wireguard.com/netns/#improving-the-classic-solutions */
+
+/* TODO: honor the TTL of DNS to determine when to retry resolving endpoints. */
+
+/* TODO: when we get multiple IP addresses when resolving a peer endpoint. We currently
+ *   just take the first from GAI. We should only accept AAAA/IPv6 if we also have a suitable
+ *   IPv6 address. The problem is, that we have to recheck that when IP addressing on other
+ *   interfaces changes. This makes it almost too cumbersome to implement. */
+
+/*****************************************************************************/
+
+G_STATIC_ASSERT (NM_WIREGUARD_PUBLIC_KEY_LEN   == NMP_WIREGUARD_PUBLIC_KEY_LEN);
+G_STATIC_ASSERT (NM_WIREGUARD_SYMMETRIC_KEY_LEN == NMP_WIREGUARD_SYMMETRIC_KEY_LEN);
+
+/*****************************************************************************/
+
+#define LINK_CONFIG_RATE_LIMIT_NSEC (50 * NM_UTILS_NS_PER_MSEC)
+
+/* a special @next_try_at_nsec timestamp indicating that we should try again as soon as possible. */
+#define NEXT_TRY_AT_NSEC_ASAP ((gint64) G_MAXINT64)
+
+/* a special @next_try_at_nsec timestamp that is
+ *  - positive (indicating resolve-checks are enabled)
+ *  - already in the past (we use the absolute timestamp of 1nsec for that). */
+#define NEXT_TRY_AT_NSEC_PAST ((gint64) 1)
+
+/* like %NEXT_TRY_AT_NSEC_ASAP, but used for indicating to retry ASAP for a @retry_in_msec value.
+ * That is a relative time duraction, contrary to @next_try_at_nsec which is an absolute
+ * timestamp. */
+#define RETRY_IN_MSEC_ASAP ((gint64) G_MAXINT64)
+
+#define RETRY_IN_MSEC_MAX ((gint64) (30 * 60 * 1000))
+
+typedef enum {
+	LINK_CONFIG_MODE_FULL,
+	LINK_CONFIG_MODE_REAPPLY,
+	LINK_CONFIG_MODE_ASSUME,
+	LINK_CONFIG_MODE_ENDPOINTS,
+} LinkConfigMode;
+
+typedef struct {
+	GCancellable *cancellable;
+
+	NMSockAddrUnion sockaddr;
+
+	/* the timestamp (in nm_utils_get_monotonic_timestamp_ns() scale) when we want
+	 * to retry resolving the endpoint (again).
+	 *
+	 * It may be set to %NEXT_TRY_AT_NSEC_ASAP to indicate to re-resolve as soon as possible.
+	 *
+	 * A @sockaddr is either fixed or it has
+	 *   - @cancellable set to indicate an ongoing request
+	 *   - @next_try_at_nsec set to a positive value, indicating when
+	 *     we ought to retry. */
+	gint64 next_try_at_nsec;
+
+	guint resolv_fail_count;
+} PeerEndpointResolveData;
+
+typedef struct {
+	NMWireGuardPeer *peer;
+
+	NMDeviceWireGuard *self;
+
+	CList lst_peers;
+
+	PeerEndpointResolveData ep_resolv;
+
+	/* dirty flag used during _peers_update_all(). */
+	bool dirty_update_all:1;
+} PeerData;
+
 NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWireGuard,
 	PROP_PUBLIC_KEY,
 	PROP_LISTEN_PORT,
 	PROP_FWMARK,
 );
 
+typedef struct {
+
+	NMDnsManager *dns_manager;
+
+	NMPlatformLnkWireGuard lnk_curr;
+	NMActRequestGetSecretsCallId *secrets_call_id;
+
+	CList lst_peers_head;
+	GHashTable *peers;
+
+	gint64 resolve_next_try_at;
+	guint  resolve_next_try_id;
+
+	gint64 link_config_last_at;
+	guint  link_config_delayed_id;
+} NMDeviceWireGuardPrivate;
+
 struct _NMDeviceWireGuard {
 	NMDevice parent;
-	NMPlatformLnkWireGuard props;
+	NMDeviceWireGuardPrivate _priv;
 };
 
 struct _NMDeviceWireGuardClass {
@@ -47,25 +151,710 @@ struct _NMDeviceWireGuardClass {
 
 G_DEFINE_TYPE (NMDeviceWireGuard, nm_device_wireguard, NM_TYPE_DEVICE)
 
-/******************************************************************/
+#define NM_DEVICE_WIREGUARD_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceWireGuard, NM_IS_DEVICE_WIREGUARD, NMDevice)
+
+/*****************************************************************************/
+
+static void _peers_resolve_start (NMDeviceWireGuard *self,
+                                  PeerData *peer_data);
+
+static void _peers_resolve_retry_reschedule (NMDeviceWireGuard *self,
+                                             gint64 new_next_try_at_nsec);
+
+static gboolean link_config_delayed_resolver_cb (gpointer user_data);
+
+static gboolean link_config_delayed_ratelimit_cb (gpointer user_data);
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_link_config_mode_to_string, LinkConfigMode,
+	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT (NULL),
+	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_FULL,      "full"),
+	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_REAPPLY,   "reapply"),
+	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_ASSUME,    "assume"),
+	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_ENDPOINTS, "endpoints"),
+);
+
+/*****************************************************************************/
+
+static gboolean
+_peer_data_equal (gconstpointer ptr_a, gconstpointer ptr_b)
+{
+	const PeerData *peer_data_a = ptr_a;
+	const PeerData *peer_data_b = ptr_b;
+
+	return nm_streq (nm_wireguard_peer_get_public_key (peer_data_a->peer),
+	                 nm_wireguard_peer_get_public_key (peer_data_b->peer));
+}
+
+static guint
+_peer_data_hash (gconstpointer ptr)
+{
+	const PeerData *peer_data = ptr;
+
+	return nm_hash_str (nm_wireguard_peer_get_public_key (peer_data->peer));
+}
+
+static PeerData *
+_peers_find (NMDeviceWireGuardPrivate *priv,
+             NMWireGuardPeer *peer)
+{
+	nm_assert (peer);
+
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (PeerData, peer) == 0);
+
+	return g_hash_table_lookup (priv->peers, &peer);
+}
+
+static void
+_peers_remove (NMDeviceWireGuardPrivate *priv,
+               PeerData *peer_data)
+{
+	nm_assert (peer_data);
+	nm_assert (g_hash_table_lookup (priv->peers, peer_data) == peer_data);
+
+	if (!g_hash_table_remove (priv->peers, peer_data))
+		nm_assert_not_reached ();
+
+	c_list_unlink_stale (&peer_data->lst_peers);
+	nm_wireguard_peer_unref (peer_data->peer);
+	nm_clear_g_cancellable (&peer_data->ep_resolv.cancellable);
+	g_slice_free (PeerData, peer_data);
+
+	if (c_list_is_empty (&peer_data->lst_peers)) {
+		nm_clear_g_source (&priv->resolve_next_try_id);
+		nm_clear_g_source (&priv->link_config_delayed_id);
+	}
+}
+
+static PeerData *
+_peers_add (NMDeviceWireGuard *self,
+            NMWireGuardPeer *peer)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	PeerData *peer_data;
+
+	nm_assert (peer);
+	nm_assert (nm_wireguard_peer_is_sealed (peer));
+	nm_assert (!_peers_find (priv, peer));
+
+	peer_data = g_slice_new (PeerData);
+	*peer_data = (PeerData) {
+		.self = self,
+		.peer = nm_wireguard_peer_ref (peer),
+		.ep_resolv = {
+			.sockaddr = NM_SOCK_ADDR_UNION_INIT_UNSPEC,
+		},
+	};
+
+	c_list_link_tail (&priv->lst_peers_head, &peer_data->lst_peers);
+	if (!nm_g_hash_table_add (priv->peers, peer_data))
+		nm_assert_not_reached ();
+	return peer_data;
+}
+
+static gboolean
+_peers_resolve_retry_timeout (gpointer user_data)
+{
+	NMDeviceWireGuard *self = user_data;
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	PeerData *peer_data;
+	gint64 now;
+	gint64 next;
+
+	priv->resolve_next_try_id = 0;
+
+	_LOGT (LOGD_DEVICE, "wireguard-peers: rechecking peer endpoints...");
+
+	now = nm_utils_get_monotonic_timestamp_ns ();
+	next = G_MAXINT64;
+	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+		if (peer_data->ep_resolv.next_try_at_nsec <= 0)
+			continue;
+
+		if (peer_data->ep_resolv.cancellable) {
+			/* we are currently resolving a name. We don't need the global
+			 * watchdog to guard this peer. No need to adjust @next for
+			 * this one, when the currently ongoing resolving completes, we
+			 * may reschedule. Skip. */
+			continue;
+		}
+
+		if (   peer_data->ep_resolv.next_try_at_nsec == NEXT_TRY_AT_NSEC_ASAP
+		    || now >= peer_data->ep_resolv.next_try_at_nsec) {
+			_peers_resolve_start (self, peer_data);
+			/* same here. Now we are resolving. We don't need the global
+			 * watchdog. Skip w.r.t. finding @next. */
+			continue;
+		}
+
+		if (next > peer_data->ep_resolv.next_try_at_nsec)
+			next = peer_data->ep_resolv.next_try_at_nsec;
+	}
+	if (next < G_MAXINT64)
+		_peers_resolve_retry_reschedule (self, next);
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+_peers_resolve_retry_reschedule (NMDeviceWireGuard *self,
+                                 gint64 new_next_try_at_nsec)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	guint32 interval_ms;
+	gint64 now;
+
+	nm_assert (new_next_try_at_nsec > 0);
+	nm_assert (new_next_try_at_nsec != NEXT_TRY_AT_NSEC_ASAP);
+
+	if (   priv->resolve_next_try_id
+	    && priv->resolve_next_try_at <= new_next_try_at_nsec) {
+		/* we already have an earlier timeout scheduled (possibly for
+		 * another peer that expires sooner). Don't reschedule now.
+		 * Even if the scheduled timeout expires too early, we will
+		 * compute the right next-timeout and reschedule then. */
+		return;
+	}
+
+	now = nm_utils_get_monotonic_timestamp_ns ();
+
+	/* schedule at most one day ahead. No problem if we expire earlier
+	 * than expected. Also, rate-limit to 500 msec. */
+	interval_ms = NM_CLAMP ((new_next_try_at_nsec - now) / NM_UTILS_NS_PER_MSEC,
+	                        (gint64) 500,
+	                        (gint64) (24*60*60*1000));
+
+	_LOGT (LOGD_DEVICE, "wireguard-peers: schedule rechecking peer endpoints in %u msec",
+	       interval_ms);
+
+	nm_clear_g_source (&priv->resolve_next_try_id);
+	priv->resolve_next_try_at = new_next_try_at_nsec;
+	priv->resolve_next_try_id = g_timeout_add (interval_ms,
+	                                           _peers_resolve_retry_timeout,
+	                                           self);
+}
+
+static void
+_peers_resolve_retry_reschedule_for_peer (NMDeviceWireGuard *self,
+                                          PeerData *peer_data,
+                                          gint64 retry_in_msec)
+{
+	nm_assert (retry_in_msec >= 0);
+
+	if (retry_in_msec == RETRY_IN_MSEC_ASAP) {
+		_peers_resolve_start (self, peer_data);
+		return;
+	}
+
+	peer_data->ep_resolv.next_try_at_nsec =   nm_utils_get_monotonic_timestamp_ns ()
+	                                        + (retry_in_msec * NM_UTILS_NS_PER_MSEC);
+	_peers_resolve_retry_reschedule (self, peer_data->ep_resolv.next_try_at_nsec);
+}
+
+static gint64
+_peers_retry_in_msec (PeerData *peer_data,
+                      gboolean after_failure)
+{
+	if (peer_data->ep_resolv.next_try_at_nsec == NEXT_TRY_AT_NSEC_ASAP) {
+		peer_data->ep_resolv.resolv_fail_count = 0;
+		return RETRY_IN_MSEC_ASAP;
+	}
+
+	if (after_failure) {
+		if (peer_data->ep_resolv.resolv_fail_count < G_MAXUINT)
+			peer_data->ep_resolv.resolv_fail_count++;
+	} else
+		peer_data->ep_resolv.resolv_fail_count = 0;
+
+	if (!after_failure)
+		return RETRY_IN_MSEC_MAX;
 
-static GVariant *
-get_public_key_as_variant (const NMDeviceWireGuard *self)
+	if (peer_data->ep_resolv.resolv_fail_count > 20)
+		return RETRY_IN_MSEC_MAX;
+
+	/* double the retry-time, starting with one second. */
+	return NM_MIN (RETRY_IN_MSEC_MAX,
+	               (1u << peer_data->ep_resolv.resolv_fail_count) * 500);
+}
+
+static void
+_peers_resolve_cb (GObject *source_object,
+                   GAsyncResult *res,
+                   gpointer user_data)
+{
+	NMDeviceWireGuard *self;
+	PeerData *peer_data;
+	gs_free_error GError *resolv_error = NULL;
+	GList *list;
+	gboolean changed = FALSE;
+	NMSockAddrUnion sockaddr;
+	gint64 retry_in_msec;
+	char s_sockaddr[100];
+	char s_retry[100];
+
+	list = g_resolver_lookup_by_name_finish (G_RESOLVER (source_object), res, &resolv_error);
+
+	if (nm_utils_error_is_cancelled (resolv_error, FALSE))
+		return;
+
+	peer_data = user_data;
+	self = peer_data->self;
+
+	g_clear_object (&peer_data->ep_resolv.cancellable);
+
+	nm_assert ((!resolv_error) != (!list));
+
+#define _retry_in_msec_to_string(retry_in_msec, s_retry) \
+	({ \
+		gint64 _retry_in_msec = (retry_in_msec); \
+		\
+		  _retry_in_msec == RETRY_IN_MSEC_ASAP \
+		? "right away" \
+		: nm_sprintf_buf (s_retry, "in %"G_GINT64_FORMAT" msec", _retry_in_msec); \
+	})
+
+	if (   resolv_error
+	    && !g_error_matches (resolv_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND)) {
+		retry_in_msec = _peers_retry_in_msec (peer_data, TRUE);
+
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: failure to resolve endpoint \"%s\": %s (retry %s)",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       nm_wireguard_peer_get_endpoint (peer_data->peer),
+		       resolv_error->message,
+		       _retry_in_msec_to_string (retry_in_msec, s_retry));
+
+		_peers_resolve_retry_reschedule_for_peer (self, peer_data, retry_in_msec);
+		return;
+	}
+
+	sockaddr = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
+
+	if (!resolv_error) {
+		GList *iter;
+
+		for (iter = list; iter; iter = iter->next) {
+			GInetAddress *a = iter->data;
+			GSocketFamily f = g_inet_address_get_family (a);
+
+			if (f == G_SOCKET_FAMILY_IPV4) {
+				nm_assert (g_inet_address_get_native_size (a) == sizeof (struct in_addr));
+				sockaddr.in = (struct sockaddr_in) {
+					.sin_family = AF_INET,
+					.sin_port   = htons (nm_sock_addr_endpoint_get_port (_nm_wireguard_peer_get_endpoint (peer_data->peer))),
+				};
+				memcpy (&sockaddr.in.sin_addr, g_inet_address_to_bytes (a), sizeof (struct in_addr));
+				break;
+			}
+			if (f == G_SOCKET_FAMILY_IPV6) {
+				nm_assert (g_inet_address_get_native_size (a) == sizeof (struct in6_addr));
+				sockaddr.in6 = (struct sockaddr_in6) {
+					.sin6_family   = AF_INET6,
+					.sin6_port     = htons (nm_sock_addr_endpoint_get_port (_nm_wireguard_peer_get_endpoint (peer_data->peer))),
+					.sin6_scope_id = 0,
+					.sin6_flowinfo = 0,
+				};
+				memcpy (&sockaddr.in6.sin6_addr, g_inet_address_to_bytes (a), sizeof (struct in6_addr));
+				break;
+			}
+		}
+
+		g_list_free_full (list, g_object_unref);
+	}
+
+	if (sockaddr.sa.sa_family == AF_UNSPEC) {
+		/* we failed to resolve the name. There is no need to reset the previous
+		 * sockaddr. Either it was already AF_UNSPEC, or we had a good name
+		 * from resolving before. In that case, we don't want to throw away
+		 * a possibly good IP address, since WireGuard supports automatic roaming
+		 * anyway. Either the IP address is still good (and we would wrongly
+		 * reject it), or it isn't -- in which case it does not hurt much. */
+	} else {
+		if (nm_sock_addr_union_cmp (&peer_data->ep_resolv.sockaddr, &sockaddr) != 0)
+			changed = TRUE;
+		peer_data->ep_resolv.sockaddr = sockaddr;
+	}
+
+	if (   resolv_error
+	    || peer_data->ep_resolv.sockaddr.sa.sa_family == AF_UNSPEC) {
+		/* while it technically did not fail, something is probably odd. Retry frequently to
+		 * resolve the name, like we would do for normal failures. */
+		retry_in_msec = _peers_retry_in_msec (peer_data, TRUE);
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: no %sresults for endpoint \"%s\" (retry %s)",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       resolv_error ? "" : "suitable ",
+		       nm_wireguard_peer_get_endpoint (peer_data->peer),
+		       _retry_in_msec_to_string (retry_in_msec, s_retry));
+	} else {
+		retry_in_msec = _peers_retry_in_msec (peer_data, FALSE);
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: endpoint \"%s\" resolved to %s (retry %s)",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       nm_wireguard_peer_get_endpoint (peer_data->peer),
+		       nm_sock_addr_union_to_string (&peer_data->ep_resolv.sockaddr, s_sockaddr, sizeof (s_sockaddr)),
+		       _retry_in_msec_to_string (retry_in_msec, s_retry));
+	}
+
+	_peers_resolve_retry_reschedule_for_peer (self, peer_data, retry_in_msec);
+
+	if (changed) {
+		NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+		/* schedule the job in the background, to give multiple resolve events time
+		 * to complete. */
+		nm_clear_g_source (&priv->link_config_delayed_id);
+		priv->link_config_delayed_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE + 1,
+		                                                link_config_delayed_resolver_cb,
+		                                                self,
+		                                                NULL);
+	}
+}
+
+static void
+_peers_resolve_start (NMDeviceWireGuard *self,
+                      PeerData *peer_data)
+{
+	gs_unref_object GResolver *resolver = NULL;
+	const char *host;
+
+	resolver = g_resolver_get_default ();
+
+	nm_assert (!peer_data->ep_resolv.cancellable);
+
+	peer_data->ep_resolv.cancellable = g_cancellable_new ();
+
+	/* set a special next-try timestamp. It is positive, and indicates
+	 * that we are in the process of trying.
+	 * This timestamp however already lies in the past, but that is correct,
+	 * because we are currently in the process of trying. We will determine
+	 * a next-try timestamp once the try completes. */
+	peer_data->ep_resolv.next_try_at_nsec = NEXT_TRY_AT_NSEC_PAST;
+
+	host = nm_sock_addr_endpoint_get_host (_nm_wireguard_peer_get_endpoint (peer_data->peer));
+
+	g_resolver_lookup_by_name_async (resolver,
+	                                 host,
+	                                 peer_data->ep_resolv.cancellable,
+	                                 _peers_resolve_cb,
+	                                 peer_data);
+
+	_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: resolving name \"%s\" for endpoint \"%s\"...",
+	       nm_wireguard_peer_get_public_key (peer_data->peer),
+	       host,
+	       nm_wireguard_peer_get_endpoint (peer_data->peer));
+}
+
+static void
+_peers_resolve_reresolve_all (NMDeviceWireGuard *self)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	PeerData *peer_data;
+
+	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+		if (peer_data->ep_resolv.cancellable) {
+			/* remember to retry when the currently ongoing request completes. */
+			peer_data->ep_resolv.next_try_at_nsec = NEXT_TRY_AT_NSEC_ASAP;
+		} else if (peer_data->ep_resolv.next_try_at_nsec <= 0) {
+			/* this peer does not require resolving the name. Skip it. */
+		} else {
+			/* we have a next-try scheduled. Restart right away. */
+			peer_data->ep_resolv.resolv_fail_count = 0;
+			_peers_resolve_start (self, peer_data);
+		}
+	}
+}
+
+static gboolean
+_peers_update (NMDeviceWireGuard *self,
+               PeerData *peer_data,
+               NMWireGuardPeer *peer,
+               gboolean force_update)
+{
+	nm_auto_unref_wgpeer NMWireGuardPeer *old_peer = NULL;
+	NMSockAddrEndpoint *old_endpoint;
+	NMSockAddrEndpoint *endpoint;
+	gboolean endpoint_changed = FALSE;
+	gboolean changed;
+	NMSockAddrUnion sockaddr;
+	gboolean sockaddr_fixed;
+	char sockaddr_sbuf[100];
+
+	nm_assert (peer);
+	nm_assert (nm_wireguard_peer_is_sealed (peer));
+
+	if (   peer == peer_data->peer
+	    && !force_update)
+		return FALSE;
+
+	changed = (nm_wireguard_peer_cmp (peer,
+	                                  peer_data->peer,
+	                                  NM_SETTING_COMPARE_FLAG_EXACT) != 0);
+
+	old_peer = peer_data->peer;
+	peer_data->peer = nm_wireguard_peer_ref (peer);
+
+	old_endpoint = old_peer ? _nm_wireguard_peer_get_endpoint (old_peer) : NULL;
+	endpoint     = peer     ? _nm_wireguard_peer_get_endpoint (peer)     : NULL;
+
+	endpoint_changed = (   endpoint != old_endpoint
+	                    && (   !old_endpoint
+	                        || !endpoint
+	                        || !nm_streq (nm_sock_addr_endpoint_get_endpoint (old_endpoint),
+	                                      nm_sock_addr_endpoint_get_endpoint (endpoint))));
+
+	if (   !force_update
+	    && !endpoint_changed) {
+		/* nothing to do. */
+		return changed;
+	}
+
+	sockaddr = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC;
+	sockaddr_fixed = TRUE;
+	if (   endpoint
+	    && nm_sock_addr_endpoint_get_host (endpoint)) {
+		if (!nm_sock_addr_endpoint_get_fixed_sockaddr (endpoint, &sockaddr)) {
+			/* we have an endpoint, but it's not a static IP address. We need to resolve
+			 * the names. */
+			sockaddr_fixed = FALSE;
+		}
+	}
+
+	if (nm_sock_addr_union_cmp (&peer_data->ep_resolv.sockaddr, &sockaddr) != 0)
+		changed = TRUE;
+
+	nm_clear_g_cancellable (&peer_data->ep_resolv.cancellable);
+
+	peer_data->ep_resolv = (PeerEndpointResolveData) {
+		.sockaddr          = sockaddr,
+		.resolv_fail_count = 0,
+		.cancellable       = NULL,
+		.next_try_at_nsec  = 0,
+	};
+
+	if (!endpoint) {
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: no endpoint configured",
+		       nm_wireguard_peer_get_public_key (peer_data->peer));
+	} else if (!nm_sock_addr_endpoint_get_host (endpoint)) {
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: invalid endpoint \"%s\"",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       nm_sock_addr_endpoint_get_endpoint (endpoint));
+	} else if (sockaddr_fixed) {
+		_LOGT (LOGD_DEVICE, "wireguard-peer[%s]: fixed endpoint \"%s\" (%s)",
+		       nm_wireguard_peer_get_public_key (peer_data->peer),
+		       nm_sock_addr_endpoint_get_endpoint (endpoint),
+		       nm_sock_addr_union_to_string (&peer_data->ep_resolv.sockaddr, sockaddr_sbuf, sizeof (sockaddr_sbuf)));
+	} else
+		_peers_resolve_start (self, peer_data);
+
+	return changed;
+}
+
+static void
+_peers_remove_all (NMDeviceWireGuardPrivate *priv)
 {
-	return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-	                                  self->props.public_key, sizeof (self->props.public_key), 1);
+	PeerData *peer_data;
+
+	while ((peer_data = c_list_first_entry (&priv->lst_peers_head, PeerData, lst_peers)))
+		_peers_remove (priv, peer_data);
 }
 
 static void
+_peers_update_all (NMDeviceWireGuard *self,
+                   NMSettingWireGuard *s_wg,
+                   gboolean *out_peers_removed)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	PeerData *peer_data_safe;
+	PeerData *peer_data;
+	guint i, n;
+	gboolean peers_removed = FALSE;
+
+	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers)
+		peer_data->dirty_update_all = TRUE;
+
+	n = nm_setting_wireguard_get_peers_len (s_wg);
+	for (i = 0; i < n; i++) {
+		NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i);
+		gboolean added = FALSE;
+
+		peer_data = _peers_find (priv, peer);
+		if (!peer_data) {
+			peer_data = _peers_add (self, peer);
+			added = TRUE;
+		}
+		_peers_update (self, peer_data, peer, added);
+		peer_data->dirty_update_all = FALSE;
+	}
+
+	c_list_for_each_entry_safe (peer_data, peer_data_safe, &priv->lst_peers_head, lst_peers) {
+		if (peer_data->dirty_update_all) {
+			_peers_remove (priv, peer_data);
+			peers_removed = TRUE;
+		}
+	}
+
+	NM_SET_OUT (out_peers_removed, peers_removed);
+}
+
+static void
+_peers_get_platform_list (NMDeviceWireGuardPrivate *priv,
+                          LinkConfigMode config_mode,
+                          NMPWireGuardPeer **out_peers,
+                          NMPlatformWireGuardChangePeerFlags **out_peer_flags,
+                          guint *out_len,
+                          GArray **out_allowed_ips_data)
+{
+	gs_free NMPWireGuardPeer *plpeers = NULL;
+	gs_free NMPlatformWireGuardChangePeerFlags *plpeer_flags = NULL;
+	gs_unref_array GArray *allowed_ips = NULL;
+	PeerData *peer_data;
+	guint i_good;
+	guint n_aip;
+	guint i_aip;
+	guint len;
+	guint i;
+
+	nm_assert (out_peers && !*out_peers);
+	nm_assert (out_peer_flags && !*out_peer_flags);
+	nm_assert (out_len && *out_len == 0);
+	nm_assert (out_allowed_ips_data && !*out_allowed_ips_data);
+
+	len = g_hash_table_size (priv->peers);
+
+	nm_assert (len == c_list_length (&priv->lst_peers_head));
+
+	if (len == 0)
+		return;
+
+	plpeers = g_new0 (NMPWireGuardPeer, len);
+	plpeer_flags = g_new0 (NMPlatformWireGuardChangePeerFlags, len);
+
+	i_good = 0;
+	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
+		NMPlatformWireGuardChangePeerFlags *plf = &plpeer_flags[i_good];
+		NMPWireGuardPeer *plp = &plpeers[i_good];
+		NMSettingSecretFlags psk_secret_flags;
+
+		if (!nm_utils_base64secret_decode (nm_wireguard_peer_get_public_key (peer_data->peer),
+		                                   sizeof (plp->public_key),
+		                                   plp->public_key))
+			continue;
+
+		*plf = NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE;
+
+		plp->persistent_keepalive_interval = nm_wireguard_peer_get_persistent_keepalive (peer_data->peer);
+		if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+		                            LINK_CONFIG_MODE_REAPPLY))
+			*plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_KEEPALIVE_INTERVAL;
+
+		/* if the peer has an endpoint but it is not yet resolved (not ready),
+		 * we still configure it and leave the endpoint unspecified. Later,
+		 * when we can resolve the endpoint, we will update. */
+		plp->endpoint = peer_data->ep_resolv.sockaddr;
+		if (plp->endpoint.sa.sa_family == AF_UNSPEC) {
+			/* we don't actually ever clear endpoints, if we don't have better information. */
+		} else
+			*plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ENDPOINT;
+
+		if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+		                            LINK_CONFIG_MODE_REAPPLY)) {
+			psk_secret_flags = nm_wireguard_peer_get_preshared_key_flags (peer_data->peer);
+			if (!NM_FLAGS_HAS (psk_secret_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) {
+				if (   !nm_utils_base64secret_decode (nm_wireguard_peer_get_preshared_key (peer_data->peer),
+				                                      sizeof (plp->preshared_key),
+				                                      plp->preshared_key)
+				    && config_mode == LINK_CONFIG_MODE_FULL)
+					goto skip;
+			}
+			*plf |= NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY;
+		}
+
+		if (   NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+		                               LINK_CONFIG_MODE_REAPPLY)
+		    && ((n_aip = nm_wireguard_peer_get_allowed_ips_len (peer_data->peer)) > 0)) {
+			if (!allowed_ips)
+				allowed_ips = g_array_new (FALSE, FALSE, sizeof (NMPWireGuardAllowedIP));
+
+			*plf |=   NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_ALLOWEDIPS
+			        | NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REPLACE_ALLOWEDIPS;
+
+			plp->_construct_idx_start = allowed_ips->len;
+			for (i_aip = 0; i_aip < n_aip; i_aip++) {
+				const char *aip;
+				NMIPAddr addrbin = { };
+				int addr_family;
+				gboolean valid;
+				int prefix;
+
+				aip = nm_wireguard_peer_get_allowed_ip (peer_data->peer, i_aip, &valid);
+				if (   !valid
+				    || !nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC,
+				                                          aip,
+				                                          &addr_family,
+				                                          &addrbin,
+				                                          &prefix)) {
+					/* the address is really not expected to be invalid, because then
+					 * the connection would not verify. Anyway, silently skip it. */
+					continue;
+				}
+
+				if (prefix == -1)
+					prefix = addr_family == AF_INET ? 32 : 128;
+
+				g_array_append_val (allowed_ips,
+				                    ((NMPWireGuardAllowedIP) {
+				                        .family = addr_family,
+				                        .mask = prefix,
+				                        .addr = addrbin,
+				                    }));
+			}
+			plp->_construct_idx_end = allowed_ips->len;
+		}
+
+		i_good++;
+		continue;
+
+skip:
+		memset (plp, 0, sizeof (*plp));
+	}
+
+	if (i_good == 0)
+		return;
+
+	for (i = 0; i < i_good; i++) {
+		NMPWireGuardPeer *plp = &plpeers[i];
+		guint l;
+
+		if (plp->_construct_idx_end == 0) {
+			nm_assert (plp->_construct_idx_start == 0);
+			plp->allowed_ips = NULL;
+			plp->allowed_ips_len = 0;
+		} else {
+			nm_assert (plp->_construct_idx_start < plp->_construct_idx_end);
+			l = plp->_construct_idx_end - plp->_construct_idx_start;
+			plp->allowed_ips = &g_array_index (allowed_ips, NMPWireGuardAllowedIP, plp->_construct_idx_start);
+			plp->allowed_ips_len = l;
+		}
+	}
+	*out_peers            = g_steal_pointer (&plpeers);
+	*out_peer_flags       = g_steal_pointer (&plpeer_flags);;
+	*out_len              = i_good;
+	*out_allowed_ips_data = g_steal_pointer (&allowed_ips);
+}
+
+/*****************************************************************************/
+
+static void
 update_properties (NMDevice *device)
 {
 	NMDeviceWireGuard *self;
+	NMDeviceWireGuardPrivate *priv;
 	const NMPlatformLink *plink;
 	const NMPlatformLnkWireGuard *props = NULL;
 	int ifindex;
 
 	g_return_if_fail (NM_IS_DEVICE_WIREGUARD (device));
 	self = NM_DEVICE_WIREGUARD (device);
+	priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
 
 	ifindex = nm_device_get_ifindex (device);
 	props = nm_platform_link_get_lnk_wireguard (nm_device_get_platform (device), ifindex, &plink);
@@ -78,16 +867,16 @@ update_properties (NMDevice *device)
 
 #define CHECK_PROPERTY_CHANGED(field, prop) \
 	G_STMT_START { \
-		if (self->props.field != props->field) { \
-			self->props.field = props->field; \
+		if (priv->lnk_curr.field != props->field) { \
+			priv->lnk_curr.field = props->field; \
 			_notify (self, prop); \
 		} \
 	} G_STMT_END
 
 #define CHECK_PROPERTY_CHANGED_ARRAY(field, prop) \
 	G_STMT_START { \
-		if (memcmp (&self->props.field, &props->field, sizeof (props->field)) != 0) { \
-			memcpy (&self->props.field, &props->field, sizeof (props->field)); \
+		if (memcmp (&priv->lnk_curr.field, &props->field, sizeof (priv->lnk_curr.field)) != 0) { \
+			memcpy (&priv->lnk_curr.field, &props->field, sizeof (priv->lnk_curr.field)); \
 			_notify (self, prop); \
 		} \
 	} G_STMT_END
@@ -107,24 +896,655 @@ link_changed (NMDevice *device,
 	update_properties (device);
 }
 
+static NMDeviceCapabilities
+get_generic_capabilities (NMDevice *dev)
+{
+	return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+create_and_realize (NMDevice *device,
+                    NMConnection *connection,
+                    NMDevice *parent,
+                    const NMPlatformLink **out_plink,
+                    GError **error)
+{
+	const char *iface = nm_device_get_iface (device);
+	int r;
+
+	g_return_val_if_fail (iface, FALSE);
+
+	r = nm_platform_link_wireguard_add (nm_device_get_platform (device), iface, out_plink);
+	if (r < 0) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
+		             "Failed to create WireGuard interface '%s' for '%s': %s",
+		             iface,
+		             nm_connection_get_id (connection),
+		             nm_strerror (r));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+_secrets_cancel (NMDeviceWireGuard *self)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	if (priv->secrets_call_id)
+		nm_act_request_cancel_secrets (NULL, priv->secrets_call_id);
+	nm_assert (!priv->secrets_call_id);
+}
+
+static void
+_secrets_cb (NMActRequest *req,
+             NMActRequestGetSecretsCallId *call_id,
+             NMSettingsConnection *connection,
+             GError *error,
+             gpointer user_data)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (user_data);
+	NMDevice *device = NM_DEVICE (self);
+	NMDeviceWireGuardPrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE_WIREGUARD (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	g_return_if_fail (priv->secrets_call_id == call_id);
+
+	priv->secrets_call_id = NULL;
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	g_return_if_fail (req == nm_device_get_act_request (device));
+	g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
+	g_return_if_fail (nm_act_request_get_settings_connection (req) == connection);
+
+	if (error) {
+		_LOGW (LOGD_ETHER, "%s", error->message);
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+	} else
+		nm_device_activate_schedule_stage1_device_prepare (device);
+}
+
+static void
+_secrets_get_secrets (NMDeviceWireGuard *self,
+                      const char *setting_name,
+                      NMSecretAgentGetSecretsFlags flags,
+                      const char *const*hints)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	NMActRequest *req;
+
+	_secrets_cancel (self);
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	priv->secrets_call_id = nm_act_request_get_secrets (req,
+	                                                    TRUE,
+	                                                    setting_name,
+	                                                    flags,
+	                                                    hints,
+	                                                    _secrets_cb,
+	                                                    self);
+	g_return_if_fail (priv->secrets_call_id);
+}
+
+static NMActStageReturn
+_secrets_handle_auth_or_fail (NMDeviceWireGuard *self,
+                              NMActRequest *req,
+                              gboolean new_secrets)
+{
+	NMConnection *applied_connection;
+	const char *setting_name;
+	gs_unref_ptrarray GPtrArray *hints = NULL;
+
+	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
+		return NM_ACT_STAGE_RETURN_FAILURE;
+
+	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
+
+	nm_active_connection_clear_secrets (NM_ACTIVE_CONNECTION (req));
+
+	applied_connection = nm_act_request_get_applied_connection (req);
+	setting_name = nm_connection_need_secrets (applied_connection, &hints);
+	if (!setting_name) {
+		_LOGI (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets.");
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	if (hints)
+		g_ptr_array_add (hints, NULL);
+
+	_secrets_get_secrets (self,
+	                      setting_name,
+	                        NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+	                      | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0),
+	                      (  hints
+	                       ? (const char *const*) hints->pdata
+	                       : NULL));
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+/*****************************************************************************/
+
+static void
+_dns_config_changed (NMDnsManager *dns_manager, NMDeviceWireGuard *self)
+{
+	/* when the DNS configuration changes, we re-resolve the peer addresses.
+	 *
+	 * Possibly, we should also do that when the default-route changes, but it's
+	 * hard to figure out when that happens. */
+	_peers_resolve_reresolve_all (self);
+}
+
+/*****************************************************************************/
+
+static NMActStageReturn
+link_config (NMDeviceWireGuard *self,
+             const char *reason,
+             LinkConfigMode config_mode,
+             NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	nm_auto_bzero_secret_ptr NMSecretPtr wg_lnk_clear_private_key = NM_SECRET_PTR_INIT ();
+	NMSettingWireGuard *s_wg;
+	NMConnection *connection;
+	NMActStageReturn ret;
+	gs_unref_array GArray *allowed_ips_data = NULL;
+	NMPlatformLnkWireGuard wg_lnk;
+	gs_free NMPWireGuardPeer *plpeers = NULL;
+	gs_free NMPlatformWireGuardChangePeerFlags *plpeer_flags = NULL;
+	guint plpeers_len = 0;
+	const char *setting_name;
+	gboolean peers_removed;
+	NMPlatformWireGuardChangeFlags wg_change_flags;
+	int ifindex;
+	int r;
+
+	NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+
+	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+	s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
+	g_return_val_if_fail (s_wg, NM_ACT_STAGE_RETURN_FAILURE);
+
+	priv->link_config_last_at = nm_utils_get_monotonic_timestamp_ns ();
+
+	_LOGT (LOGD_DEVICE, "wireguard link config (%s, %s)...",
+	       reason, _link_config_mode_to_string (config_mode));
+
+	if (!priv->dns_manager) {
+		priv->dns_manager = g_object_ref (nm_dns_manager_get ());
+		g_signal_connect (priv->dns_manager, NM_DNS_MANAGER_CONFIG_CHANGED, G_CALLBACK (_dns_config_changed), self);
+	}
+
+	if (   NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL)
+	    && (setting_name = nm_connection_need_secrets (connection, NULL))) {
+		NMActRequest *req = nm_device_get_act_request (NM_DEVICE (self));
+
+		_LOGD (LOGD_DEVICE,
+		       "Activation: connection '%s' has security, but secrets are required.",
+		       nm_connection_get_id (connection));
+
+		ret = _secrets_handle_auth_or_fail (self, req, FALSE);
+		if (ret != NM_ACT_STAGE_RETURN_SUCCESS) {
+			if (ret != NM_ACT_STAGE_RETURN_POSTPONE) {
+				nm_assert (ret == NM_ACT_STAGE_RETURN_FAILURE);
+				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+			}
+			return ret;
+		}
+	}
+
+	ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self));
+	if (ifindex <= 0) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	_peers_update_all (self, s_wg, &peers_removed);
+
+	wg_lnk = (NMPlatformLnkWireGuard) { };
 
-/******************************************************************/
+	wg_change_flags = NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE;
+
+	if (   NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL)
+	    || (   NM_IN_SET (config_mode, LINK_CONFIG_MODE_REAPPLY)
+	        && peers_removed))
+		wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS;
+
+	if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
+	                            LINK_CONFIG_MODE_REAPPLY)) {
+
+		wg_lnk.listen_port = nm_setting_wireguard_get_listen_port (s_wg),
+		wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT;
+
+		wg_lnk.fwmark = nm_setting_wireguard_get_fwmark (s_wg),
+		wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK;
+
+		if (nm_utils_base64secret_decode (nm_setting_wireguard_get_private_key (s_wg),
+		                                  sizeof (wg_lnk.private_key),
+		                                  wg_lnk.private_key)) {
+			wg_lnk_clear_private_key = NM_SECRET_PTR_ARRAY (wg_lnk.private_key);
+			wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY;
+		} else {
+			if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL)) {
+				_LOGD (LOGD_DEVICE, "the provided private-key is invalid");
+				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+				return NM_ACT_STAGE_RETURN_FAILURE;
+			}
+		}
+	}
+
+	_peers_get_platform_list (priv,
+	                          config_mode,
+	                          &plpeers,
+	                          &plpeer_flags,
+	                          &plpeers_len,
+	                          &allowed_ips_data);
+
+	r = nm_platform_link_wireguard_change (nm_device_get_platform (NM_DEVICE (self)),
+	                                       ifindex,
+	                                       &wg_lnk,
+	                                       plpeers,
+	                                       plpeer_flags,
+	                                       plpeers_len,
+	                                       wg_change_flags);
+
+	nm_explicit_bzero (plpeers, sizeof (plpeers) * plpeers_len);
+
+	if (r < 0) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static void
+link_config_delayed (NMDeviceWireGuard *self,
+                     const char *reason)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	gint64 now;
+
+	priv->link_config_delayed_id = 0;
+
+	if (priv->link_config_last_at != 0) {
+		now = nm_utils_get_monotonic_timestamp_ns ();
+		if (now < priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC) {
+			/* we ratelimit calls to link_config(), because we call this whenever a resolver
+			 * completes. */
+			_LOGT (LOGD_DEVICE, "wireguard link config (%s) (postponed)", reason);
+			priv->link_config_delayed_id = g_timeout_add (NM_MAX ((priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC - now) / NM_UTILS_NS_PER_MSEC,
+			                                                      (gint64) 1),
+			                                              link_config_delayed_ratelimit_cb,
+			                                              self);
+			return;
+		}
+	}
+
+	link_config (self, reason, LINK_CONFIG_MODE_ENDPOINTS, NULL);
+}
+
+static gboolean
+link_config_delayed_ratelimit_cb (gpointer user_data)
+{
+	link_config_delayed (user_data, "after-ratelimiting");
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+link_config_delayed_resolver_cb (gpointer user_data)
+{
+	link_config_delayed (user_data, "resolver-update");
+	return G_SOURCE_REMOVE;
+}
+
+static NMActStageReturn
+act_stage2_config (NMDevice *device,
+                   NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceSysIfaceState sys_iface_state;
+	NMDeviceStateReason failure_reason;
+	NMActStageReturn ret;
+
+	sys_iface_state = nm_device_sys_iface_state_get (device);
+
+	if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_EXTERNAL) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	ret = link_config (NM_DEVICE_WIREGUARD (device),
+	                                        "configure",
+	                                          (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME)
+	                                        ? LINK_CONFIG_MODE_ASSUME
+	                                        : LINK_CONFIG_MODE_FULL,
+	                                        &failure_reason);
+
+	if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME) {
+		/* this never fails. */
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	if (ret != NM_ACT_STAGE_RETURN_FAILURE) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
+		return ret;
+	}
+
+	nm_device_state_changed (device,
+	                         NM_DEVICE_STATE_FAILED,
+	                         failure_reason);
+	NM_SET_OUT (out_failure_reason, failure_reason);
+	return NM_ACT_STAGE_RETURN_FAILURE;
+}
+
+static NMIPConfig *
+_get_dev2_ip_config (NMDeviceWireGuard *self,
+                     int addr_family)
+{
+	gs_unref_object NMIPConfig *ip_config = NULL;
+	NMConnection *connection;
+	NMSettingWireGuard *s_wg;
+	guint n_peers;
+	guint i;
+	int ip_ifindex;
+	guint32 route_metric;
+	guint32 route_table_coerced;
+
+	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
+	s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
+
+	/* Differences to `wg-quick`.
+	 *
+	 * `wg-quick` supports the "Table" setting with 3 modes:
+	 *
+	 * a1) "off": this is what we do with "peer-routes" disabled.
+	 *
+	 * a2) an explicit routing table. This is our behavior with "peer-routes" on. In this case
+	 *   we honor the "ipv4.route-table" and "ipv6.route-table" settings. One difference is that
+	 *   `wg-quick` would resolve table names from /etc/iproute2/rt_tables. Our connection profiles
+	 *   only contain table numbers, so that conversion from name to table must have happened
+	 *   before already.
+	 *
+	 * a3) "auto" (the default). In this case, `wg-quick` would only add the route to the
+	 *   main table, if the AllowedIP range is not yet reachable on the link. With "peer-routes"
+	 *   enabled, we don't check for that and always add the routes to the main-table
+	 *   (with 'ipv4.route-table' and 'ipv6.route-table' set to zero or RT_TABLE_MAIN (254)).
+	 *
+	 *   Also, in "auto" mode, `wg-quick` would add special handling for /0 routes and pick
+	 *   an empty table to configure policy routing to avoid routing loops. This handling
+	 *   of routing-loops via policy routing is not yet done, and requires a separate solution
+	 *   from constructing the peer-routes here.
+	 */
+	if (!nm_setting_wireguard_get_peer_routes (s_wg))
+		return NULL;
+
+	ip_ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self));
+
+	if (ip_ifindex <= 0)
+		return NULL;
+
+	route_metric = nm_device_get_route_metric (NM_DEVICE (self), addr_family);
+
+	route_table_coerced = nm_platform_route_table_coerce (nm_device_get_route_table (NM_DEVICE (self), addr_family, TRUE));
+
+	n_peers = nm_setting_wireguard_get_peers_len (s_wg);
+	for (i = 0; i < n_peers; i++) {
+		NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i);
+		guint n_aips;
+		guint j;
+
+		n_aips = nm_wireguard_peer_get_allowed_ips_len (peer);
+		for (j = 0; j < n_aips; j++) {
+			NMPlatformIPXRoute rt;
+			NMIPAddr addrbin;
+			const char *aip;
+			gboolean valid;
+			int prefix;
+
+			aip = nm_wireguard_peer_get_allowed_ip (peer, j, &valid);
+
+			if (   !valid
+			    || !nm_utils_parse_inaddr_prefix_bin (addr_family,
+			                                          aip,
+			                                          NULL,
+			                                          &addrbin,
+			                                          &prefix))
+				continue;
+
+			if (prefix < 0)
+				prefix = (addr_family == AF_INET) ? 32 : 128;
+
+			if (!ip_config)
+				ip_config = nm_device_ip_config_new (NM_DEVICE (self), addr_family);
+
+			nm_utils_ipx_address_clear_host_address (addr_family, &addrbin, NULL, prefix);
+
+			if (addr_family == AF_INET) {
+				rt.r4 = (NMPlatformIP4Route) {
+					.network       = addrbin.addr4,
+					.plen          = prefix,
+					.ifindex       = ip_ifindex,
+					.rt_source     = NM_IP_CONFIG_SOURCE_USER,
+					.table_coerced = route_table_coerced,
+					.metric        = route_metric,
+				};
+			} else {
+				rt.r6 = (NMPlatformIP6Route) {
+					.network       = addrbin.addr6,
+					.plen          = prefix,
+					.ifindex       = ip_ifindex,
+					.rt_source     = NM_IP_CONFIG_SOURCE_USER,
+					.table_coerced = route_table_coerced,
+					.metric        = route_metric,
+				};
+			}
+
+			nm_ip_config_add_route (ip_config, &rt.rx, NULL);
+		}
+	}
+
+	return g_steal_pointer (&ip_config);
+}
+
+static NMActStageReturn
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
+{
+	gs_unref_object NMIPConfig *ip_config = NULL;
+
+	ip_config = _get_dev2_ip_config (NM_DEVICE_WIREGUARD (device), addr_family);
+
+	nm_device_set_dev2_ip_config (device, addr_family, ip_config);
+
+	return NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
+}
+
+static guint32
+get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+{
+	/* When "MTU" for `wg-quick up` is unset, it calls `ip route get` for
+	 * each configured endpoint, to determine the suitable MTU how to reach
+	 * each endpoint.
+	 * For `wg-quick` this works very well, because whenever the script runs it
+	 * determines the best setting at that point in time. It's simply not concerned
+	 * with what happens later (and it's not around anyway).
+	 *
+	 * NetworkManager sticks around, so the right MTU would need to be re-determined
+	 * whenever anything relevant changes. Which basically means, to re-evaluate whenever
+	 * something related to addresses or routing changes (which happens all the time).
+	 *
+	 * The correct MTU indeed depends on the MTU setting of other interfaces (or routes).
+	 * But it's still odd, that activating/deactivating a seemingly unrelated interface
+	 * would trigger an MTU change. It's odd to explain/document and odd to implemented
+	 * -- despite this being the reality.
+	 *
+	 * For now, only support configuring an explicit MTU, or leave the setting untouched.
+	 * The same limitation also applies to other "ip-tunnel" types, where we could use
+	 * similar smarts for autodetecting the MTU.
+	 */
+	return nm_device_get_configured_mtu_from_connection (device,
+	                                                     NM_TYPE_SETTING_WIREGUARD,
+	                                                     out_source);
+}
+
+static void
+device_state_changed (NMDevice *device,
+                      NMDeviceState new_state,
+                      NMDeviceState old_state,
+                      NMDeviceStateReason reason)
+{
+	NMDeviceWireGuardPrivate *priv;
+
+	if (new_state <= NM_DEVICE_STATE_ACTIVATED)
+		return;
+
+	priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (device);
+
+	_peers_remove_all (priv);
+	_secrets_cancel (NM_DEVICE_WIREGUARD (device));
+}
+
+/*****************************************************************************/
+
+static gboolean
+can_reapply_change (NMDevice *device,
+                    const char *setting_name,
+                    NMSetting *s_old,
+                    NMSetting *s_new,
+                    GHashTable *diffs,
+                    GError **error)
+{
+	if (nm_streq (setting_name, NM_SETTING_WIREGUARD_SETTING_NAME)) {
+		/* Most, but not all WireGuard settings can be reapplied. Whitelist.
+		 *
+		 * MTU cannot be reapplied. */
+		return nm_device_hash_check_invalid_keys (diffs,
+		                                          NM_SETTING_WIREGUARD_SETTING_NAME,
+		                                          error,
+		                                          NM_SETTING_WIREGUARD_FWMARK,
+		                                          NM_SETTING_WIREGUARD_LISTEN_PORT,
+		                                          NM_SETTING_WIREGUARD_PEERS,
+		                                          NM_SETTING_WIREGUARD_PEER_ROUTES,
+		                                          NM_SETTING_WIREGUARD_PRIVATE_KEY,
+		                                          NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS);
+	}
+
+	return NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->can_reapply_change (device,
+	                                                                               setting_name,
+	                                                                               s_old,
+	                                                                               s_new,
+	                                                                               diffs,
+	                                                                               error);
+}
+
+static void
+reapply_connection (NMDevice *device,
+                    NMConnection *con_old,
+                    NMConnection *con_new)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (device);
+	gs_unref_object NMIPConfig *ip4_config = NULL;
+	gs_unref_object NMIPConfig *ip6_config = NULL;
+
+	ip4_config = _get_dev2_ip_config (self, AF_INET);
+	ip6_config = _get_dev2_ip_config (self, AF_INET6);
+
+	nm_device_set_dev2_ip_config (device, AF_INET, ip4_config);
+	nm_device_set_dev2_ip_config (device, AF_INET6, ip6_config);
+
+	NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->reapply_connection (device,
+	                                                                        con_old,
+	                                                                        con_new);
+
+	link_config (NM_DEVICE_WIREGUARD (device),
+	             "reapply",
+	             LINK_CONFIG_MODE_REAPPLY,
+	             NULL);
+}
+
+/*****************************************************************************/
+
+static void
+update_connection (NMDevice *device, NMConnection *connection)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (device);
+	NMSettingWireGuard *s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
+	const NMPObject *obj_wg;
+	const NMPObjectLnkWireGuard *olnk_wg;
+	guint i;
+
+	if (!s_wg) {
+		s_wg = NM_SETTING_WIREGUARD (nm_setting_wireguard_new ());
+		nm_connection_add_setting (connection, NM_SETTING (s_wg));
+	}
+
+	g_object_set (s_wg,
+	              NM_SETTING_WIREGUARD_FWMARK,
+	              (guint) priv->lnk_curr.fwmark,
+	              NM_SETTING_WIREGUARD_LISTEN_PORT,
+	              (guint) priv->lnk_curr.listen_port,
+	              NULL);
+
+	obj_wg = NMP_OBJECT_UP_CAST (nm_platform_link_get_lnk_wireguard (nm_device_get_platform (device),
+	                                                                 nm_device_get_ip_ifindex (device),
+	                                                                 NULL));
+	if (!obj_wg)
+		return;
+
+	olnk_wg = &obj_wg->_lnk_wireguard;
+
+	for (i = 0; i < olnk_wg->peers_len; i++) {
+		nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
+		const NMPWireGuardPeer *ppeer = &olnk_wg->peers[i];
+
+		peer = nm_wireguard_peer_new ();
+
+		_nm_wireguard_peer_set_public_key_bin (peer, ppeer->public_key);
+
+		nm_setting_wireguard_append_peer (s_wg, peer);
+	}
+}
+
+/*****************************************************************************/
 
 static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
 	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
 
 	switch (prop_id) {
 	case PROP_PUBLIC_KEY:
-		g_value_take_variant (value, get_public_key_as_variant (self));
+		g_value_take_variant (value,
+		                      g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                                 priv->lnk_curr.public_key,
+		                                                 sizeof (priv->lnk_curr.public_key),
+		                                                 1));
 		break;
 	case PROP_LISTEN_PORT:
-		g_value_set_uint (value, self->props.listen_port);
+		g_value_set_uint (value, priv->lnk_curr.listen_port);
 		break;
 	case PROP_FWMARK:
-		g_value_set_uint (value, self->props.fwmark);
+		g_value_set_uint (value, priv->lnk_curr.fwmark);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -132,9 +1552,44 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
+/*****************************************************************************/
+
 static void
 nm_device_wireguard_init (NMDeviceWireGuard *self)
 {
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	c_list_init (&priv->lst_peers_head);
+	priv->peers = g_hash_table_new (_peer_data_hash, _peer_data_equal);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	_secrets_cancel (self);
+
+	_peers_remove_all (priv);
+
+	G_OBJECT_CLASS (nm_device_wireguard_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	nm_explicit_bzero (priv->lnk_curr.private_key, sizeof (priv->lnk_curr.private_key));
+
+	if (priv->dns_manager) {
+		g_signal_handlers_disconnect_by_func (priv->dns_manager, _dns_config_changed, self);
+		g_object_unref (priv->dns_manager);
+	}
+
+	G_OBJECT_CLASS (nm_device_wireguard_parent_class)->finalize (object);
 }
 
 static const NMDBusInterfaceInfoExtended interface_info_device_wireguard = {
@@ -156,13 +1611,26 @@ nm_device_wireguard_class_init (NMDeviceWireGuardClass *klass)
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->get_property = get_property;
+	object_class->dispose = dispose;
+	object_class->finalize = finalize;
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_wireguard);
 
-	device_class->connection_type_supported = NULL;
+	device_class->connection_type_supported = NM_SETTING_WIREGUARD_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_WIREGUARD_SETTING_NAME;
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD);
 
+	device_class->state_changed = device_state_changed;
+	device_class->create_and_realize = create_and_realize;
+	device_class->act_stage2_config = act_stage2_config;
+	device_class->act_stage2_config_also_for_external_or_assume = TRUE;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
+	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->link_changed = link_changed;
+	device_class->update_connection = update_connection;
+	device_class->can_reapply_change = can_reapply_change;
+	device_class->reapply_connection = reapply_connection;
+	device_class->get_configured_mtu = get_configured_mtu;
 
 	obj_properties[PROP_PUBLIC_KEY] =
 	    g_param_spec_variant (NM_DEVICE_WIREGUARD_PUBLIC_KEY,
@@ -207,6 +1675,7 @@ create_device (NMDeviceFactory *factory,
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL (WIREGUARD, WireGuard, wireguard,
-	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD),
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD)
+	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_WIREGUARD_SETTING_NAME),
 	factory_class->create_device = create_device;
 )
diff --git a/src/devices/nm-device-wpan.c b/src/devices/nm-device-wpan.c
index 05b507d5..cdfd1f70 100644
--- a/src/devices/nm-device-wpan.c
+++ b/src/devices/nm-device-wpan.c
@@ -23,7 +23,6 @@
 #include "nm-device-wpan.h"
 
 #include <stdlib.h>
-#include <string.h>
 #include <sys/types.h>
 #include <linux/if.h>
 
@@ -119,11 +118,11 @@ static NMActStageReturn
 act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceWpan *self = NM_DEVICE_WPAN (device);
-	NMConnection *connection;
 	NMSettingWpan *s_wpan;
 	NMPlatform *platform;
 	guint16 pan_id;
 	guint16 short_address;
+	gint16 page, channel;
 	int ifindex;
 	const guint8 *hwaddr;
 	gsize hwaddr_len = 0;
@@ -139,12 +138,11 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	g_return_val_if_fail (platform, NM_ACT_STAGE_RETURN_FAILURE);
 
 	ifindex = nm_device_get_ifindex (device);
+
 	g_return_val_if_fail (ifindex > 0, NM_ACT_STAGE_RETURN_FAILURE);
 
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+	s_wpan = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WPAN);
 
-	s_wpan = NM_SETTING_WPAN (nm_connection_get_setting (connection, NM_TYPE_SETTING_WPAN));
 	g_return_val_if_fail (s_wpan, NM_ACT_STAGE_RETURN_FAILURE);
 
 	hwaddr = nm_platform_link_get_address (platform, ifindex, &hwaddr_len);
@@ -183,9 +181,18 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		}
 	}
 
+	channel = nm_setting_wpan_get_channel (s_wpan);
+	if (channel != NM_SETTING_WPAN_CHANNEL_DEFAULT) {
+		page = nm_setting_wpan_get_page (s_wpan);
+		if (!nm_platform_wpan_set_channel (platform, ifindex, page, channel)) {
+			_LOGW (LOGD_DEVICE, "unable to set the channel");
+			goto out;
+		}
+	}
+
 	ret = NM_ACT_STAGE_RETURN_SUCCESS;
 out:
-        nm_device_bring_up (device, TRUE, NULL);
+	nm_device_bring_up (device, TRUE, NULL);
 
 	if (lowpan_device)
 		nm_device_bring_up (lowpan_device, TRUE, NULL);
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 89e1cc51..7514fa78 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -24,9 +24,7 @@
 #include "nm-device.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 #include <sys/ioctl.h>
 #include <signal.h>
 #include <sys/types.h>
@@ -67,6 +65,7 @@
 #include "settings/nm-settings.h"
 #include "nm-setting-ethtool.h"
 #include "nm-auth-utils.h"
+#include "nm-keep-alive.h"
 #include "nm-netns.h"
 #include "nm-dispatcher.h"
 #include "nm-config.h"
@@ -114,14 +113,6 @@ typedef enum {
 	CLEANUP_TYPE_DECONFIGURE,
 } CleanupType;
 
-typedef enum {
-	IP_NONE = 0,
-	IP_WAIT,
-	IP_CONF,
-	IP_DONE,
-	IP_FAIL
-} IpState;
-
 typedef struct {
 	CList lst_slave;
 	NMDevice *slave;
@@ -174,6 +165,7 @@ struct _NMDeviceConnectivityHandle {
 	bool is_periodic:1;
 	bool is_periodic_bump:1;
 	bool is_periodic_bump_on_complete:1;
+	int addr_family;
 };
 
 typedef struct {
@@ -195,7 +187,6 @@ enum {
 	REMOVED,
 	RECHECK_AUTO_ACTIVATE,
 	RECHECK_ASSUME,
-	CONNECTIVITY_CHANGED,
 	LAST_SIGNAL,
 };
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -241,7 +232,8 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDevice,
 	PROP_REFRESH_RATE_MS,
 	PROP_TX_BYTES,
 	PROP_RX_BYTES,
-	PROP_CONNECTIVITY,
+	PROP_IP4_CONNECTIVITY,
+	PROP_IP6_CONNECTIVITY,
 );
 
 typedef struct _NMDevicePrivate {
@@ -392,8 +384,11 @@ typedef struct _NMDevicePrivate {
 
 	bool            ipv6ll_handle:1; /* TRUE if NM handles the device's IPv6LL address */
 	bool            ipv6ll_has:1;
+	bool            ndisc_started:1;
 	bool            device_link_changed_down:1;
 
+	bool            concheck_rp_filter_checked:1;
+
 	/* Generic DHCP stuff */
 	char *          dhcp_anycast_address;
 
@@ -413,11 +408,8 @@ typedef struct _NMDevicePrivate {
 		NMIPConfig *ip_config_x[2];
 	};
 
-	union {
-		const IpState   ip4_state;
-		IpState         ip4_state_;
-	};
-	AppliedConfig   dev_ip4_config; /* Config from DHCP, PPP, LLv4, etc */
+	/* Config from DHCP, PPP, LLv4, etc */
+	AppliedConfig  dev_ip_config_4;
 
 	/* config from the setting */
 	union {
@@ -446,18 +438,16 @@ typedef struct _NMDevicePrivate {
 		GSList *vpn_configs_x[2];
 	};
 
-	/* WWAN configuration */
+	/* Extra device configuration, injected by the subclass of NMDevice.
+	 * This is used for example by NMDeviceModem for WWAN configuration. */
 	union {
 		struct {
-			AppliedConfig  wwan_ip_config_6;
-			AppliedConfig  wwan_ip_config_4;
+			AppliedConfig dev2_ip_config_6;
+			AppliedConfig dev2_ip_config_4;
 		};
-		AppliedConfig wwan_ip_config_x[2];
+		AppliedConfig dev2_ip_config_x[2];
 	};
 
-	bool v4_has_shadowed_routes;
-	const char *ip4_rp_filter;
-
 	/* DHCPv4 tracking */
 	struct {
 		NMDhcpClient *  client;
@@ -500,9 +490,16 @@ typedef struct _NMDevicePrivate {
 	} acd;
 
 	union {
-		const IpState   ip6_state;
-		IpState         ip6_state_;
+		struct {
+			const NMDeviceIPState   ip_state_6;
+			const NMDeviceIPState   ip_state_4;
+		};
+		union {
+			const NMDeviceIPState   ip_state_x[2];
+			NMDeviceIPState         ip_state_x_[2];
+		};
 	};
+
 	AppliedConfig  ac_ip6_config;  /* config from IPv6 autoconfiguration */
 	NMIP6Config *  ext_ip6_config_captured; /* Configuration captured from platform. */
 	NMIP6Config *  dad6_ip6_config;
@@ -555,24 +552,24 @@ typedef struct _NMDevicePrivate {
 	NMLldpListener *lldp_listener;
 
 	NMConnectivity *concheck_mgr;
+	CList concheck_lst_head;
+	struct {
+		/* if periodic checks are enabled, this is the source id for the next check. */
+		guint p_cur_id;
 
-	/* if periodic checks are enabled, this is the source id for the next check. */
-	guint concheck_p_cur_id;
-
-	/* the currently configured max periodic interval. */
-	guint concheck_p_max_interval;
-
-	/* the current interval. If we are probing, the interval might be lower
-	 * then the configured max interval. */
-	guint concheck_p_cur_interval;
+		/* the currently configured max periodic interval. */
+		guint p_max_interval;
 
-	/* the timestamp, when we last scheduled the timer concheck_p_cur_id with current interval
-	 * concheck_p_cur_interval. */
-	gint64 concheck_p_cur_basetime_ns;
+		/* the current interval. If we are probing, the interval might be lower
+		 * then the configured max interval. */
+		guint p_cur_interval;
 
-	NMConnectivityState connectivity_state;
+		/* the timestamp, when we last scheduled the timer p_cur_id with current interval
+		 * p_cur_interval. */
+		gint64 p_cur_basetime_ns;
 
-	CList concheck_lst_head;
+		NMConnectivityState state;
+	} concheck_x[2];
 
 	guint check_delete_unrealized_id;
 
@@ -643,7 +640,26 @@ static void _set_mtu (NMDevice *self, guint32 mtu);
 static void _commit_mtu (NMDevice *self, const NMIP4Config *config);
 static void _cancel_activation (NMDevice *self);
 
-static void concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean is_periodic);
+static void concheck_update_state (NMDevice *self,
+                                   int addr_family,
+                                   NMConnectivityState state,
+                                   gboolean is_periodic);
+
+static void activate_stage4_ip_config_timeout_4 (NMDevice *self);
+static void activate_stage4_ip_config_timeout_6 (NMDevice *self);
+
+static void (*const activate_stage4_ip_config_timeout_x[2]) (NMDevice *self) = {
+	activate_stage4_ip_config_timeout_6,
+	activate_stage4_ip_config_timeout_4,
+};
+
+static void activate_stage5_ip_config_result_4 (NMDevice *self);
+static void activate_stage5_ip_config_result_6 (NMDevice *self);
+
+static void (*const activate_stage5_ip_config_result_x[2]) (NMDevice *self) = {
+	activate_stage5_ip_config_result_6,
+	activate_stage5_ip_config_result_4,
+};
 
 /*****************************************************************************/
 
@@ -739,10 +755,10 @@ NM_UTILS_LOOKUP_STR_DEFINE (nm_device_state_reason_to_str, NMDeviceStateReason,
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE,           "ip-address-duplicate"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED,          "ip-method-unsupported"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED,     "sriov-configuration-failed"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_PEER_NOT_FOUND,                 "peer-not-found"),
 );
 
-#define reason_to_string(reason) \
-	NM_UTILS_LOOKUP_STR (nm_device_state_reason_to_str, reason)
+#define reason_to_string_a(reason) NM_UTILS_LOOKUP_STR_A (nm_device_state_reason_to_str, reason)
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (mtu_source_to_str, NMDeviceMtuSource,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
@@ -868,28 +884,28 @@ concheck_get_mgr (NMDevice *self)
 	return priv->concheck_mgr;
 }
 
-static NMIP4Config *
-_ip4_config_new (NMDevice *self)
+NMIP4Config *
+nm_device_ip4_config_new (NMDevice *self)
 {
 	return nm_ip4_config_new (nm_device_get_multi_index (self),
 	                          nm_device_get_ip_ifindex (self));
 }
 
-static NMIP6Config *
-_ip6_config_new (NMDevice *self)
+NMIP6Config *
+nm_device_ip6_config_new (NMDevice *self)
 {
 	return nm_ip6_config_new (nm_device_get_multi_index (self),
 	                          nm_device_get_ip_ifindex (self));
 }
 
-static NMIPConfig *
-_ip_config_new (NMDevice *self, int addr_family)
+NMIPConfig *
+nm_device_ip_config_new (NMDevice *self, int addr_family)
 {
 	nm_assert_addr_family (addr_family);
 
 	return addr_family == AF_INET
-	       ? (gpointer) _ip4_config_new (self)
-	       : (gpointer) _ip6_config_new (self);
+	       ? (gpointer) nm_device_ip4_config_new (self)
+	       : (gpointer) nm_device_ip6_config_new (self);
 }
 
 static void
@@ -902,6 +918,12 @@ applied_config_clear (AppliedConfig *config)
 static void
 applied_config_init (AppliedConfig *config, gpointer ip_config)
 {
+	nm_assert (   !ip_config
+	           || (!config->orig && !config->current)
+	           || nm_ip_config_get_addr_family (ip_config) == nm_ip_config_get_addr_family (config->orig ?: config->current));
+	nm_assert (   !ip_config
+	           || NM_IS_IP_CONFIG (ip_config, AF_UNSPEC));
+
 	nm_g_object_ref (ip_config);
 	applied_config_clear (config);
 	config->orig = ip_config;
@@ -910,7 +932,7 @@ applied_config_init (AppliedConfig *config, gpointer ip_config)
 static void
 applied_config_init_new (AppliedConfig *config, NMDevice *self, int addr_family)
 {
-	gs_unref_object NMIPConfig *c = _ip_config_new (self, addr_family);
+	gs_unref_object NMIPConfig *c = nm_device_ip_config_new (self, addr_family);
 
 	applied_config_init (config, c);
 }
@@ -1116,8 +1138,8 @@ init_ip_config_dns_priority (NMDevice *self, NMIPConfig *config)
 	int priority;
 
 	property = (nm_ip_config_get_addr_family (config) == AF_INET)
-	             ? "ipv4.dns-priority"
-	             : "ipv6.dns-priority";
+	             ? NM_CON_DEFAULT ("ipv4.dns-priority")
+	             : NM_CON_DEFAULT ("ipv6.dns-priority");
 
 	priority = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
 	                                                        property,
@@ -1130,96 +1152,85 @@ init_ip_config_dns_priority (NMDevice *self, NMIPConfig *config)
 
 /*****************************************************************************/
 
-static gboolean
-nm_device_ipv4_sysctl_set (NMDevice *self, const char *property, const char *value)
+static char *
+nm_device_sysctl_ip_conf_get (NMDevice *self,
+                              int addr_family,
+                              const char *property)
 {
-	NMPlatform *platform = nm_device_get_platform (self);
-	gs_free char *value_to_free = NULL;
-	const char *value_to_set;
-	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-
-	if (!nm_device_get_ip_ifindex (self))
-		return FALSE;
+	const char *ifname;
 
-	if (value) {
-		value_to_set = value;
-	} else {
-		/* Set to a default value when we've got a NULL @value. */
-		value_to_free = nm_platform_sysctl_get (platform,
-		                                        NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, buf, "default", property)));
-		value_to_set = value_to_free;
-	}
+	nm_assert_addr_family (addr_family);
 
-	return nm_platform_sysctl_set (platform,
-	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, buf, nm_device_get_ip_iface (self), property)),
-	                               value_to_set);
+	ifname = nm_device_get_ip_iface_from_platform (self);
+	if (!ifname)
+		return NULL;
+	return nm_platform_sysctl_ip_conf_get (nm_device_get_platform (self), addr_family, ifname, property);
 }
 
-static guint32
-nm_device_ipv4_sysctl_get_effective_uint32 (NMDevice *self, const char *property, guint32 fallback)
+static gint64
+nm_device_sysctl_ip_conf_get_int_checked (NMDevice *self,
+                                          int addr_family,
+                                          const char *property,
+                                          guint base,
+                                          gint64 min,
+                                          gint64 max,
+                                          gint64 fallback)
 {
-	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-	gint64 v, v_all;
+	const char *ifname;
 
-	if (!nm_device_get_ip_ifindex (self))
-		return fallback;
+	nm_assert_addr_family (addr_family);
 
-	/* for this kind of sysctl (e.g. "rp_filter"), kernel effectively uses the
-	 * MAX of the per-device value and the "all" value.
-	 *
-	 * Also do that, by reading both sysctls and return the maximum. */
-
-	v = nm_platform_sysctl_get_int_checked (nm_device_get_platform (self),
-	                                        NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET,
-	                                                                                                  buf,
-	                                                                                                  nm_device_get_ip_iface (self),
-	                                                                                                  property)),
-	                                        10,
-	                                        0,
-	                                        G_MAXUINT32,
-	                                        -1);
-
-	v_all = nm_platform_sysctl_get_int_checked (nm_device_get_platform (self),
-	                                            NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET,
-	                                                                                                      buf,
-	                                                                                                      "all",
-	                                                                                                      property)),
-	                                            10,
-	                                            0,
-	                                            G_MAXUINT32,
-	                                            -1);
-
-	v = NM_MAX (v, v_all);
-	return v > -1 ? (guint32) v : fallback;
+	ifname = nm_device_get_ip_iface_from_platform (self);
+	if (!ifname) {
+		errno = EINVAL;
+		return fallback;
+	}
+	return nm_platform_sysctl_ip_conf_get_int_checked (nm_device_get_platform (self),
+	                                                   addr_family,
+	                                                   ifname,
+	                                                   property,
+	                                                   base,
+	                                                   min,
+	                                                   max,
+	                                                   fallback);
 }
 
 gboolean
-nm_device_ipv6_sysctl_set (NMDevice *self, const char *property, const char *value)
+nm_device_sysctl_ip_conf_set (NMDevice *self,
+                              int addr_family,
+                              const char *property,
+                              const char *value)
 {
-	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-
-	if (!nm_device_get_ip_ifindex (self))
-		return FALSE;
+	NMPlatform *platform = nm_device_get_platform (self);
+	gs_free char *value_to_free = NULL;
+	const char *ifname;
 
-	return nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, nm_device_get_ip_iface (self), property)), value);
-}
+	nm_assert_addr_family (addr_family);
 
-static guint32
-nm_device_ipv6_sysctl_get_uint32 (NMDevice *self, const char *property, guint32 fallback)
-{
-	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+	ifname = nm_device_get_ip_iface_from_platform (self);
+	if (!ifname)
+		return FALSE;
 
-	if (!nm_device_get_ip_ifindex (self))
-		return fallback;
+	if (!value) {
+		/* Set to a default value when we've got a NULL @value. */
+		value_to_free = nm_platform_sysctl_ip_conf_get (platform,
+		                                                addr_family,
+		                                                "default",
+		                                                property);
+		value = value_to_free;
+		if (!value)
+			return FALSE;
+	}
 
-	return nm_platform_sysctl_get_int_checked (nm_device_get_platform (self),
-	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, nm_device_get_ip_iface (self), property)),
-	                                           10,
-	                                           0,
-	                                           G_MAXUINT32,
-	                                           fallback);
+	return nm_platform_sysctl_ip_conf_set (platform,
+	                                       addr_family,
+	                                       ifname,
+	                                       property,
+	                                       value);
 }
 
+/*****************************************************************************/
+
 gboolean
 nm_device_has_capability (NMDevice *self, NMDeviceCapabilities caps)
 {
@@ -1275,7 +1286,7 @@ _get_stable_id (NMDevice *self,
 
 		if (!stable_id) {
 			default_id = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-			                                                    "connection.stable-id",
+			                                                    NM_CON_DEFAULT ("connection.stable-id"),
 			                                                    self);
 			stable_id = default_id;
 		}
@@ -1283,7 +1294,7 @@ _get_stable_id (NMDevice *self,
 		uuid = nm_connection_get_uuid (connection);
 
 		/* the cloned-mac-address may be generated based on the stable-id.
-		 * Thus, at this point, we can only use the permanant MAC address
+		 * Thus, at this point, we can only use the permanent MAC address
 		 * as seed. */
 		hwaddr = nm_device_get_permanent_hw_address_full (self, TRUE, &hwaddr_is_fake);
 
@@ -1324,44 +1335,42 @@ _get_stable_id (NMDevice *self,
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_ip_state_to_string, IpState,
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_ip_state_to_string, NMDeviceIPState,
 	NM_UTILS_LOOKUP_DEFAULT_WARN ("unknown"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_NONE, "none"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_WAIT, "wait"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_CONF, "conf"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_DONE, "done"),
-	NM_UTILS_LOOKUP_STR_ITEM (IP_FAIL, "fail"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_NONE, "none"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_WAIT, "wait"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_CONF, "conf"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_DONE, "done"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_FAIL, "fail"),
 );
 
 static void
-_set_ip_state (NMDevice *self, int addr_family, IpState new_state)
+_set_ip_state (NMDevice *self, int addr_family, NMDeviceIPState new_state)
 {
-	IpState *p;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	nm_assert_addr_family (addr_family);
 
-	p =   (addr_family == AF_INET)
-	    ? &priv->ip4_state_
-	    : &priv->ip6_state_;
+	if (priv->ip_state_x[IS_IPv4] == new_state)
+		return;
 
-	if (*p != new_state) {
-		_LOGT (LOGD_DEVICE, "ip%c-state: set to %d (%s)",
-		       nm_utils_addr_family_to_char (addr_family),
-		       (int) new_state,
-		       _ip_state_to_string (new_state));
-		*p = new_state;
+	_LOGT (LOGD_DEVICE, "ip%c-state: set to %d (%s)",
+	       nm_utils_addr_family_to_char (addr_family),
+	       (int) new_state,
+	       _ip_state_to_string (new_state));
 
-		if (new_state == IP_DONE) {
-			/* we only set the IPx_READY flag once we reach IP_DONE state. We don't
-			 * ever clear it, even if we later enter IP_FAIL state.
-			 *
-			 * This is not documented/guaranteed behavior, but seems to make sense for now. */
-			_active_connection_set_state_flags (self,
-			                                    addr_family == AF_INET
-			                                      ? NM_ACTIVATION_STATE_FLAG_IP4_READY
-			                                      : NM_ACTIVATION_STATE_FLAG_IP6_READY);
-		}
+	priv->ip_state_x_[IS_IPv4] = new_state;
+
+	if (new_state == NM_DEVICE_IP_STATE_DONE) {
+		/* we only set the IPx_READY flag once we reach NM_DEVICE_IP_STATE_DONE state. We don't
+		 * ever clear it, even if we later enter NM_DEVICE_IP_STATE_FAIL state.
+		 *
+		 * This is not documented/guaranteed behavior, but seems to make sense for now. */
+		_active_connection_set_state_flags (self,
+		                                    addr_family == AF_INET
+		                                      ? NM_ACTIVATION_STATE_FLAG_IP4_READY
+		                                      : NM_ACTIVATION_STATE_FLAG_IP6_READY);
 	}
 }
 
@@ -1473,6 +1482,18 @@ nm_device_get_ip_iface (NMDevice *self)
 	return priv->ip_iface ?: priv->iface;
 }
 
+const char *
+nm_device_get_ip_iface_from_platform (NMDevice *self)
+{
+	int ifindex;
+
+	ifindex = nm_device_get_ip_ifindex (self);
+	if (ifindex <= 0)
+		return NULL;
+
+	return nm_platform_link_get_name (nm_device_get_platform (self), ifindex);
+}
+
 int
 nm_device_get_ip_ifindex (const NMDevice *self)
 {
@@ -1957,9 +1978,10 @@ nm_device_get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *iid, gboo
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
 	if (!ignore_token) {
-		s_ip6 = (NMSettingIP6Config *)
-		    nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
+		s_ip6 = nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
+
 		g_return_val_if_fail (s_ip6, FALSE);
+
 		token = nm_setting_ip6_config_get_token (s_ip6);
 	}
 	if (token)
@@ -2037,9 +2059,14 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 	 */
 
 	switch (device_type) {
-	/* 50 is also used for VPN plugins (NM_VPN_ROUTE_METRIC_DEFAULT) */
+
+	/* 50 is also used for VPN plugins (NM_VPN_ROUTE_METRIC_DEFAULT).
+	 *
+	 * Note that returning 50 from this function means that this device-type is
+	 * in some aspects a VPN. */
 	case NM_DEVICE_TYPE_WIREGUARD:
-		return 50;
+		return NM_VPN_ROUTE_METRIC_DEFAULT;
+
 	case NM_DEVICE_TYPE_ETHERNET:
 	case NM_DEVICE_TYPE_VETH:
 		return 100;
@@ -2087,6 +2114,7 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 		return 800;
 	case NM_DEVICE_TYPE_WPAN:
 		return 850;
+	case NM_DEVICE_TYPE_WIFI_P2P:
 	case NM_DEVICE_TYPE_GENERIC:
 		return 950;
 	case NM_DEVICE_TYPE_UNKNOWN:
@@ -2100,13 +2128,14 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 }
 
 static gboolean
-default_route_metric_penalty_detect (NMDevice *self)
+default_route_metric_penalty_detect (NMDevice *self, int addr_family)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	/* currently we don't differentiate between IPv4 and IPv6 when detecting
 	 * connectivity. */
-	if (   priv->connectivity_state != NM_CONNECTIVITY_FULL
+	if (   priv->concheck_x[IS_IPv4].state != NM_CONNECTIVITY_FULL
 	    && nm_connectivity_check_enabled (concheck_get_mgr (self)))
 		return TRUE;
 
@@ -2141,9 +2170,7 @@ nm_device_get_route_metric (NMDevice *self,
 
 	connection = nm_device_get_applied_connection (self);
 	if (connection) {
-		s_ip = addr_family == AF_INET
-		       ? nm_connection_get_setting_ip4_config (connection)
-		       : nm_connection_get_setting_ip6_config (connection);
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 
 		/* Slave interfaces don't have IP settings, but we may get here when
 		 * external changes are made or when noticing IP changes when starting
@@ -2159,7 +2186,9 @@ nm_device_get_route_metric (NMDevice *self,
 	/* use the current NMConfigData, which makes this configuration reloadable.
 	 * Note that that means that the route-metric might change between SIGHUP.
 	 * You must cache the returned value if that is a problem. */
-	property = addr_family == AF_INET ? "ipv4.route-metric" : "ipv6.route-metric";
+	property = addr_family == AF_INET
+	    ? NM_CON_DEFAULT ("ipv4.route-metric")
+	    : NM_CON_DEFAULT ("ipv6.route-metric");
 	route_metric = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
 	                                                            property,
 	                                                            self,
@@ -2189,7 +2218,7 @@ _get_mdns (NMDevice *self)
 		return mdns;
 
 	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                    "connection.mdns",
+	                                                    NM_CON_DEFAULT ("connection.mdns"),
 	                                                    self,
 	                                                    NM_SETTING_CONNECTION_MDNS_NO,
 	                                                    NM_SETTING_CONNECTION_MDNS_YES,
@@ -2211,7 +2240,7 @@ _get_llmnr (NMDevice *self)
 		return llmnr;
 
 	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                    "connection.llmnr",
+	                                                    NM_CON_DEFAULT ("connection.llmnr"),
 	                                                    self,
 	                                                    NM_SETTING_CONNECTION_LLMNR_NO,
 	                                                    NM_SETTING_CONNECTION_LLMNR_YES,
@@ -2246,11 +2275,7 @@ nm_device_get_route_table (NMDevice *self,
 
 	connection = nm_device_get_applied_connection (self);
 	if (connection) {
-		if (addr_family == AF_INET)
-			s_ip = nm_connection_get_setting_ip4_config (connection);
-		else
-			s_ip = nm_connection_get_setting_ip6_config (connection);
-
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 		if (s_ip)
 			route_table = nm_setting_ip_config_get_route_table (s_ip);
 
@@ -2260,7 +2285,9 @@ nm_device_get_route_table (NMDevice *self,
 		if (route_table == 0) {
 			const char *property;
 
-			property = addr_family == AF_INET ? "ipv4.route-table" : "ipv6.route-table";
+			property = addr_family == AF_INET
+			    ? NM_CON_DEFAULT ("ipv4.route-table")
+			    : NM_CON_DEFAULT ("ipv6.route-table");
 			route_table = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
 			                                                           property,
 			                                                           self,
@@ -2373,10 +2400,27 @@ nm_device_get_act_request (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->act_request.obj;
 }
 
+NMActivationStateFlags
+nm_device_get_activation_state_flags (NMDevice *self)
+{
+	NMActRequest *ac;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_ACTIVATION_STATE_FLAG_NONE);
+
+	ac = NM_DEVICE_GET_PRIVATE (self)->act_request.obj;
+	if (!ac)
+		return NM_ACTIVATION_STATE_FLAG_NONE;
+	return nm_active_connection_get_state_flags (NM_ACTIVE_CONNECTION (ac));
+}
+
 NMSettingsConnection *
 nm_device_get_settings_connection (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	return priv->act_request.obj ? nm_act_request_get_settings_connection (priv->act_request.obj) : NULL;
 }
@@ -2420,7 +2464,7 @@ nm_device_has_unmodified_applied_connection (NMDevice *self, NMSettingCompareFla
 	return nm_active_connection_has_unmodified_applied_connection ((NMActiveConnection *) priv->act_request.obj, compare_flags);
 }
 
-NMSetting *
+gpointer
 nm_device_get_applied_setting (NMDevice *self, GType setting_type)
 {
 	NMConnection *connection;
@@ -2456,24 +2500,37 @@ typedef enum {
 } ConcheckScheduleMode;
 
 static NMDeviceConnectivityHandle *concheck_start (NMDevice *self,
+                                                   int addr_family,
                                                    NMDeviceConnectivityCallback callback,
                                                    gpointer user_data,
                                                    gboolean is_periodic);
 
 static void concheck_periodic_schedule_set (NMDevice *self,
+                                            int addr_family,
                                             ConcheckScheduleMode mode);
 
 static gboolean
-concheck_periodic_timeout_cb (gpointer user_data)
+_concheck_periodic_timeout_cb (NMDevice *self, int addr_family)
 {
-	NMDevice *self = user_data;
-
-	_LOGt (LOGD_CONCHECK, "connectivity: periodic timeout");
-	concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_CHECK_PERIODIC);
+	_LOGt (LOGD_CONCHECK, "connectivity: [IPv%c] periodic timeout",
+	       nm_utils_addr_family_to_char (addr_family));
+	concheck_periodic_schedule_set (self, addr_family, CONCHECK_SCHEDULE_CHECK_PERIODIC);
 	return G_SOURCE_REMOVE;
 }
 
 static gboolean
+concheck_ip4_periodic_timeout_cb (gpointer user_data)
+{
+	return _concheck_periodic_timeout_cb (user_data, AF_INET);
+}
+
+static gboolean
+concheck_ip6_periodic_timeout_cb (gpointer user_data)
+{
+	return _concheck_periodic_timeout_cb (user_data, AF_INET6);
+}
+
+static gboolean
 concheck_is_possible (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -2491,17 +2548,18 @@ concheck_is_possible (NMDevice *self)
 }
 
 static gboolean
-concheck_periodic_schedule_do (NMDevice *self, gint64 now_ns)
+concheck_periodic_schedule_do (NMDevice *self, int addr_family, gint64 now_ns)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean periodic_check_disabled = FALSE;
 	gint64 expiry, tdiff;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	/* we always cancel whatever was pending. */
-	if (nm_clear_g_source (&priv->concheck_p_cur_id))
+	if (nm_clear_g_source (&priv->concheck_x[IS_IPv4].p_cur_id))
 		periodic_check_disabled = TRUE;
 
-	if (priv->concheck_p_max_interval == 0) {
+	if (priv->concheck_x[IS_IPv4].p_max_interval == 0) {
 		/* periodic checks are disabled */
 		goto out;
 	}
@@ -2510,46 +2568,50 @@ concheck_periodic_schedule_do (NMDevice *self, gint64 now_ns)
 		goto out;
 
 	nm_assert (now_ns > 0);
-	nm_assert (priv->concheck_p_cur_interval > 0);
+	nm_assert (priv->concheck_x[IS_IPv4].p_cur_interval > 0);
 
 	/* we schedule the timeout based on our current settings cur-interval and cur-basetime.
 	 * Before calling concheck_periodic_schedule_do(), make sure that these properties are
 	 * correct. */
 
-	expiry = priv->concheck_p_cur_basetime_ns + (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+	expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
 	tdiff = expiry - now_ns;
 
-	_LOGT (LOGD_CONCHECK, "connectivity: periodic-check: %sscheduled in %lld milliseconds (%u seconds interval)",
+	_LOGT (LOGD_CONCHECK, "connectivity: [IPv%c] periodic-check: %sscheduled in %lld milliseconds (%u seconds interval)",
+	       nm_utils_addr_family_to_char (addr_family),
 	       periodic_check_disabled ? "re-" : "",
 	       (long long) (tdiff / NM_UTILS_NS_PER_MSEC),
-	       priv->concheck_p_cur_interval);
+	       priv->concheck_x[IS_IPv4].p_cur_interval);
 
-	priv->concheck_p_cur_id = g_timeout_add (NM_MAX ((gint64) 0, tdiff) / NM_UTILS_NS_PER_MSEC,
-	                                         concheck_periodic_timeout_cb,
-	                                         self);
+	priv->concheck_x[IS_IPv4].p_cur_id =
+		g_timeout_add (NM_MAX ((gint64) 0, tdiff) / NM_UTILS_NS_PER_MSEC,
+	                       IS_IPv4 ? concheck_ip4_periodic_timeout_cb : concheck_ip6_periodic_timeout_cb,
+	                       self);
 	return TRUE;
 out:
-	if (periodic_check_disabled)
-		_LOGT (LOGD_CONCHECK, "connectivity: periodic-check: unscheduled");
+	if (periodic_check_disabled) {
+		_LOGT (LOGD_CONCHECK, "connectivity: [IPv%c] periodic-check: unscheduled",
+		       nm_utils_addr_family_to_char (addr_family));
+	}
 	return FALSE;
 }
 
 #define CONCHECK_P_PROBE_INTERVAL 1
 
 static void
-concheck_periodic_schedule_set (NMDevice *self,
-                                ConcheckScheduleMode mode)
+concheck_periodic_schedule_set (NMDevice *self, int addr_family, ConcheckScheduleMode mode)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gint64 new_expiry, exp_expiry, cur_expiry, tdiff;
 	gint64 now_ns = 0;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-	if (priv->concheck_p_max_interval == 0) {
+	if (priv->concheck_x[IS_IPv4].p_max_interval == 0) {
 		/* periodic check is disabled. Nothing to do. */
 		return;
 	}
 
-	if (!priv->concheck_p_cur_id) {
+	if (!priv->concheck_x[IS_IPv4].p_cur_id) {
 		/* we currently don't have a timeout scheduled. No need to reschedule
 		 * another one... */
 		if (NM_IN_SET (mode, CONCHECK_SCHEDULE_UPDATE_INTERVAL,
@@ -2563,19 +2625,19 @@ concheck_periodic_schedule_set (NMDevice *self,
 
 	switch (mode) {
 	case CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART:
-		priv->concheck_p_cur_interval = NM_MIN (priv->concheck_p_max_interval, CONCHECK_P_PROBE_INTERVAL);
-		priv->concheck_p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
-		if (concheck_periodic_schedule_do (self, now_ns))
-			concheck_start (self, NULL, NULL, TRUE);
+		priv->concheck_x[IS_IPv4].p_cur_interval = NM_MIN (priv->concheck_x[IS_IPv4].p_max_interval, CONCHECK_P_PROBE_INTERVAL);
+		priv->concheck_x[IS_IPv4].p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+		if (concheck_periodic_schedule_do (self, addr_family, now_ns))
+			concheck_start (self, addr_family, NULL, NULL, TRUE);
 		return;
 
 	case CONCHECK_SCHEDULE_UPDATE_INTERVAL:
-		/* called with "UPDATE_INTERVAL" and already have a concheck_p_cur_id scheduled. */
+		/* called with "UPDATE_INTERVAL" and already have a p_cur_id scheduled. */
 
-		nm_assert (priv->concheck_p_max_interval > 0);
-		nm_assert (priv->concheck_p_cur_interval > 0);
+		nm_assert (priv->concheck_x[IS_IPv4].p_max_interval > 0);
+		nm_assert (priv->concheck_x[IS_IPv4].p_cur_interval > 0);
 
-		if (priv->concheck_p_cur_interval <= priv->concheck_p_max_interval) {
+		if (priv->concheck_x[IS_IPv4].p_cur_interval <= priv->concheck_x[IS_IPv4].p_max_interval) {
 			/* we currently have a shorter interval set, than what we now have. Either,
 			 * because we are probing, or because the previous max interval was shorter.
 			 *
@@ -2584,17 +2646,17 @@ concheck_periodic_schedule_set (NMDevice *self,
 			return;
 		}
 
-		cur_expiry = priv->concheck_p_cur_basetime_ns + (priv->concheck_p_max_interval * NM_UTILS_NS_PER_SECOND);
+		cur_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (priv->concheck_x[IS_IPv4].p_max_interval * NM_UTILS_NS_PER_SECOND);
 		nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
 
-		priv->concheck_p_cur_interval = priv->concheck_p_max_interval;
+		priv->concheck_x[IS_IPv4].p_cur_interval = priv->concheck_x[IS_IPv4].p_max_interval;
 		if (cur_expiry <= now_ns) {
 			/* Since the last time we scheduled a periodic check, already more than the
 			 * new max_interval passed. We need to start a check right away (and
 			 * schedule a timeout in cur-interval in the future). */
-			priv->concheck_p_cur_basetime_ns = now_ns;
-			if (concheck_periodic_schedule_do (self, now_ns))
-				concheck_start (self, NULL, NULL, TRUE);
+			priv->concheck_x[IS_IPv4].p_cur_basetime_ns = now_ns;
+			if (concheck_periodic_schedule_do (self, addr_family, now_ns))
+				concheck_start (self, addr_family, NULL, NULL, TRUE);
 		} else {
 			/* we are reducing the max-interval to a shorter interval that we have currently
 			 * scheduled (with cur_interval).
@@ -2602,24 +2664,26 @@ concheck_periodic_schedule_set (NMDevice *self,
 			 * However, since the last time we scheduled the check, not even the new max-interval
 			 * expired. All we need to do, is reschedule the timer to expire sooner. The cur_basetime
 			 * is unchanged. */
-			concheck_periodic_schedule_do (self, now_ns);
+			concheck_periodic_schedule_do (self, addr_family, now_ns);
 		}
 		return;
 
 	case CONCHECK_SCHEDULE_CHECK_EXTERNAL:
 		/* a external connectivity check delays our periodic check. We reset the counter. */
-		priv->concheck_p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
-		concheck_periodic_schedule_do (self, now_ns);
+		priv->concheck_x[IS_IPv4].p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+		concheck_periodic_schedule_do (self, addr_family, now_ns);
 		return;
 
 	case CONCHECK_SCHEDULE_CHECK_PERIODIC:
 	{
 		gboolean any_periodic_pending;
 		NMDeviceConnectivityHandle *handle;
-		guint old_interval = priv->concheck_p_cur_interval;
+		guint old_interval = priv->concheck_x[IS_IPv4].p_cur_interval;
 
 		any_periodic_pending = FALSE;
 		c_list_for_each_entry (handle, &priv->concheck_lst_head, concheck_lst) {
+			if (handle->addr_family != addr_family)
+				continue;
 			if (handle->is_periodic_bump) {
 				handle->is_periodic_bump = FALSE;
 				handle->is_periodic_bump_on_complete = FALSE;
@@ -2630,7 +2694,7 @@ concheck_periodic_schedule_set (NMDevice *self,
 			/* we reached a timeout to schedule a new periodic request, however we still
 			 * have period requests pending that didn't complete yet. We need to bump the
 			 * interval already. */
-			priv->concheck_p_cur_interval = NM_MIN (old_interval * 2, priv->concheck_p_max_interval);
+			priv->concheck_x[IS_IPv4].p_cur_interval = NM_MIN (old_interval * 2, priv->concheck_x[IS_IPv4].p_max_interval);
 		}
 
 		/* we just reached a timeout. The expected expiry (exp_expiry) should be
@@ -2638,13 +2702,13 @@ concheck_periodic_schedule_set (NMDevice *self,
 		 *
 		 * We want to reschedule the timeout at exp_expiry (aka now) + cur_interval. */
 		nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
-		exp_expiry = priv->concheck_p_cur_basetime_ns + (old_interval * NM_UTILS_NS_PER_SECOND);
-		new_expiry = exp_expiry + (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+		exp_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (old_interval * NM_UTILS_NS_PER_SECOND);
+		new_expiry = exp_expiry + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
 		tdiff = NM_MAX (new_expiry - now_ns, 0);
-		priv->concheck_p_cur_basetime_ns = (now_ns + tdiff) - (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
-		if (concheck_periodic_schedule_do (self, now_ns)) {
-			handle = concheck_start (self, NULL, NULL, TRUE);
-			if (old_interval != priv->concheck_p_cur_interval) {
+		priv->concheck_x[IS_IPv4].p_cur_basetime_ns = (now_ns + tdiff) - (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
+		if (concheck_periodic_schedule_do (self, addr_family, now_ns)) {
+			handle = concheck_start (self, addr_family, NULL, NULL, TRUE);
+			if (old_interval != priv->concheck_x[IS_IPv4].p_cur_interval) {
 				/* we just bumped the interval already when scheduling this check.
 				 * When the handle returns, don't bump a second time.
 				 *
@@ -2659,54 +2723,57 @@ concheck_periodic_schedule_set (NMDevice *self,
 	/* we just got an event that we lost connectivity (that is, concheck returned). We reset
 	 * the interval to min/max or increase the probe interval (bump). */
 	case CONCHECK_SCHEDULE_RETURNED_MIN:
-		priv->concheck_p_cur_interval = NM_MIN (priv->concheck_p_max_interval, CONCHECK_P_PROBE_INTERVAL);
+		priv->concheck_x[IS_IPv4].p_cur_interval = NM_MIN (priv->concheck_x[IS_IPv4].p_max_interval, CONCHECK_P_PROBE_INTERVAL);
 		break;
 	case CONCHECK_SCHEDULE_RETURNED_MAX:
-		priv->concheck_p_cur_interval = priv->concheck_p_max_interval;
+		priv->concheck_x[IS_IPv4].p_cur_interval = priv->concheck_x[IS_IPv4].p_max_interval;
 		break;
 	case CONCHECK_SCHEDULE_RETURNED_BUMP:
-		priv->concheck_p_cur_interval = NM_MIN (priv->concheck_p_cur_interval * 2, priv->concheck_p_max_interval);
+		priv->concheck_x[IS_IPv4].p_cur_interval = NM_MIN (priv->concheck_x[IS_IPv4].p_cur_interval * 2, priv->concheck_x[IS_IPv4].p_max_interval);
 		break;
 	}
 
 	/* we are here, because we returned from a connectivity check and adjust the current interval.
 	 *
 	 * But note that we calculate the new timeout based on the time when we scheduled the
-	 * last check, instead of counting from now. The reaons is, that we want that the times
+	 * last check, instead of counting from now. The reason is that we want that the times
 	 * when we schedule checks be at precise intervals, without including the time it took for
 	 * the connectivity check. */
-	new_expiry = priv->concheck_p_cur_basetime_ns + (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
+	new_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
 	tdiff = NM_MAX (new_expiry - nm_utils_get_monotonic_timestamp_ns_cached (&now_ns), 0);
-	priv->concheck_p_cur_basetime_ns = now_ns + tdiff - (priv->concheck_p_cur_interval * NM_UTILS_NS_PER_SECOND);
-	concheck_periodic_schedule_do (self, now_ns);
+	priv->concheck_x[IS_IPv4].p_cur_basetime_ns = now_ns + tdiff - (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
+	concheck_periodic_schedule_do (self, addr_family, now_ns);
 }
 
 static void
-concheck_update_interval (NMDevice *self, gboolean check_now)
+concheck_update_interval (NMDevice *self, int addr_family, gboolean check_now)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	guint new_interval;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	new_interval = nm_connectivity_get_interval (concheck_get_mgr (self));
 
 	new_interval = NM_MIN (new_interval, 7 *24 * 3600);
 
-	if (new_interval != priv->concheck_p_max_interval) {
-		_LOGT (LOGD_CONCHECK, "connectivity: periodic-check: set interval to %u seconds", new_interval);
-		priv->concheck_p_max_interval = new_interval;
+	if (new_interval != priv->concheck_x[IS_IPv4].p_max_interval) {
+		_LOGT (LOGD_CONCHECK, "connectivity: [IPv%c] periodic-check: set interval to %u seconds",
+		       nm_utils_addr_family_to_char (addr_family), new_interval);
+		priv->concheck_x[IS_IPv4].p_max_interval = new_interval;
 	}
 
 	if (!new_interval) {
 		/* this will cancel any potentially pending timeout because max-interval is zero.
 		 * But it logs a nice message... */
-		concheck_periodic_schedule_do (self, 0);
+		concheck_periodic_schedule_do (self, addr_family, 0);
 
 		/* also update the fake connectivity state. */
-		concheck_update_state (self, NM_CONNECTIVITY_FAKE, TRUE);
+		concheck_update_state (self, addr_family, NM_CONNECTIVITY_FAKE, TRUE);
 		return;
 	}
 
 	concheck_periodic_schedule_set (self,
+	                                addr_family,
 	                                check_now
 	                                  ? CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART
 	                                  : CONCHECK_SCHEDULE_UPDATE_INTERVAL);
@@ -2715,13 +2782,16 @@ concheck_update_interval (NMDevice *self, gboolean check_now)
 void
 nm_device_check_connectivity_update_interval (NMDevice *self)
 {
-	concheck_update_interval (self, FALSE);
+	concheck_update_interval (self, AF_INET, TRUE);
+	concheck_update_interval (self, AF_INET6, TRUE);
 }
 
 static void
-concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean allow_periodic_bump)
+concheck_update_state (NMDevice *self, int addr_family,
+                       NMConnectivityState state, gboolean allow_periodic_bump)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	/* @state is a result of the connectivity check. We only expect a precise
 	 * number of possible values. */
@@ -2734,7 +2804,7 @@ concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean allow
 	if (state == NM_CONNECTIVITY_ERROR) {
 		/* on error, we don't change the current connectivity state,
 		 * except making UNKNOWN to NONE. */
-		state = priv->connectivity_state;
+		state = priv->concheck_x[IS_IPv4].state;
 		if (state == NM_CONNECTIVITY_UNKNOWN)
 			state = NM_CONNECTIVITY_NONE;
 	} else if (state == NM_CONNECTIVITY_FAKE) {
@@ -2753,11 +2823,11 @@ concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean allow
 			state = NM_CONNECTIVITY_NONE;
 	}
 
-	if (priv->connectivity_state == state) {
+	if (priv->concheck_x[IS_IPv4].state == state) {
 		/* we got a connectivty update, but the state didn't change. If we were probing,
 		 * we bump the probe frequency. */
 		if (allow_periodic_bump)
-			concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_BUMP);
+			concheck_periodic_schedule_set (self, addr_family, CONCHECK_SCHEDULE_RETURNED_BUMP);
 		return;
 	}
 	/* we need to update the probe interval before emitting signals. Emitting
@@ -2766,23 +2836,22 @@ concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean allow
 	if (state == NM_CONNECTIVITY_FULL) {
 		/* we reached full connectivity state. Stop probing by setting the
 		 * interval to the max. */
-		concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_MAX);
-	} else if (priv->connectivity_state == NM_CONNECTIVITY_FULL) {
+		concheck_periodic_schedule_set (self, addr_family, CONCHECK_SCHEDULE_RETURNED_MAX);
+	} else if (priv->concheck_x[IS_IPv4].state == NM_CONNECTIVITY_FULL) {
 		/* we are about to loose connectivity. (re)start probing by setting
 		 * the timeout interval to the min. */
-		concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_MIN);
+		concheck_periodic_schedule_set (self, addr_family, CONCHECK_SCHEDULE_RETURNED_MIN);
 	} else {
 		if (allow_periodic_bump)
-			concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_RETURNED_BUMP);
+			concheck_periodic_schedule_set (self, addr_family, CONCHECK_SCHEDULE_RETURNED_BUMP);
 	}
 
 	_LOGD (LOGD_CONCHECK, "connectivity state changed from %s to %s",
-	       nm_connectivity_state_to_string (priv->connectivity_state),
+	       nm_connectivity_state_to_string (priv->concheck_x[IS_IPv4].state),
 	       nm_connectivity_state_to_string (state));
-	priv->connectivity_state = state;
+	priv->concheck_x[IS_IPv4].state = state;
 
-	_notify (self, PROP_CONNECTIVITY);
-	g_signal_emit (self, signals[CONNECTIVITY_CHANGED], 0);
+	_notify (self, IS_IPv4 ? PROP_IP4_CONNECTIVITY : PROP_IP6_CONNECTIVITY);
 
 	if (   priv->state == NM_DEVICE_STATE_ACTIVATED
 	    && !nm_device_sys_iface_state_is_external (self)) {
@@ -2795,10 +2864,39 @@ concheck_update_state (NMDevice *self, NMConnectivityState state, gboolean allow
 	}
 }
 
+static const char *
+nm_device_get_effective_ip_config_method (NMDevice *self,
+                                          int addr_family)
+{
+	NMDeviceClass *klass;
+	NMConnection *connection = nm_device_get_applied_connection (self);
+	const char *method;
+
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), "" /* bogus */);
+	nm_assert_addr_family (addr_family);
+
+	method = nm_utils_get_ip_config_method (connection, addr_family);
+
+	if (   (addr_family == AF_INET  && nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
+	    || (addr_family == AF_INET6 && nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO))) {
+		klass = NM_DEVICE_GET_CLASS (self);
+		if (klass->get_auto_ip_config_method) {
+			const char *auto_method;
+
+			auto_method = klass->get_auto_ip_config_method (self, addr_family);
+			if (auto_method)
+				return auto_method;
+		}
+	}
+
+	return method;
+}
+
 static void
-concheck_handle_complete (NMDeviceConnectivityHandle *handle,
-                          GError *error)
+concheck_handle_complete (NMDeviceConnectivityHandle *handle, GError *error)
 {
+	const gboolean IS_IPv4 = (handle->addr_family == AF_INET);
+
 	/* The moment we invoke the callback, we unlink it. It signals
 	 * that @handle is handled -- as far as the callee of callback
 	 * is concerned. */
@@ -2810,7 +2908,7 @@ concheck_handle_complete (NMDeviceConnectivityHandle *handle,
 	if (handle->callback) {
 		handle->callback (handle->self,
 		                  handle,
-		                  NM_DEVICE_GET_PRIVATE (handle->self)->connectivity_state,
+		                  NM_DEVICE_GET_PRIVATE (handle->self)->concheck_x[IS_IPv4].state,
 		                  error,
 		                  handle->user_data);
 	}
@@ -2846,7 +2944,8 @@ concheck_cb (NMConnectivity *connectivity,
 		/* the only place where we nm_connectivity_check_cancel(@c_handle), is
 		 * from inside concheck_handle_complete(). This is a recursive call,
 		 * nothing to do. */
-		_LOGT (LOGD_CONCHECK, "connectivity: complete check (seq:%llu, cancelled)",
+		_LOGT (LOGD_CONCHECK, "connectivity: [IPv%c] complete check (seq:%llu, cancelled)",
+		       nm_utils_addr_family_to_char (handle->addr_family),
 		       (long long unsigned) handle->seq);
 		return;
 	}
@@ -2856,7 +2955,8 @@ concheck_cb (NMConnectivity *connectivity,
 
 	self_keep_alive = g_object_ref (self);
 
-	_LOGT (LOGD_CONCHECK, "connectivity: complete check (seq:%llu, state:%s)",
+	_LOGT (LOGD_CONCHECK, "connectivity: [Ipv%c] complete check (seq:%llu, state:%s)",
+	       nm_utils_addr_family_to_char (handle->addr_family),
 	       (long long unsigned) handle->seq,
 	       nm_connectivity_state_to_string (state));
 
@@ -2872,6 +2972,8 @@ concheck_cb (NMConnectivity *connectivity,
 	any_periodic_before = FALSE;
 	any_periodic_after = FALSE;
 	c_list_for_each_entry (other_handle, &priv->concheck_lst_head, concheck_lst) {
+		if (other_handle->addr_family != handle->addr_family)
+			continue;
 		if (other_handle->is_periodic_bump_on_complete) {
 			if (other_handle->seq < seq)
 				any_periodic_before = TRUE;
@@ -2898,7 +3000,7 @@ concheck_cb (NMConnectivity *connectivity,
 	}
 
 	/* first update the new state, and emit signals. */
-	concheck_update_state (self, state, allow_periodic_bump);
+	concheck_update_state (self, handle->addr_family, state, allow_periodic_bump);
 
 	handle_is_alive = FALSE;
 
@@ -2910,6 +3012,8 @@ concheck_cb (NMConnectivity *connectivity,
 	 * @handle, as they are automatically obsoleted. */
 check_handles:
 	c_list_for_each_entry (other_handle, &priv->concheck_lst_head, concheck_lst) {
+		if (other_handle->addr_family != handle->addr_family)
+			continue;
 		if (other_handle->seq >= seq) {
 			/* it's not guaranteed that @handle is still in the list. It might already
 			 * be canceled while invoking callbacks for a previous other_handle.
@@ -2947,6 +3051,7 @@ check_handles:
 
 static NMDeviceConnectivityHandle *
 concheck_start (NMDevice *self,
+                int addr_family,
                 NMDeviceConnectivityCallback callback,
                 gpointer user_data,
                 gboolean is_periodic)
@@ -2954,6 +3059,7 @@ concheck_start (NMDevice *self,
 	static guint64 seq_counter = 0;
 	NMDevicePrivate *priv;
 	NMDeviceConnectivityHandle *handle;
+	const char *ifname;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
@@ -2967,14 +3073,51 @@ concheck_start (NMDevice *self,
 	handle->is_periodic = is_periodic;
 	handle->is_periodic_bump = is_periodic;
 	handle->is_periodic_bump_on_complete = is_periodic;
+	handle->addr_family = addr_family;
 
 	c_list_link_tail (&priv->concheck_lst_head, &handle->concheck_lst);
 
-	_LOGT (LOGD_CONCHECK, "connectivity: start check (seq:%llu%s)",
+	_LOGT (LOGD_CONCHECK, "connectivity: [IPv%c] start check (seq:%llu%s)",
+	       nm_utils_addr_family_to_char (addr_family),
 	       (long long unsigned) handle->seq,
 	       is_periodic ? ", periodic-check" : "");
 
+	if (   addr_family == AF_INET
+	    && !priv->concheck_rp_filter_checked) {
+
+		if ((ifname = nm_device_get_ip_iface_from_platform (self))) {
+			int val, val_all;
+
+			val = nm_platform_sysctl_ip_conf_get_int_checked (nm_device_get_platform (self),
+			                                                  AF_INET,
+			                                                  ifname,
+			                                                  "rp_filter",
+			                                                  10, 0, 2, 3);
+			if (val < 2) {
+				val_all = nm_platform_sysctl_ip_conf_get_int_checked (nm_device_get_platform (self),
+				                                                      AF_INET,
+				                                                      "all",
+				                                                      "rp_filter",
+				                                                      10, 0, 2, val);
+				if (val_all > val) {
+					val = val_all;
+					ifname = "all";
+				}
+			}
+
+			if (val == 1) {
+				_LOGW (LOGD_CONCHECK, "connectivity: \"/proc/sys/net/ipv4/conf/%s/rp_filter\" is set to \"1\". "
+				       "This might break connectivity checking for IPv4 on this device", ifname);
+			}
+		}
+
+		/* we only check once per device. It's a warning after all.  */
+		priv->concheck_rp_filter_checked = TRUE;
+	}
+
 	handle->c_handle = nm_connectivity_check_start (concheck_get_mgr (self),
+	                                                handle->addr_family,
+	                                                nm_device_get_ip_ifindex (self),
 	                                                nm_device_get_ip_iface (self),
 	                                                concheck_cb,
 	                                                handle);
@@ -2983,6 +3126,7 @@ concheck_start (NMDevice *self,
 
 NMDeviceConnectivityHandle *
 nm_device_check_connectivity (NMDevice *self,
+                              int addr_family,
                               NMDeviceConnectivityCallback callback,
                               gpointer user_data)
 {
@@ -2991,8 +3135,8 @@ nm_device_check_connectivity (NMDevice *self,
 	if (!concheck_is_possible (self))
 		return NULL;
 
-	concheck_periodic_schedule_set (self, CONCHECK_SCHEDULE_CHECK_EXTERNAL);
-	handle = concheck_start (self, callback, user_data, FALSE);
+	concheck_periodic_schedule_set (self, addr_family, CONCHECK_SCHEDULE_CHECK_EXTERNAL);
+	handle = concheck_start (self, addr_family, callback, user_data, FALSE);
 	return handle;
 }
 
@@ -3014,11 +3158,26 @@ nm_device_check_connectivity_cancel (NMDeviceConnectivityHandle *handle)
 }
 
 NMConnectivityState
-nm_device_get_connectivity_state (NMDevice *self)
+nm_device_get_connectivity_state (NMDevice *self, int addr_family)
 {
+	NMDevicePrivate *priv;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
+
 	g_return_val_if_fail (NM_IS_DEVICE (self), NM_CONNECTIVITY_UNKNOWN);
+	nm_assert_addr_family (addr_family);
 
-	return NM_DEVICE_GET_PRIVATE (self)->connectivity_state;
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	switch (addr_family) {
+	case AF_INET:
+	case AF_INET6:
+		return priv->concheck_x[IS_IPv4].state;
+	default:
+		nm_assert (addr_family == AF_UNSPEC);
+		return NM_MAX_WITH_CMP (nm_connectivity_state_cmp,
+		                        priv->concheck_x[0].state,
+		                        priv->concheck_x[1].state);
+	}
 }
 
 /*****************************************************************************/
@@ -3053,6 +3212,7 @@ find_slave_info (NMDevice *self, NMDevice *slave)
 static gboolean
 nm_device_master_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *connection)
 {
+	NMDevicePrivate *priv;
 	SlaveInfo *info;
 	gboolean success = FALSE;
 	gboolean configure;
@@ -3061,6 +3221,7 @@ nm_device_master_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *c
 	g_return_val_if_fail (slave != NULL, FALSE);
 	g_return_val_if_fail (NM_DEVICE_GET_CLASS (self)->enslave_slave != NULL, FALSE);
 
+	priv = NM_DEVICE_GET_PRIVATE (self);
 	info = find_slave_info (self, slave);
 	if (!info)
 		return FALSE;
@@ -3083,15 +3244,20 @@ nm_device_master_enslave_slave (NMDevice *self, NMDevice *slave, NMConnection *c
 	 */
 	nm_device_update_hw_address (self);
 
+	/* Send ARP announcements if did not yet and have addresses. */
+	if (   priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
+	    && !priv->acd.announcing)
+		nm_device_arp_announce (self);
+
 	/* Restart IP configuration if we're waiting for slaves.  Do this
 	 * after updating the hardware address as IP config may need the
 	 * new address.
 	 */
 	if (success) {
-		if (NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_WAIT)
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_WAIT)
 			nm_device_activate_stage3_ip4_start (self);
 
-		if (NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_WAIT)
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_WAIT)
 			nm_device_activate_stage3_ip6_start (self);
 	}
 
@@ -3421,11 +3587,14 @@ nm_device_set_carrier (NMDevice *self, gboolean carrier)
 static void
 nm_device_set_carrier_from_platform (NMDevice *self)
 {
+	int ifindex;
+
 	if (nm_device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) {
-		if (!nm_device_has_capability (self, NM_DEVICE_CAP_NONSTANDARD_CARRIER)) {
+		if (   !nm_device_has_capability (self, NM_DEVICE_CAP_NONSTANDARD_CARRIER)
+		    && (ifindex = nm_device_get_ip_ifindex (self)) > 0) {
 			nm_device_set_carrier (self,
 			                       nm_platform_link_is_connected (nm_device_get_platform (self),
-			                                                      nm_device_get_ip_ifindex (self)));
+			                                                      ifindex));
 		}
 	} else {
 		NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -3599,6 +3768,8 @@ device_link_changed (NMDevice *self)
 	priv->device_link_changed_down = FALSE;
 
 	ifindex = nm_device_get_ifindex (self);
+	if (ifindex <= 0)
+		return G_SOURCE_REMOVE;
 	pllink = nm_platform_link_get (nm_device_get_platform (self), ifindex);
 	if (!pllink)
 		return G_SOURCE_REMOVE;
@@ -3709,11 +3880,13 @@ device_link_changed (NMDevice *self)
 	if (priv->up && (!was_up || seen_down)) {
 		/* the link was down and just came up. That happens for example, while changing MTU.
 		 * We must restore IP configuration. */
-		if (priv->ip4_state == IP_DONE) {
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 			if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 				_LOGW (LOGD_IP4, "failed applying IP4 config after link comes up again");
 		}
-		if (priv->ip6_state == IP_DONE) {
+
+		priv->linklocal6_dad_counter = 0;
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 			if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 				_LOGW (LOGD_IP6, "failed applying IP6 config after link comes up again");
 		}
@@ -3798,126 +3971,6 @@ link_changed_cb (NMPlatform *platform,
 
 /*****************************************************************************/
 
-typedef struct {
-	in_addr_t network;
-	guint8 plen;
-} IP4RPFilterData;
-
-static guint
-_v4_has_shadowed_routes_detect_hash (const IP4RPFilterData *d)
-{
-	NMHashState h;
-
-	nm_hash_init (&h, 1105201169u);
-	nm_hash_update_vals (&h,
-	                     d->network,
-	                     d->plen);
-	return nm_hash_complete (&h);
-}
-
-static gboolean
-_v4_has_shadowed_routes_detect_equal (const IP4RPFilterData *d1, const IP4RPFilterData *d2)
-{
-	return d1->network == d2->network && d1->plen == d2->plen;
-}
-
-static gboolean
-_v4_has_shadowed_routes_detect (NMDevice *self)
-{
-	NMPlatform *platform;
-	int ifindex;
-	NMPLookup lookup;
-	const NMDedupMultiHeadEntry *head_entry;
-	NMDedupMultiIter iter;
-	const NMPObject *o;
-	guint data_len;
-	gs_unref_hashtable GHashTable *data_hash = NULL;
-	gs_free IP4RPFilterData *data_arr = NULL;
-
-	ifindex = nm_device_get_ip_ifindex (self);
-	if (ifindex <= 0)
-		return FALSE;
-
-	platform = nm_device_get_platform (self);
-
-	head_entry = nm_platform_lookup (platform,
-	                                 nmp_lookup_init_object (&lookup,
-	                                                         NMP_OBJECT_TYPE_IP4_ROUTE,
-	                                                         ifindex));
-	if (!head_entry)
-		return FALSE;
-
-	/* first, create a lookup index @data_hash for all network/plen pairs. */
-	data_len = 0;
-	data_arr = g_new (IP4RPFilterData, head_entry->len);
-	data_hash = g_hash_table_new ((GHashFunc) _v4_has_shadowed_routes_detect_hash,
-	                              (GEqualFunc) _v4_has_shadowed_routes_detect_equal);
-
-	nmp_cache_iter_for_each (&iter, head_entry, &o) {
-		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (o);
-		IP4RPFilterData *d;
-
-		nm_assert (r->ifindex == ifindex);
-
-		if (   NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
-		    || r->table_coerced)
-			continue;
-
-		d = &data_arr[data_len++];
-		d->network = nm_utils_ip4_address_clear_host_address (r->network, r->plen);
-		d->plen = r->plen;
-		g_hash_table_add (data_hash, d);
-	}
-
-	/* then, search if there is any route on another interface with the same
-	 * network/plen destination. If yes, we consider this a multihoming
-	 * setup. */
-	head_entry = nm_platform_lookup (platform,
-	                                 nmp_lookup_init_obj_type (&lookup,
-	                                                           NMP_OBJECT_TYPE_IP4_ROUTE));
-	nmp_cache_iter_for_each (&iter, head_entry, &o) {
-		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (o);
-		IP4RPFilterData d;
-
-		if (   r->ifindex == ifindex
-		    || NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
-		    || r->table_coerced)
-			continue;
-
-		d.network = nm_utils_ip4_address_clear_host_address (r->network, r->plen);
-		d.plen = r->plen;
-		if (g_hash_table_contains (data_hash, &d))
-			return TRUE;
-	}
-
-	return FALSE;
-}
-
-static void
-ip4_rp_filter_update (NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *ip4_rp_filter;
-
-	if (   priv->v4_has_shadowed_routes
-	    || nm_device_get_best_default_route (self, AF_INET)) {
-		if (nm_device_ipv4_sysctl_get_effective_uint32 (self, "rp_filter", 0) != 1) {
-			/* Don't touch the rp_filter if it's not strict. */
-			return;
-		}
-		/* Loose rp_filter */
-		ip4_rp_filter = "2";
-	} else {
-		/* Default rp_filter */
-		ip4_rp_filter = NULL;
-	}
-
-	if (ip4_rp_filter != priv->ip4_rp_filter) {
-		nm_device_ipv4_sysctl_set (self, "rp_filter", ip4_rp_filter);
-		priv->ip4_rp_filter = ip4_rp_filter;
-	}
-}
-
 static void
 link_changed (NMDevice *self, const NMPlatformLink *pllink)
 {
@@ -4331,8 +4384,7 @@ realize_start_setup (NMDevice *self,
 	 * NetworkManager might down the interface or remove the 127.0.0.1 address. */
 	nm_device_set_unmanaged_flags (self,
 	                               NM_UNMANAGED_BY_TYPE,
-	                                  is_loopback (self)
-	                               || NM_IS_DEVICE_WIREGUARD (self));
+	                               is_loopback (self));
 
 	nm_device_set_unmanaged_by_user_udev (self);
 	nm_device_set_unmanaged_by_user_conf (self);
@@ -4804,7 +4856,8 @@ nm_device_master_release_slaves (NMDevice *self)
 	if (priv->state == NM_DEVICE_STATE_FAILED)
 		reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
 
-	if (!nm_platform_link_get (nm_device_get_platform (self), priv->ifindex))
+	if (   priv->ifindex <= 0
+	    || !nm_platform_link_get (nm_device_get_platform (self), priv->ifindex))
 		configure = FALSE;
 
 	c_list_for_each_safe (iter, safe, &priv->slaves) {
@@ -4855,21 +4908,11 @@ static gboolean
 get_ip_config_may_fail (NMDevice *self, int addr_family)
 {
 	NMConnection *connection;
-	NMSettingIPConfig *s_ip = NULL;
+	NMSettingIPConfig *s_ip;
 
 	connection = nm_device_get_applied_connection (self);
 
-	/* Fail the connection if the failed IP method is required to complete */
-	switch (addr_family) {
-	case AF_INET:
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-		break;
-	case AF_INET6:
-		s_ip = nm_connection_get_setting_ip6_config (connection);
-		break;
-	default:
-		nm_assert_not_reached ();
-	}
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 
 	return !s_ip || nm_setting_ip_config_get_may_fail (s_ip);
 }
@@ -4903,32 +4946,32 @@ check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 	    && !priv->is_enslaved)
 		return;
 
-	s_ip4 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP4_CONFIG);
+	s_ip4 = nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP4_CONFIG);
 	if (s_ip4 && nm_streq0 (nm_setting_ip_config_get_method (s_ip4),
 	                        NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
 		ip4_disabled = TRUE;
 
-	s_ip6 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
+	s_ip6 = nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
 	if (s_ip6 && nm_streq0 (nm_setting_ip_config_get_method (s_ip6),
 	                        NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
 		ip6_ignore = TRUE;
 
-	if (   priv->ip4_state == IP_DONE
-	    && priv->ip6_state == IP_DONE) {
+	if (   priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
+	    && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 		/* Both method completed (or disabled), proceed with activation */
 		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
 		return;
 	}
 
-	if (   (priv->ip4_state == IP_FAIL || (ip4_disabled && priv->ip4_state == IP_DONE))
-	    && (priv->ip6_state == IP_FAIL || (ip6_ignore && priv->ip6_state == IP_DONE))) {
+	if (   (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL || (ip4_disabled && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE))
+	    && (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL || (ip6_ignore && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE))) {
 		/* Either both methods failed, or only one failed and the other is
 		 * disabled */
 		if (nm_device_sys_iface_state_is_external_or_assume (self)) {
 			/* We have assumed configuration, but couldn't redo it. No problem,
 			 * move to check state. */
-			_set_ip_state (self, AF_INET, IP_DONE);
-			_set_ip_state (self, AF_INET6, IP_DONE);
+			_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_DONE);
+			_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
 			state = NM_DEVICE_STATE_IP_CHECK;
 		} else if (   may_fail
 		           && get_ip_config_may_fail (self, AF_INET)
@@ -4951,14 +4994,14 @@ check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 	}
 
 	/* If a method is still pending but required, wait */
-	if (priv->ip4_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET))
+	if (priv->ip_state_4 != NM_DEVICE_IP_STATE_DONE && !get_ip_config_may_fail (self, AF_INET))
 		return;
-	if (priv->ip6_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET6))
+	if (priv->ip_state_6 != NM_DEVICE_IP_STATE_DONE && !get_ip_config_may_fail (self, AF_INET6))
 		return;
 
 	/* If at least a method has completed, proceed with activation */
-	if (   (priv->ip4_state == IP_DONE && !ip4_disabled)
-	    || (priv->ip6_state == IP_DONE && !ip6_ignore)) {
+	if (   (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && !ip4_disabled)
+	    || (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE && !ip6_ignore)) {
 		if (full_state_update)
 			nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
 		return;
@@ -5267,7 +5310,7 @@ nm_device_autoconnect_allowed (NMDevice *self)
 	/* The 'autoconnect-allowed' signal is emitted on a device to allow
 	 * other listeners to block autoconnect on the device if they wish.
 	 * This is mainly used by the OLPC Mesh devices to block autoconnect
-	 * on their companion WiFi device as they share radio resources and
+	 * on their companion Wi-Fi device as they share radio resources and
 	 * cannot be connected at the same time.
 	 */
 
@@ -5486,10 +5529,13 @@ nm_device_generate_connection (NMDevice *self,
 		nm_connection_add_setting (connection, nm_setting_proxy_new ());
 
 		pllink = nm_platform_link_get (nm_device_get_platform (self), priv->ifindex);
-		if (pllink && pllink->inet6_token.id) {
+		if (   pllink
+		    && pllink->inet6_token.id) {
+			char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
 			g_object_set (s_ip6,
 			              NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, NM_IN6_ADDR_GEN_MODE_EUI64,
-			              NM_SETTING_IP6_CONFIG_TOKEN, nm_utils_inet6_interface_identifier_to_token (pllink->inet6_token, NULL),
+			              NM_SETTING_IP6_CONFIG_TOKEN, nm_utils_inet6_interface_identifier_to_token (pllink->inet6_token, sbuf),
 			              NULL);
 		}
 	}
@@ -5507,8 +5553,8 @@ nm_device_generate_connection (NMDevice *self,
 	/* Ignore the connection if it has no IP configuration,
 	 * no slave configuration, and is not a master interface.
 	 */
-	ip4_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	ip6_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	ip4_method = nm_utils_get_ip_config_method (connection, AF_INET);
+	ip6_method = nm_utils_get_ip_config_method (connection, AF_INET6);
 	if (   g_strcmp0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0
 	    && g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0
 	    && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con))
@@ -5646,7 +5692,6 @@ check_connection_compatible (NMDevice *self, NMConnection *connection, GError **
 	NMDeviceClass *klass;
 	const char *const *patterns;
 	NMSettingMatch *s_match;
-	guint num_patterns;
 
 	klass = NM_DEVICE_GET_CLASS (self);
 	if (klass->connection_type_check_compatible) {
@@ -5687,6 +5732,8 @@ check_connection_compatible (NMDevice *self, NMConnection *connection, GError **
 	s_match = (NMSettingMatch *) nm_connection_get_setting (connection,
 	                                                        NM_TYPE_SETTING_MATCH);
 	if (s_match) {
+		guint num_patterns = 0;
+
 		patterns = nm_setting_match_get_interface_names (s_match, &num_patterns);
 		if (!nm_wildcard_match_check (device_iface, patterns, num_patterns)) {
 			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
@@ -5788,7 +5835,7 @@ unmanaged_on_quit (NMDevice *self)
 	/* the only exception are IPv4 shared connections. We unmanage them on quit. */
 	connection = nm_device_get_applied_connection (self);
 	if (connection) {
-		if (NM_IN_STRSET (nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG),
+		if (NM_IN_STRSET (nm_utils_get_ip_config_method (connection, AF_INET),
 		                  NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
 			/* shared connections are to be unmangaed. */
 			return TRUE;
@@ -6112,7 +6159,7 @@ lldp_rx_enabled (NMDevice *self)
 	lldp = nm_setting_connection_get_lldp (s_con);
 	if (lldp == NM_SETTING_CONNECTION_LLDP_DEFAULT) {
 		lldp = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-		                                                    "connection.lldp",
+		                                                    NM_CON_DEFAULT ("connection.lldp"),
 		                                                    self,
 		                                                    NM_SETTING_CONNECTION_LLDP_DEFAULT,
 		                                                    NM_SETTING_CONNECTION_LLDP_ENABLE_RX,
@@ -6207,7 +6254,7 @@ act_stage1_prepare (NMDevice *self, NMDeviceStateReason *out_failure_reason)
 
 	if (   priv->ifindex > 0
 	    && nm_device_has_capability (self, NM_DEVICE_CAP_SRIOV)
-	    && (s_sriov = (NMSettingSriov *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))) {
+	    && (s_sriov = nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))) {
 		nm_auto_freev NMPlatformVF **plat_vfs = NULL;
 		gs_free_error GError *error = NULL;
 		NMSriovVF *vf;
@@ -6216,7 +6263,7 @@ act_stage1_prepare (NMDevice *self, NMDeviceStateReason *out_failure_reason)
 		autoprobe = nm_setting_sriov_get_autoprobe_drivers (s_sriov);
 		if (autoprobe == NM_TERNARY_DEFAULT) {
 			autoprobe = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-			                                                         "sriov.autoprobe-drivers",
+			                                                         NM_CON_DEFAULT ("sriov.autoprobe-drivers"),
 			                                                         self,
 			                                                         NM_TERNARY_FALSE,
 			                                                         NM_TERNARY_TRUE,
@@ -6271,8 +6318,8 @@ activate_stage1_device_prepare (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
 
-	_set_ip_state (self, AF_INET, IP_NONE);
-	_set_ip_state (self, AF_INET6, IP_NONE);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_NONE);
+	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_NONE);
 
 	/* Notify the new ActiveConnection along with the state change */
 	nm_dbus_track_obj_path_set (&priv->act_request,
@@ -6449,23 +6496,25 @@ static void
 activate_stage2_device_config (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceClass *klass;
 	NMActStageReturn ret;
 	gboolean no_firmware = FALSE;
 	CList *iter;
 
 	nm_device_state_changed (self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
-	/* Assumed connections were already set up outside NetworkManager */
-	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
-		NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-
+	if (!nm_device_sys_iface_state_is_external_or_assume (self))
 		_ethtool_state_set (self);
 
+	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
 		if (!tc_commit (self)) {
 			_LOGW (LOGD_IP6, "failed applying traffic control rules");
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+			return;
 		}
+	}
 
+	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
 		if (!nm_device_bring_up (self, FALSE, &no_firmware)) {
 			if (no_firmware)
 				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_FIRMWARE_MISSING);
@@ -6473,15 +6522,21 @@ activate_stage2_device_config (NMDevice *self)
 				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
 			return;
 		}
+	}
+
+	klass = NM_DEVICE_GET_CLASS (self);
+	if (   klass->act_stage2_config_also_for_external_or_assume
+	    || !nm_device_sys_iface_state_is_external_or_assume (self)) {
+		NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-		ret = NM_DEVICE_GET_CLASS (self)->act_stage2_config (self, &failure_reason);
+		ret = klass->act_stage2_config (self, &failure_reason);
 		if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
 			return;
-		else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+		if (ret != NM_ACT_STAGE_RETURN_SUCCESS) {
+			nm_assert (ret == NM_ACT_STAGE_RETURN_FAILURE);
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
 			return;
 		}
-		g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 	}
 
 	/* If we have slaves that aren't yet enslaved, do that now */
@@ -6498,6 +6553,7 @@ activate_stage2_device_config (NMDevice *self)
 	}
 
 	lldp_init (self, TRUE);
+
 	nm_device_activate_schedule_stage3_ip_config_start (self);
 }
 
@@ -6559,7 +6615,7 @@ nm_device_ip_method_failed (NMDevice *self,
 	g_return_if_fail (NM_IS_DEVICE (self));
 	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	_set_ip_state (self, addr_family, IP_FAIL);
+	_set_ip_state (self, addr_family, NM_DEVICE_IP_STATE_FAIL);
 
 	if (get_ip_config_may_fail (self, addr_family))
 		check_ip_state (self, FALSE, (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG));
@@ -6586,7 +6642,7 @@ get_ipv4_dad_timeout (NMDevice *self)
 		return timeout;
 
 	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                    "ipv4.dad-timeout",
+	                                                    NM_CON_DEFAULT ("ipv4.dad-timeout"),
 	                                                    self,
 	                                                    0,
 	                                                    NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX,
@@ -6594,17 +6650,15 @@ get_ipv4_dad_timeout (NMDevice *self)
 }
 
 static void
-acd_data_destroy (gpointer ptr, GClosure *closure)
+acd_data_destroy (gpointer ptr)
 {
 	AcdData *data = ptr;
 	int i;
 
-	if (data) {
-		for (i = 0; data->configs && data->configs[i]; i++)
-			g_object_unref (data->configs[i]);
-		g_free (data->configs);
-		g_slice_free (AcdData, data);
-	}
+	for (i = 0; data->configs && data->configs[i]; i++)
+		g_object_unref (data->configs[i]);
+	g_free (data->configs);
+	g_slice_free (AcdData, data);
 }
 
 static void
@@ -6612,11 +6666,10 @@ ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean succes
 {
 	NMConnection *connection;
 	const char *method;
-	NMIP4Config *empty;
 
 	connection = nm_device_get_applied_connection (self);
 	nm_assert (connection);
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	method = nm_utils_get_ip_config_method (connection, AF_INET);
 	nm_assert (NM_IN_STRSET (method,
 	                         NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
 	                         NM_SETTING_IP4_CONFIG_METHOD_AUTO));
@@ -6627,19 +6680,18 @@ ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean succes
 		return;
 	}
 
-	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
-		empty = _ip4_config_new (self);
-		nm_device_activate_schedule_ip4_config_result (self, empty);
-		g_object_unref (empty);
-	} else {
-		if (NM_DEVICE_GET_PRIVATE (self)->ip4_state != IP_DONE)
+	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
+		nm_device_activate_schedule_ip_config_result (self, AF_INET, NULL);
+	else {
+		if (NM_DEVICE_GET_PRIVATE (self)->ip_state_4 != NM_DEVICE_IP_STATE_DONE)
 			ip_config_merge_and_apply (self, AF_INET, TRUE);
 	}
 }
 
 static void
-acd_manager_probe_terminated (NMAcdManager *acd_manager, AcdData *data)
+acd_manager_probe_terminated (NMAcdManager *acd_manager, gpointer user_data)
 {
+	AcdData *data = user_data;
 	NMDevice *self;
 	NMDevicePrivate *priv;
 	NMDedupMultiIter ipconf_iter;
@@ -6653,13 +6705,15 @@ acd_manager_probe_terminated (NMAcdManager *acd_manager, AcdData *data)
 
 	for (i = 0; data->configs && data->configs[i]; i++) {
 		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, data->configs[i], &address) {
+			char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
 			result = nm_acd_manager_check_address (acd_manager, address->address);
 			success &= result;
 
 			_NMLOG (result ? LOGL_DEBUG : LOGL_WARN,
 			        LOGD_DEVICE,
 			        "IPv4 DAD result: address %s is %s",
-			        nm_utils_inet4_ntop (address->address, NULL),
+			        nm_utils_inet4_ntop (address->address, sbuf),
 			        result ? "unique" : "duplicate");
 		}
 	}
@@ -6667,7 +6721,7 @@ acd_manager_probe_terminated (NMAcdManager *acd_manager, AcdData *data)
 	data->callback (self, data->configs, success);
 
 	priv->acd.dad_list = g_slist_remove (priv->acd.dad_list, acd_manager);
-	nm_acd_manager_destroy (acd_manager);
+	nm_acd_manager_free (acd_manager);
 }
 
 /**
@@ -6683,6 +6737,10 @@ acd_manager_probe_terminated (NMAcdManager *acd_manager, AcdData *data)
 static void
 ipv4_dad_start (NMDevice *self, NMIP4Config **configs, AcdCallback cb)
 {
+	static const NMAcdCallbacks acd_callbacks = {
+		.probe_terminated_callback = acd_manager_probe_terminated,
+		.user_data_destroy         = acd_data_destroy,
+	};
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMAcdManager *acd_manager;
 	const NMPlatformIP4Address *address;
@@ -6726,26 +6784,23 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, AcdCallback cb)
 		return;
 	}
 
-	/* don't take additional references of @acd_manager that outlive @self.
-	 * Otherwise, the callback can be invoked on a dangling pointer as we don't
-	 * disconnect the handler. */
-	acd_manager = nm_acd_manager_new (nm_device_get_ip_ifindex (self), hwaddr_arr, length);
-	priv->acd.dad_list = g_slist_append (priv->acd.dad_list, acd_manager);
-
 	data = g_slice_new0 (AcdData);
 	data->configs = configs;
 	data->callback = cb;
 	data->device = self;
 
+	acd_manager = nm_acd_manager_new (nm_device_get_ip_ifindex (self),
+	                                  hwaddr_arr,
+	                                  length,
+	                                  &acd_callbacks,
+	                                  data);
+	priv->acd.dad_list = g_slist_append (priv->acd.dad_list, acd_manager);
+
 	for (i = 0; configs[i]; i++) {
 		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, configs[i], &address)
 			nm_acd_manager_add_address (acd_manager, address->address);
 	}
 
-	g_signal_connect_data (acd_manager, NM_ACD_MANAGER_PROBE_TERMINATED,
-	                       G_CALLBACK (acd_manager_probe_terminated), data,
-	                       acd_data_destroy, 0);
-
 	ret = nm_acd_manager_start_probe (acd_manager, timeout);
 
 	if (!ret) {
@@ -6755,7 +6810,7 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, AcdCallback cb)
 		cb (self, configs, TRUE);
 
 		priv->acd.dad_list = g_slist_remove (priv->acd.dad_list, acd_manager);
-		nm_acd_manager_destroy (acd_manager);
+		nm_acd_manager_free (acd_manager);
 	}
 }
 
@@ -6783,7 +6838,7 @@ ipv4ll_get_ip4_config (NMDevice *self, guint32 lla)
 	NMPlatformIP4Address address;
 	NMPlatformIP4Route route;
 
-	config = _ip4_config_new (self);
+	config = nm_device_ip4_config_new (self);
 	g_assert (config);
 
 	memset (&address, 0, sizeof (address));
@@ -6811,8 +6866,6 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 {
 	NMDevice *self = data;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection = NULL;
-	const char *method;
 	struct in_addr address;
 	NMIP4Config *config;
 	int r;
@@ -6820,13 +6873,8 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 	if (priv->act_request.obj == NULL)
 		return;
 
-	connection = nm_act_request_get_applied_connection (priv->act_request.obj);
-	g_assert (connection);
-
-	/* Ignore if the connection isn't an AutoIP connection */
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	if (g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) != 0)
-		return;
+	nm_assert (nm_streq (nm_device_get_effective_ip_config_method (self, AF_INET),
+	                     NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL));
 
 	switch (event) {
 	case SD_IPV4LL_EVENT_BIND:
@@ -6850,11 +6898,11 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 			return;
 		}
 
-		if (priv->ip4_state == IP_CONF) {
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) {
 			nm_clear_g_source (&priv->ipv4ll_timeout);
-			nm_device_activate_schedule_ip4_config_result (self, config);
-		} else if (priv->ip4_state == IP_DONE) {
-			applied_config_init (&priv->dev_ip4_config, config);
+			nm_device_activate_schedule_ip_config_result (self, AF_INET, NM_IP_CONFIG_CAST (config));
+		} else if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
+			applied_config_init (&priv->dev_ip_config_4, config);
 			if (!ip_config_merge_and_apply (self, AF_INET, TRUE)) {
 				_LOGE (LOGD_AUTOIP4, "failed to update IP4 config for autoip change.");
 				nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
@@ -6881,8 +6929,8 @@ ipv4ll_timeout_cb (gpointer user_data)
 		priv->ipv4ll_timeout = 0;
 		ipv4ll_cleanup (self);
 
-		if (priv->ip4_state == IP_CONF)
-			nm_device_activate_schedule_ip4_config_timeout (self);
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)
+			nm_device_activate_schedule_ip_config_timeout (self, AF_INET);
 	}
 
 	return FALSE;
@@ -6965,7 +7013,7 @@ ensure_con_ip_config (NMDevice *self, int addr_family)
 	if (!connection)
 		return;
 
-	con_ip_config = _ip_config_new (self, addr_family);
+	con_ip_config = nm_device_ip_config_new (self, addr_family);
 
 	if (IS_IPv4) {
 		nm_ip4_config_merge_setting (NM_IP4_CONFIG (con_ip_config),
@@ -6998,6 +7046,7 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	priv->dhcp4.was_active = FALSE;
 	nm_clear_g_source (&priv->dhcp4.grace_id);
 	g_clear_pointer (&priv->dhcp4.pac_url, g_free);
 	g_clear_pointer (&priv->dhcp4.root_path, g_free);
@@ -7046,10 +7095,9 @@ ip_config_merge_and_apply (NMDevice *self,
 
 	/* Apply ignore-auto-routes and ignore-auto-dns settings */
 	if (connection) {
-		NMSettingIPConfig *s_ip =   IS_IPv4
-		                          ? nm_connection_get_setting_ip4_config (connection)
-		                          : nm_connection_get_setting_ip6_config (connection);
+		NMSettingIPConfig *s_ip;
 
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 		if (s_ip) {
 			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip);
 			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip);
@@ -7068,7 +7116,7 @@ ip_config_merge_and_apply (NMDevice *self,
 		}
 	}
 
-	composite = _ip_config_new (self, addr_family);
+	composite = nm_device_ip_config_new (self, addr_family);
 
 	if (!IS_IPv4) {
 		nm_ip6_config_set_privacy (NM_IP6_CONFIG (composite),
@@ -7110,7 +7158,7 @@ ip_config_merge_and_apply (NMDevice *self,
 	if (commit) {
 		gboolean v;
 
-		v = default_route_metric_penalty_detect (self);
+		v = default_route_metric_penalty_detect (self, addr_family);
 		if (IS_IPv4)
 			priv->default_route_metric_penalty_ip4_has = v;
 		else
@@ -7120,7 +7168,7 @@ ip_config_merge_and_apply (NMDevice *self,
 	/* Merge all the IP configs into the composite config */
 
 	if (IS_IPv4) {
-		config = applied_config_get_current (&priv->dev_ip4_config);
+		config = applied_config_get_current (&priv->dev_ip_config_4);
 		if (config) {
 			nm_ip4_config_merge (NM_IP4_CONFIG (composite), NM_IP4_CONFIG (config),
 			                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
@@ -7161,7 +7209,7 @@ ip_config_merge_and_apply (NMDevice *self,
 	/* Merge WWAN config *last* to ensure modem-given settings overwrite
 	 * any external stuff set by pppd or other scripts.
 	 */
-	config = applied_config_get_current (&priv->wwan_ip_config_x[IS_IPv4]);
+	config = applied_config_get_current (&priv->dev2_ip_config_x[IS_IPv4]);
 	if (config) {
 		nm_ip_config_merge (composite, config,
 		                      (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
@@ -7212,15 +7260,15 @@ ip_config_merge_and_apply (NMDevice *self,
 	}
 
 	if (!IS_IPv4) {
-		if (commit) {
-			NMUtilsIPv6IfaceId iid;
+		NMUtilsIPv6IfaceId iid;
 
-			if (   ip6_addr_gen_token
-			    && nm_utils_ipv6_interface_identifier_get_from_token (&iid, ip6_addr_gen_token)) {
-				nm_platform_link_set_ipv6_token (nm_device_get_platform (self),
-				                                 nm_device_get_ip_ifindex (self),
-				                                 iid);
-			}
+		if (   commit
+		    && priv->ndisc_started
+		    && ip6_addr_gen_token
+		    && nm_utils_ipv6_interface_identifier_get_from_token (&iid, ip6_addr_gen_token)) {
+			nm_platform_link_set_ipv6_token (nm_device_get_platform (self),
+			                                 nm_device_get_ip_ifindex (self),
+			                                 iid);
 		}
 	}
 
@@ -7242,7 +7290,7 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 
 	g_return_val_if_fail (config, FALSE);
 
-	applied_config_init (&priv->dev_ip4_config, config);
+	applied_config_init (&priv->dev_ip_config_4, config);
 
 	if (!ip_config_merge_and_apply (self, AF_INET, TRUE)) {
 		_LOGW (LOGD_DHCP4, "failed to update IPv4 config for DHCP change.");
@@ -7276,28 +7324,32 @@ dhcp4_grace_period_expired (gpointer user_data)
 }
 
 static void
-dhcp4_fail (NMDevice *self)
+dhcp4_fail (NMDevice *self, NMDhcpState dhcp_state)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	_LOGD (LOGD_DHCP4, "DHCPv4 failed (ip_state %s)",
-	       _ip_state_to_string (priv->ip4_state));
+	_LOGD (LOGD_DHCP4, "DHCPv4 failed (ip_state %s, was_active %d)",
+	       _ip_state_to_string (priv->ip_state_4),
+	       priv->dhcp4.was_active);
 
 	/* Keep client running if there are static addresses configured
 	 * on the interface.
 	 */
-	if (   priv->ip4_state == IP_DONE
+	if (   priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
 	    && priv->con_ip_config_4
 	    && nm_ip4_config_get_num_addresses (priv->con_ip_config_4) > 0)
 		goto clear_config;
 
-	/* Fail the method in case of timeout or failure during initial
-	 * configuration.
+	/* Fail the method when one of the following is true:
+	 * 1) the DHCP client terminated: it does not make sense to start a grace
+	 *    period without a client running;
+	 * 2) we failed to get an initial lease AND the client was
+	 *    not active before.
 	 */
-	if (   !priv->dhcp4.was_active
-	    && priv->ip4_state == IP_CONF) {
+	if (   dhcp_state == NM_DHCP_STATE_TERMINATED
+	    || (!priv->dhcp4.was_active && priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)) {
 		dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-		nm_device_activate_schedule_ip4_config_timeout (self);
+		nm_device_activate_schedule_ip_config_timeout (self, AF_INET);
 		return;
 	}
 
@@ -7329,7 +7381,7 @@ static void
 dhcp4_dad_cb (NMDevice *self, NMIP4Config **configs, gboolean success)
 {
 	if (success)
-		nm_device_activate_schedule_ip4_config_result (self, configs[1]);
+		nm_device_activate_schedule_ip_config_result (self, AF_INET, NM_IP_CONFIG_CAST (configs[1]));
 	else {
 		nm_device_ip_method_failed (self, AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
@@ -7358,7 +7410,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 	case NM_DHCP_STATE_BOUND:
 		if (!ip4_config) {
 			_LOGW (LOGD_DHCP4, "failed to get IPv4 config in response to DHCP event.");
-			dhcp4_fail (self);
+			dhcp4_fail (self, state);
 			break;
 		}
 
@@ -7367,8 +7419,8 @@ dhcp4_state_changed (NMDhcpClient *client,
 		/* After some failures, we have been able to renew the lease:
 		 * update the ip state
 		 */
-		if (priv->ip4_state == IP_FAIL)
-			_set_ip_state (self, AF_INET, IP_CONF);
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL)
+			_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_CONF);
 
 		g_free (priv->dhcp4.pac_url);
 		priv->dhcp4.pac_url = g_strdup (g_hash_table_lookup (options, "wpad"));
@@ -7380,11 +7432,11 @@ dhcp4_state_changed (NMDhcpClient *client,
 		nm_dhcp4_config_set_options (priv->dhcp4.config, options);
 		_notify (self, PROP_DHCP4_CONFIG);
 
-		if (priv->ip4_state == IP_CONF) {
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) {
 			connection = nm_device_get_applied_connection (self);
 			g_assert (connection);
 
-			manual = _ip4_config_new (self);
+			manual = nm_device_ip4_config_new (self);
 			nm_ip4_config_merge_setting (manual,
 			                             nm_connection_get_setting_ip4_config (connection),
 			                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
@@ -7397,24 +7449,25 @@ dhcp4_state_changed (NMDhcpClient *client,
 			configs[1] = g_object_ref (ip4_config);
 
 			ipv4_dad_start (self, configs, dhcp4_dad_cb);
-		} else if (priv->ip4_state == IP_DONE) {
+		} else if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 			if (dhcp4_lease_change (self, ip4_config))
 				nm_device_update_metered (self);
 			else
-				dhcp4_fail (self);
+				dhcp4_fail (self, state);
 		}
 		break;
 	case NM_DHCP_STATE_TIMEOUT:
-		dhcp4_fail (self);
+		dhcp4_fail (self, state);
 		break;
 	case NM_DHCP_STATE_EXPIRE:
 		/* Ignore expiry before we even have a lease (NAK, old lease, etc) */
-		if (priv->ip4_state == IP_CONF)
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)
 			break;
 		/* fall through */
 	case NM_DHCP_STATE_DONE:
 	case NM_DHCP_STATE_FAIL:
-		dhcp4_fail (self);
+	case NM_DHCP_STATE_TERMINATED:
+		dhcp4_fail (self, state);
 		break;
 	default:
 		break;
@@ -7434,10 +7487,7 @@ get_dhcp_timeout (NMDevice *self, int addr_family)
 
 	connection = nm_device_get_applied_connection (self);
 
-	if (addr_family == AF_INET)
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-	else
-		s_ip = nm_connection_get_setting_ip6_config (connection);
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 
 	timeout = nm_setting_ip_config_get_dhcp_timeout (s_ip);
 	if (timeout)
@@ -7445,8 +7495,8 @@ get_dhcp_timeout (NMDevice *self, int addr_family)
 
 	timeout = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
 	                                                       addr_family == AF_INET
-	                                                         ? "ipv4.dhcp-timeout"
-	                                                         : "ipv6.dhcp-timeout",
+	                                                         ? NM_CON_DEFAULT ("ipv4.dhcp-timeout")
+	                                                         : NM_CON_DEFAULT ("ipv6.dhcp-timeout"),
 	                                                       self,
 	                                                       0, G_MAXINT32, 0);
 	if (timeout)
@@ -7481,7 +7531,8 @@ dhcp4_get_client_id (NMDevice *self,
 
 	if (!client_id) {
 		client_id_default = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-		                                                           "ipv4.dhcp-client-id", self);
+		                                                           NM_CON_DEFAULT ("ipv4.dhcp-client-id"),
+		                                                           self);
 		if (client_id_default && client_id_default[0]) {
 			/* a non-empty client-id is always valid, see nm_dhcp_utils_client_id_string_to_bytes().  */
 			client_id = client_id_default;
@@ -7490,7 +7541,7 @@ dhcp4_get_client_id (NMDevice *self,
 
 	if (!client_id) {
 		_LOGD (LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4,
-		       "ipv4.dhcp-client-id: no explicity client-id configured");
+		       "ipv4.dhcp-client-id: no explicit client-id configured");
 		return NULL;
 	}
 
@@ -7723,7 +7774,7 @@ shared4_new_config (NMDevice *self, NMConnection *connection)
 		is_generated = TRUE;
 	}
 
-	config = _ip4_config_new (self);
+	config = nm_device_ip4_config_new (self);
 	nm_ip4_config_add_address (config, &address);
 	if (is_generated) {
 		/* Remove the address lock when the object gets disposed */
@@ -7737,37 +7788,25 @@ shared4_new_config (NMDevice *self, NMConnection *connection)
 /*****************************************************************************/
 
 static gboolean
-connection_ip4_method_requires_carrier (NMConnection *connection,
-                                        gboolean *out_ip4_enabled)
-{
-	const char *method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	static const char *ip4_carrier_methods[] = {
-		NM_SETTING_IP4_CONFIG_METHOD_AUTO,
-		NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL,
-		NULL
-	};
+connection_ip_method_requires_carrier (NMConnection *connection,
+                                       int addr_family,
+                                       gboolean *out_ip_enabled)
+{
+	const char *method;
 
-	if (out_ip4_enabled)
-		*out_ip4_enabled = !!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
-	return g_strv_contains (ip4_carrier_methods, method);
-}
+	method = nm_utils_get_ip_config_method (connection, addr_family);
 
-static gboolean
-connection_ip6_method_requires_carrier (NMConnection *connection,
-                                        gboolean *out_ip6_enabled)
-{
-	const char *method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-	static const char *ip6_carrier_methods[] = {
-		NM_SETTING_IP6_CONFIG_METHOD_AUTO,
-		NM_SETTING_IP6_CONFIG_METHOD_DHCP,
-		NM_SETTING_IP6_CONFIG_METHOD_SHARED,
-		NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
-		NULL
-	};
+	if (addr_family == AF_INET) {
+		NM_SET_OUT (out_ip_enabled, !nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED));
+		return NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+		                             NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL);
+	}
 
-	if (out_ip6_enabled)
-		*out_ip6_enabled = !!strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
-	return g_strv_contains (ip6_carrier_methods, method);
+	NM_SET_OUT (out_ip_enabled, !nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE));
+	return NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+	                             NM_SETTING_IP6_CONFIG_METHOD_DHCP,
+	                             NM_SETTING_IP6_CONFIG_METHOD_SHARED,
+	                             NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL);
 }
 
 static gboolean
@@ -7784,7 +7823,7 @@ connection_requires_carrier (NMConnection *connection)
 	if (nm_setting_connection_get_master (s_con))
 		return FALSE;
 
-	ip4_carrier_wanted = connection_ip4_method_requires_carrier (connection, &ip4_used);
+	ip4_carrier_wanted = connection_ip_method_requires_carrier (connection, AF_INET, &ip4_used);
 	if (ip4_carrier_wanted) {
 		/* If IPv4 wants a carrier and cannot fail, the whole connection
 		 * requires a carrier regardless of the IPv6 method.
@@ -7794,7 +7833,7 @@ connection_requires_carrier (NMConnection *connection)
 			return TRUE;
 	}
 
-	ip6_carrier_wanted = connection_ip6_method_requires_carrier (connection, &ip6_used);
+	ip6_carrier_wanted = connection_ip_method_requires_carrier (connection, AF_INET6, &ip6_used);
 	if (ip6_carrier_wanted) {
 		/* If IPv6 wants a carrier and cannot fail, the whole connection
 		 * requires a carrier regardless of the IPv4 method.
@@ -7835,107 +7874,6 @@ have_any_ready_slaves (NMDevice *self)
 	return FALSE;
 }
 
-static gboolean
-ip4_requires_slaves (NMConnection *connection)
-{
-	const char *method;
-
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	return strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0;
-}
-
-static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *self,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
-	const char *method;
-
-	connection = nm_device_get_applied_connection (self);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	if (   connection_ip4_method_requires_carrier (connection, NULL)
-	    && nm_device_is_master (self)
-	    && !priv->carrier) {
-		_LOGI (LOGD_IP4 | LOGD_DEVICE,
-		       "IPv4 config waiting until carrier is on");
-		return NM_ACT_STAGE_RETURN_IP_WAIT;
-	}
-
-	if (nm_device_is_master (self) && ip4_requires_slaves (connection)) {
-		/* If the master has no ready slaves, and depends on slaves for
-		 * a successful IPv4 attempt, then postpone IPv4 addressing.
-		 */
-		if (!have_any_ready_slaves (self)) {
-			_LOGI (LOGD_DEVICE | LOGD_IP4,
-			       "IPv4 config waiting until slaves are ready");
-			return NM_ACT_STAGE_RETURN_IP_WAIT;
-		}
-	}
-
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-
-	if (NM_IN_STRSET (method,
-	                  NM_SETTING_IP4_CONFIG_METHOD_AUTO,
-	                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
-		NMSettingIPConfig *s_ip4;
-		NMIP4Config **configs, *config;
-		guint num_addresses;
-
-		s_ip4 = nm_connection_get_setting_ip4_config (connection);
-		g_return_val_if_fail (s_ip4, NM_ACT_STAGE_RETURN_FAILURE);
-		num_addresses = nm_setting_ip_config_get_num_addresses (s_ip4);
-
-		if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
-			ret = dhcp4_start (self);
-			if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
-				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
-				return ret;
-			}
-		} else {
-			g_return_val_if_fail (num_addresses != 0, NM_ACT_STAGE_RETURN_FAILURE);
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-		}
-
-		if (num_addresses) {
-			config = _ip4_config_new (self);
-			nm_ip4_config_merge_setting (config,
-			                             nm_connection_get_setting_ip4_config (connection),
-			                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
-			                             NM_SETTING_CONNECTION_LLMNR_DEFAULT,
-			                             nm_device_get_route_table (self, AF_INET, TRUE),
-			                             nm_device_get_route_metric (self, AF_INET));
-			configs = g_new0 (NMIP4Config *, 2);
-			configs[0] = config;
-			ipv4_dad_start (self, configs, ipv4_manual_method_apply);
-		}
-	} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
-		ret = ipv4ll_start (self);
-		if (ret == NM_ACT_STAGE_RETURN_FAILURE)
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
-	} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
-		if (out_config) {
-			*out_config = shared4_new_config (self, connection);
-			if (*out_config) {
-				priv->dnsmasq_manager = nm_dnsmasq_manager_new (nm_device_get_ip_iface (self));
-				ret = NM_ACT_STAGE_RETURN_SUCCESS;
-			} else {
-				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-				ret = NM_ACT_STAGE_RETURN_FAILURE;
-			}
-		} else
-			g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE);
-	} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
-		ret = NM_ACT_STAGE_RETURN_SUCCESS;
-	else
-		_LOGW (LOGD_IP4, "unhandled IPv4 config method '%s'; will fail", method);
-
-	return ret;
-}
-
 /*****************************************************************************/
 /* DHCPv6 stuff */
 
@@ -7944,6 +7882,7 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	priv->dhcp6.was_active = FALSE;
 	priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
 	applied_config_clear (&priv->dhcp6.ip6_config);
 	g_clear_pointer (&priv->dhcp6.event_id, g_free);
@@ -8017,12 +7956,14 @@ dhcp6_grace_period_expired (gpointer user_data)
 }
 
 static void
-dhcp6_fail (NMDevice *self, gboolean timeout)
+dhcp6_fail (NMDevice *self, NMDhcpState dhcp_state)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean is_dhcp_managed;
 
-	_LOGD (LOGD_DHCP6, "DHCPv6 failed%s", timeout ? " (timeout)" : "");
+	_LOGD (LOGD_DHCP6, "DHCPv6 failed (ip_state %s, was_active %d)",
+	       _ip_state_to_string (priv->ip_state_6),
+	       priv->dhcp6.was_active);
 
 	is_dhcp_managed = (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED);
 
@@ -8030,18 +7971,21 @@ dhcp6_fail (NMDevice *self, gboolean timeout)
 		/* Keep client running if there are static addresses configured
 		 * on the interface.
 		 */
-		if (   priv->ip6_state == IP_DONE
+		if (   priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE
 		    && priv->con_ip_config_6
 		    && nm_ip6_config_get_num_addresses (priv->con_ip_config_6))
 			goto clear_config;
 
-		/* Fail the method in case of timeout or failure during initial
-		 * configuration.
+		/* Fail the method when one of the following is true:
+		 * 1) the DHCP client terminated: it does not make sense to start a grace
+		 *    period without a client running;
+		 * 2) we failed to get an initial lease AND the client was
+		 *    not active before.
 		 */
-		if (   !priv->dhcp6.was_active
-		    && (timeout || priv->ip6_state == IP_CONF)) {
+		if (   dhcp_state == NM_DHCP_STATE_TERMINATED
+		    || (!priv->dhcp6.was_active && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)) {
 			dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-			nm_device_activate_schedule_ip6_config_timeout (self);
+			nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 			return;
 		}
 
@@ -8061,8 +8005,8 @@ dhcp6_fail (NMDevice *self, gboolean timeout)
 	} else {
 		/* not a hard failure; just live with the RA info */
 		dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-		if (priv->ip6_state == IP_CONF)
-			nm_device_activate_schedule_ip6_config_result (self);
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)
+			nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 	}
 	return;
 
@@ -8076,21 +8020,6 @@ clear_config:
 }
 
 static void
-dhcp6_timeout (NMDevice *self, NMDhcpClient *client)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED)
-		dhcp6_fail (self, TRUE);
-	else {
-		/* not a hard failure; just live with the RA info */
-		dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-		if (priv->ip6_state == IP_CONF)
-			nm_device_activate_schedule_ip6_config_result (self);
-	}
-}
-
-static void
 dhcp6_state_changed (NMDhcpClient *client,
                      NMDhcpState state,
                      NMIP6Config *ip6_config,
@@ -8136,28 +8065,35 @@ dhcp6_state_changed (NMDhcpClient *client,
 		/* After long time we have been able to renew the lease:
 		 * update the ip state
 		 */
-		if (priv->ip6_state == IP_FAIL)
-			_set_ip_state (self, AF_INET6, IP_CONF);
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
+			_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
 
-		if (priv->ip6_state == IP_CONF) {
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
 			if (!applied_config_get_current (&priv->dhcp6.ip6_config)) {
 				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_DHCP_FAILED);
 				break;
 			}
-			nm_device_activate_schedule_ip6_config_result (self);
-		} else if (priv->ip6_state == IP_DONE)
+			nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
+		} else if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE)
 			if (!dhcp6_lease_change (self))
-				dhcp6_fail (self, FALSE);
+				dhcp6_fail (self, state);
 		break;
 	case NM_DHCP_STATE_TIMEOUT:
-		dhcp6_timeout (self, client);
+		if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED)
+			dhcp6_fail (self, state);
+		else {
+			/* not a hard failure; just live with the RA info */
+			dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
+			if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)
+				nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
+		}
 		break;
 	case NM_DHCP_STATE_EXPIRE:
 		/* Ignore expiry before we even have a lease (NAK, old lease, etc) */
-		if (priv->ip6_state != IP_CONF)
-			dhcp6_fail (self, FALSE);
+		if (priv->ip_state_6 != NM_DEVICE_IP_STATE_CONF)
+			dhcp6_fail (self, state);
 		break;
-	case NM_DHCP_STATE_DONE:
+	case NM_DHCP_STATE_TERMINATED:
 		/* In IPv6 info-only mode, the client doesn't handle leases so it
 		 * may exit right after getting a response from the server.  That's
 		 * normal.  In that case we just ignore the exit.
@@ -8165,8 +8101,9 @@ dhcp6_state_changed (NMDhcpClient *client,
 		if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF)
 			break;
 		/* fall through */
+	case NM_DHCP_STATE_DONE:
 	case NM_DHCP_STATE_FAIL:
-		dhcp6_fail (self, FALSE);
+		dhcp6_fail (self, state);
 		break;
 	default:
 		break;
@@ -8312,7 +8249,8 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
 
 	if (!duid) {
 		duid_default = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-		                                                      "ipv6.dhcp-duid", self);
+		                                                      NM_CON_DEFAULT ("ipv6.dhcp-duid"),
+		                                                      self);
 		duid = duid_default;
 		if (!duid)
 			duid = "lease";
@@ -8359,7 +8297,7 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
 		/* preferably, we would salt the checksum differently for each @duid type. We missed
 		 * to do that initially, so most types use the DEFAULT_SALT.
 		 *
-		 * Implemenations that are added later, should use a distinct salt instead,
+		 * Implementations that are added later, should use a distinct salt instead,
 		 * like "stable-ll"/"stable-llt" with ARPHRD_INFINIBAND below. */
 		const guint32 DEFAULT_SALT = 670531087u;
 		nm_auto_free_checksum GChecksum *sum = NULL;
@@ -8664,6 +8602,7 @@ nm_device_use_ip6_subnet (NMDevice *self, const NMPlatformIP6Address *subnet)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMPlatformIP6Address address = *subnet;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	if (!applied_config_get_current (&priv->ac_ip6_config))
 		applied_config_init_new (&priv->ac_ip6_config, self, AF_INET6);
@@ -8673,7 +8612,7 @@ nm_device_use_ip6_subnet (NMDevice *self, const NMPlatformIP6Address *subnet)
 	applied_config_add_address (&priv->ac_ip6_config, NM_PLATFORM_IP_ADDRESS_CAST (&address));
 
 	_LOGD (LOGD_IP6, "ipv6-pd: using %s address (preferred for %u seconds)",
-	       nm_utils_inet6_ntop (&address.address, NULL),
+	       nm_utils_inet6_ntop (&address.address, sbuf),
 	       subnet->preferred);
 
 	/* This also updates the ndisc if there are actual changes. */
@@ -8727,7 +8666,7 @@ linklocal6_failed (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	nm_clear_g_source (&priv->linklocal6_timeout_id);
-	nm_device_activate_schedule_ip6_config_timeout (self);
+	nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 }
 
 static gboolean
@@ -8765,20 +8704,20 @@ linklocal6_check_complete (NMDevice *self)
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	method = nm_device_get_effective_ip_config_method (self, AF_INET6);
 
 	_LOGD (LOGD_DEVICE, "linklocal6: waiting for link-local addresses successful, continue with method %s", method);
 
-	if (   strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
-	    || strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0)
+	if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+	                          NM_SETTING_IP6_CONFIG_METHOD_SHARED))
 		addrconf6_start_with_link_ready (self);
-	else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
+	else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
 		if (!dhcp6_start_with_link_ready (self, connection)) {
 			/* Time out IPv6 instead of failing the entire activation */
-			nm_device_activate_schedule_ip6_config_timeout (self);
+			nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 		}
-	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0)
-		nm_device_activate_schedule_ip6_config_result (self);
+	} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
+		nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 	else
 		g_return_if_fail (FALSE);
 }
@@ -8792,6 +8731,7 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 	NMSettingIP6Config *s_ip6 = NULL;
 	GError *error = NULL;
 	const char *addr_type;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	if (!priv->ipv6ll_handle)
 		return;
@@ -8852,7 +8792,8 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 		addr_type = "EUI-64";
 	}
 
-	_LOGD (LOGD_IP6, "linklocal6: generated %s IPv6LL address %s", addr_type, nm_utils_inet6_ntop (&lladdr, NULL));
+	_LOGD (LOGD_IP6, "linklocal6: generated %s IPv6LL address %s",
+	       addr_type, nm_utils_inet6_ntop (&lladdr, sbuf));
 	priv->ipv6ll_has = TRUE;
 	priv->ipv6ll_addr = lladdr;
 	ip_config_merge_and_apply (self, AF_INET6, TRUE);
@@ -8862,8 +8803,6 @@ static gboolean
 linklocal6_start (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
-	const char *method;
 
 	nm_clear_g_source (&priv->linklocal6_timeout_id);
 
@@ -8873,11 +8812,8 @@ linklocal6_start (NMDevice *self)
 	                                         | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL))
 		return TRUE;
 
-	connection = nm_device_get_applied_connection (self);
-	g_assert (connection);
-
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-	_LOGD (LOGD_DEVICE, "linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses configured. Wait.", method);
+	_LOGD (LOGD_DEVICE, "linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses configured. Wait.",
+	       nm_device_get_effective_ip_config_method (self, AF_INET6));
 
 	check_and_add_ipv6ll_addr (self);
 
@@ -8925,19 +8861,23 @@ nm_device_get_configured_mtu_from_connection (NMDevice *self,
 	if (setting_type == NM_TYPE_SETTING_WIRED) {
 		if (setting)
 			mtu = nm_setting_wired_get_mtu (NM_SETTING_WIRED (setting));
-		global_property_name = "ethernet.mtu";
+		global_property_name = NM_CON_DEFAULT ("ethernet.mtu");
 	} else if (setting_type == NM_TYPE_SETTING_WIRELESS) {
 		if (setting)
 			mtu = nm_setting_wireless_get_mtu (NM_SETTING_WIRELESS (setting));
-		global_property_name = "wifi.mtu";
+		global_property_name = NM_CON_DEFAULT ("wifi.mtu");
 	} else if (setting_type == NM_TYPE_SETTING_INFINIBAND) {
 		if (setting)
 			mtu = nm_setting_infiniband_get_mtu (NM_SETTING_INFINIBAND (setting));
-		global_property_name = "infiniband.mtu";
+		global_property_name = NM_CON_DEFAULT ("infiniband.mtu");
 	} else if (setting_type == NM_TYPE_SETTING_IP_TUNNEL) {
 		if (setting)
 			mtu = nm_setting_ip_tunnel_get_mtu (NM_SETTING_IP_TUNNEL (setting));
-		global_property_name = "ip-tunnel.mtu";
+		global_property_name = NM_CON_DEFAULT ("ip-tunnel.mtu");
+	} else if (setting_type == NM_TYPE_SETTING_WIREGUARD) {
+		if (setting)
+			mtu = nm_setting_wireguard_get_mtu (NM_SETTING_WIREGUARD (setting));
+		global_property_name = NM_CON_DEFAULT ("wireguard.mtu");
 	} else
 		g_return_val_if_reached (0);
 
@@ -9017,7 +8957,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 	{
 		guint32 mtu = 0;
 
-		/* preferably, get the MTU from explict user-configuration.
+		/* preferably, get the MTU from explicit user-configuration.
 		 * Only if that fails, look at the current @config (which contains
 		 * MTUs from DHCP/PPP) or maybe fallback to a device-specific MTU. */
 
@@ -9052,7 +8992,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 	if (mtu_desired && mtu_desired < 1280) {
 		NMSettingIPConfig *s_ip6;
 
-		s_ip6 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
+		s_ip6 = nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
 		if (   s_ip6
 		    && !NM_IN_STRSET (nm_setting_ip_config_get_method (s_ip6),
 		                      NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
@@ -9107,7 +9047,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 #define _IP6_MTU_SYS() \
 	({ \
 		if (!ip6_mtu_sysctl.initialized) { \
-			ip6_mtu_sysctl.value = nm_device_ipv6_sysctl_get_uint32 (self, "mtu", 0); \
+			ip6_mtu_sysctl.value = nm_device_sysctl_ip_conf_get_int_checked (self, AF_INET6, "mtu", 10, 0, G_MAXUINT32, 0); \
 			ip6_mtu_sysctl.initialized = TRUE; \
 		} \
 		ip6_mtu_sysctl.value; \
@@ -9124,7 +9064,10 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 		}
 
 		if (mtu_desired && mtu_desired != mtu_plat) {
-			if (nm_platform_link_set_mtu (nm_device_get_platform (self), ifindex, mtu_desired) == NM_PLATFORM_ERROR_CANT_SET_MTU) {
+			int r;
+
+			r = nm_platform_link_set_mtu (nm_device_get_platform (self), ifindex, mtu_desired);
+			if (r == -NME_PL_CANT_SET_MTU) {
 				anticipated_failure = TRUE;
 				success = FALSE;
 				_LOGW (LOGD_DEVICE, "mtu: failure to set MTU. %s",
@@ -9138,8 +9081,8 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 		}
 
 		if (ip6_mtu && ip6_mtu != _IP6_MTU_SYS ()) {
-			if (!nm_device_ipv6_sysctl_set (self, "mtu",
-			                                nm_sprintf_buf (sbuf, "%u", (unsigned) ip6_mtu))) {
+			if (!nm_device_sysctl_ip_conf_set (self, AF_INET6, "mtu",
+			                                   nm_sprintf_buf (sbuf, "%u", (unsigned) ip6_mtu))) {
 				int errsv = errno;
 
 				_NMLOG (anticipated_failure && errsv == EINVAL ? LOGL_DEBUG : LOGL_WARN,
@@ -9281,7 +9224,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 	}
 
 	if (changed & NM_NDISC_CONFIG_HOP_LIMIT)
-		nm_platform_sysctl_set_ip6_hop_limit_safe (nm_device_get_platform (self), nm_device_get_ip_iface (self), rdata->hop_limit);
+		nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (nm_device_get_platform (self), nm_device_get_ip_iface (self), rdata->hop_limit);
 
 	if (changed & NM_NDISC_CONFIG_MTU) {
 		if (priv->ip6_mtu != rdata->mtu) {
@@ -9290,7 +9233,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 		}
 	}
 
-	nm_device_activate_schedule_ip6_config_result (self);
+	nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 }
 
 static void
@@ -9304,7 +9247,7 @@ ndisc_ra_timeout (NMNDisc *ndisc, NMDevice *self)
 	 */
 
 	_LOGD (LOGD_IP6, "timed out waiting for IPv6 router advertisement");
-	if (priv->ip6_state == IP_CONF) {
+	if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
 		/* If RA is our only source of addressing information and we don't
 		 * ever receive one, then time out IPv6.  But if there is other
 		 * IPv6 configuration, like manual IPv6 addresses or external IPv6
@@ -9317,9 +9260,9 @@ ndisc_ra_timeout (NMNDisc *ndisc, NMDevice *self)
 		    && nm_ip6_config_find_first_address (priv->ip_config_6,
 		                                           NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
 		                                         | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY))
-			nm_device_activate_schedule_ip6_config_result (self);
+			nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 		else
-			nm_device_activate_schedule_ip6_config_timeout (self);
+			nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 	}
 }
 
@@ -9337,7 +9280,7 @@ addrconf6_start_with_link_ready (NMDevice *self)
 	} else {
 		/* Don't abort the addrconf at this point -- if ndisc needs the iid
 		 * it will notice this itself. */
-		_LOGI (LOGD_IP6, "addrconf6: no interface identifier; IPv6 adddress creation may fail");
+		_LOGI (LOGD_IP6, "addrconf6: no interface identifier; IPv6 address creation may fail");
 	}
 
 	/* Apply any manual configuration before starting RA */
@@ -9348,15 +9291,15 @@ addrconf6_start_with_link_ready (NMDevice *self)
 	switch (nm_ndisc_get_node_type (priv->ndisc)) {
 	case NM_NDISC_NODE_TYPE_HOST:
 		/* Accepting prefixes from discovered routers. */
-		nm_device_ipv6_sysctl_set (self, "accept_ra", "1");
-		nm_device_ipv6_sysctl_set (self, "accept_ra_defrtr", "0");
-		nm_device_ipv6_sysctl_set (self, "accept_ra_pinfo", "0");
-		nm_device_ipv6_sysctl_set (self, "accept_ra_rtr_pref", "0");
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra", "1");
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra_defrtr", "0");
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra_pinfo", "0");
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra_rtr_pref", "0");
 		break;
 	case NM_NDISC_NODE_TYPE_ROUTER:
 		/* We're the router. */
-		nm_device_ipv6_sysctl_set (self, "forwarding", "1");
-		nm_device_activate_schedule_ip6_config_result (self);
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "forwarding", "1");
+		nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 		priv->needs_ip6_subnet = TRUE;
 		g_signal_emit (self, signals[IP6_SUBNET_NEEDED], 0);
 		break;
@@ -9375,22 +9318,17 @@ addrconf6_start_with_link_ready (NMDevice *self)
 
 	ndisc_set_router_config (priv->ndisc, self);
 	nm_ndisc_start (priv->ndisc);
+	priv->ndisc_started = TRUE;
 	return;
 }
 
 static NMNDiscNodeType
 ndisc_node_type (NMDevice *self)
 {
-	NMConnection *connection;
-
-	connection = nm_device_get_applied_connection (self);
-	g_assert (connection);
-
-	if (strcmp (nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG),
-	            NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0)
+	if (nm_streq (nm_device_get_effective_ip_config_method (self, AF_INET6),
+	              NM_SETTING_IP4_CONFIG_METHOD_SHARED))
 		return NM_NDISC_NODE_TYPE_ROUTER;
-	else
-		return NM_NDISC_NODE_TYPE_HOST;
+	return NM_NDISC_NODE_TYPE_HOST;
 }
 
 static gboolean
@@ -9459,6 +9397,7 @@ addrconf6_cleanup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	priv->ndisc_started = FALSE;
 	nm_clear_g_signal_handler (priv->ndisc, &priv->ndisc_changed_id);
 	nm_clear_g_signal_handler (priv->ndisc, &priv->ndisc_timeout_id);
 
@@ -9472,34 +9411,36 @@ addrconf6_cleanup (NMDevice *self)
 
 /*****************************************************************************/
 
-static const char *ip6_properties_to_save[] = {
-	"accept_ra",
-	"accept_ra_defrtr",
-	"accept_ra_pinfo",
-	"accept_ra_rtr_pref",
-	"forwarding",
-	"disable_ipv6",
-	"hop_limit",
-	"use_tempaddr",
-};
-
 static void
 save_ip6_properties (NMDevice *self)
 {
+	static const char *const ip6_properties_to_save[] = {
+		"accept_ra",
+		"accept_ra_defrtr",
+		"accept_ra_pinfo",
+		"accept_ra_rtr_pref",
+		"forwarding",
+		"disable_ipv6",
+		"hop_limit",
+		"use_tempaddr",
+	};
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *ifname = nm_device_get_ip_iface (self);
+	NMPlatform *platform = nm_device_get_platform (self);
+	const char *ifname;
 	char *value;
 	int i;
 
 	g_hash_table_remove_all (priv->ip6_saved_properties);
 
-	if (!nm_device_get_ip_ifindex (self))
+	ifname = nm_device_get_ip_iface_from_platform (self);
+	if (!ifname)
 		return;
 
 	for (i = 0; i < G_N_ELEMENTS (ip6_properties_to_save); i++) {
-		char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
-
-		value = nm_platform_sysctl_get (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, ifname, ip6_properties_to_save[i])));
+		value = nm_platform_sysctl_ip_conf_get (platform,
+		                                        AF_INET6,
+		                                        ifname,
+		                                        ip6_properties_to_save[i]);
 		if (value) {
 			g_hash_table_insert (priv->ip6_saved_properties,
 			                     (char *) ip6_properties_to_save[i],
@@ -9521,24 +9462,23 @@ restore_ip6_properties (NMDevice *self)
 		if (   priv->ipv6ll_handle
 		    && nm_streq (key, "disable_ipv6"))
 			continue;
-		nm_device_ipv6_sysctl_set (self, key, value);
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, key, value);
 	}
 }
 
-static inline void
+static void
 set_disable_ipv6 (NMDevice *self, const char *value)
 {
 	/* We only touch disable_ipv6 when NM is not managing the IPv6LL address */
 	if (!NM_DEVICE_GET_PRIVATE (self)->ipv6ll_handle)
-		nm_device_ipv6_sysctl_set (self, "disable_ipv6", value);
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", value);
 }
 
-static inline void
+static void
 set_nm_ipv6ll (NMDevice *self, gboolean enable)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int ifindex = nm_device_get_ip_ifindex (self);
-	char *value;
 
 	if (!nm_platform_check_kernel_support (nm_device_get_platform (self),
 	                                       NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
@@ -9546,32 +9486,34 @@ set_nm_ipv6ll (NMDevice *self, gboolean enable)
 
 	priv->ipv6ll_handle = enable;
 	if (ifindex > 0) {
-		NMPlatformError plerr;
 		const char *detail = enable ? "enable" : "disable";
+		int r;
 
 		_LOGD (LOGD_IP6, "will %s userland IPv6LL", detail);
-		plerr = nm_platform_link_set_user_ipv6ll_enabled (nm_device_get_platform (self), ifindex, enable);
-		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
-			_NMLOG ((   plerr == NM_PLATFORM_ERROR_NOT_FOUND
-			         || plerr == NM_PLATFORM_ERROR_OPNOTSUPP) ? LOGL_DEBUG : LOGL_WARN,
+		r = nm_platform_link_set_user_ipv6ll_enabled (nm_device_get_platform (self), ifindex, enable);
+		if (r < 0) {
+			_NMLOG (  NM_IN_SET (r, -NME_PL_NOT_FOUND,
+			                        -NME_PL_OPNOTSUPP)
+			        ? LOGL_DEBUG
+			        : LOGL_WARN,
 			        LOGD_IP6,
 			        "failed to %s userspace IPv6LL address handling (%s)",
 			        detail,
-			        nm_platform_error_to_string_a (plerr));
+			        nm_strerror (r));
 		}
 
 		if (enable) {
-			char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+			gs_free char *value = NULL;
 
 			/* Bounce IPv6 to ensure the kernel stops IPv6LL address generation */
-			value = nm_platform_sysctl_get (nm_device_get_platform (self),
-			                                NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, nm_device_get_ip_iface (self), "disable_ipv6")));
-			if (g_strcmp0 (value, "0") == 0)
-				nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
-			g_free (value);
+			value = nm_device_sysctl_ip_conf_get (self,
+			                                      AF_INET6,
+			                                      "disable_ipv6");
+			if (nm_streq0 (value, "0"))
+				nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1");
 
 			/* Ensure IPv6 is enabled */
-			nm_device_ipv6_sysctl_set (self, "disable_ipv6", "0");
+			nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "0");
 		}
 
 	}
@@ -9616,7 +9558,7 @@ _ip6_privacy_get (NMDevice *self)
 
 	/* 2.) use the default value from the configuration. */
 	ip6_privacy = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                           "ipv6.ip6-privacy",
+	                                                           NM_CON_DEFAULT ("ipv6.ip6-privacy"),
 	                                                           self,
 	                                                           NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN,
 	                                                           NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
@@ -9632,150 +9574,228 @@ _ip6_privacy_get (NMDevice *self)
 	 * Instead of reading static config files in /etc, just read the current sysctl value.
 	 * This works as NM only writes to "/proc/sys/net/ipv6/conf/IFNAME/use_tempaddr", but leaves
 	 * the "default" entry untouched. */
-	ip6_privacy = nm_platform_sysctl_get_int32 (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv6/conf/default/use_tempaddr"), NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+	ip6_privacy = nm_platform_sysctl_get_int32 (nm_device_get_platform (self),
+	                                            NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv6/conf/default/use_tempaddr"),
+	                                            NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
 	return _ip6_privacy_clamp (ip6_privacy);
 }
 
 /*****************************************************************************/
 
 static gboolean
-ip6_requires_slaves (NMConnection *connection)
+ip_requires_slaves (NMDevice *self, int addr_family)
 {
 	const char *method;
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	method = nm_device_get_effective_ip_config_method (self, addr_family);
+
+	if (addr_family == AF_INET)
+		return nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
 	/* SLAAC, DHCP, and Link-Local depend on connectivity (and thus slaves)
 	 * to complete addressing.  SLAAC and DHCP need a peer to provide a prefix.
 	 */
-	return    strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
-	       || strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0;
+	return NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+	                             NM_SETTING_IP6_CONFIG_METHOD_DHCP);
 }
 
 static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *self,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *self,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMConnection *connection;
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	const char *method;
-	NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
-	const char *ip6_privacy_str = "0";
+
+	nm_assert_addr_family (addr_family);
 
 	connection = nm_device_get_applied_connection (self);
+
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
-	if (   connection_ip6_method_requires_carrier (connection, NULL)
+	if (   connection_ip_method_requires_carrier (connection, addr_family, NULL)
 	    && nm_device_is_master (self)
 	    && !priv->carrier) {
-		_LOGI (LOGD_IP6 | LOGD_DEVICE,
-		       "IPv6 config waiting until carrier is on");
+		_LOGI (LOGD_IP | LOGD_DEVICE,
+		       "IPv%c config waiting until carrier is on",
+		       nm_utils_addr_family_to_char (addr_family));
 		return NM_ACT_STAGE_RETURN_IP_WAIT;
 	}
 
-	if (nm_device_is_master (self) && ip6_requires_slaves (connection)) {
+	if (   nm_device_is_master (self)
+	    && ip_requires_slaves (self, addr_family)) {
 		/* If the master has no ready slaves, and depends on slaves for
-		 * a successful IPv6 attempt, then postpone IPv6 addressing.
+		 * a successful IP configuration attempt, then postpone IP addressing.
 		 */
 		if (!have_any_ready_slaves (self)) {
-			_LOGI (LOGD_DEVICE | LOGD_IP6,
-			       "IPv6 config waiting until slaves are ready");
+			_LOGI (LOGD_DEVICE | LOGD_IP,
+			       "IPv%c config waiting until slaves are ready",
+			       nm_utils_addr_family_to_char (addr_family));
 			return NM_ACT_STAGE_RETURN_IP_WAIT;
 		}
 	}
 
-	priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	if (!IS_IPv4)
+		priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
 
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) {
-		if (   !priv->master
-		    && !nm_device_sys_iface_state_is_external (self)) {
-			gboolean ipv6ll_handle_old = priv->ipv6ll_handle;
+	method = nm_device_get_effective_ip_config_method (self, addr_family);
 
-			/* When activating an IPv6 'ignore' connection we need to revert back
-			 * to kernel IPv6LL, but the kernel won't actually assign an address
-			 * to the interface until disable_ipv6 is bounced.
-			 */
-			set_nm_ipv6ll (self, FALSE);
-			if (ipv6ll_handle_old)
-				nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
-			restore_ip6_properties (self);
+	_LOGD (LOGD_IP | LOGD_DEVICE, "IPv%c config method is %s",
+	       nm_utils_addr_family_to_char (addr_family), method);
+
+	if (IS_IPv4) {
+		if (NM_IN_STRSET (method,
+		                  NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+		                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
+			NMSettingIPConfig *s_ip4;
+			NMIP4Config **configs, *config;
+			guint num_addresses;
+
+			s_ip4 = nm_connection_get_setting_ip4_config (connection);
+			g_return_val_if_fail (s_ip4, NM_ACT_STAGE_RETURN_FAILURE);
+			num_addresses = nm_setting_ip_config_get_num_addresses (s_ip4);
+
+			if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
+				ret = dhcp4_start (self);
+				if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+					NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
+					return ret;
+				}
+			} else {
+				g_return_val_if_fail (num_addresses != 0, NM_ACT_STAGE_RETURN_FAILURE);
+				ret = NM_ACT_STAGE_RETURN_POSTPONE;
+			}
+
+			if (num_addresses) {
+				config = nm_device_ip4_config_new (self);
+				nm_ip4_config_merge_setting (config,
+				                             nm_connection_get_setting_ip4_config (connection),
+				                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
+				                             NM_SETTING_CONNECTION_LLMNR_DEFAULT,
+				                             nm_device_get_route_table (self, AF_INET, TRUE),
+				                             nm_device_get_route_metric (self, AF_INET));
+				configs = g_new0 (NMIP4Config *, 2);
+				configs[0] = config;
+				ipv4_dad_start (self, configs, ipv4_manual_method_apply);
+			}
+		} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
+			ret = ipv4ll_start (self);
+			if (ret == NM_ACT_STAGE_RETURN_FAILURE)
+				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
+		} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
+			if (out_config) {
+				*out_config = shared4_new_config (self, connection);
+				if (*out_config) {
+					priv->dnsmasq_manager = nm_dnsmasq_manager_new (nm_device_get_ip_iface (self));
+					ret = NM_ACT_STAGE_RETURN_SUCCESS;
+				} else {
+					NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+					ret = NM_ACT_STAGE_RETURN_FAILURE;
+				}
+			} else
+				g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE);
+		} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+			ret = NM_ACT_STAGE_RETURN_SUCCESS;
+		else
+			_LOGW (LOGD_IP4, "unhandled IPv4 config method '%s'; will fail", method);
+
+		return ret;
+	} else {
+		NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
+		const char *ip6_privacy_str = "0";
+
+		if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
+			if (   !priv->master
+			    && !nm_device_sys_iface_state_is_external (self)) {
+				gboolean ipv6ll_handle_old = priv->ipv6ll_handle;
+
+				/* When activating an IPv6 'ignore' connection we need to revert back
+				 * to kernel IPv6LL, but the kernel won't actually assign an address
+				 * to the interface until disable_ipv6 is bounced.
+				 */
+				set_nm_ipv6ll (self, FALSE);
+				if (ipv6ll_handle_old)
+					nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1");
+				restore_ip6_properties (self);
+			}
+			return NM_ACT_STAGE_RETURN_IP_DONE;
 		}
-		return NM_ACT_STAGE_RETURN_IP_DONE;
-	}
 
-	/* Ensure the MTU makes sense. If it was below 1280 the kernel would not
-	 * expose any ipv6 sysctls or allow presence of any addresses on the interface,
-	 * including LL, which * would make it impossible to autoconfigure MTU to a
-	 * correct value. */
-	_commit_mtu (self, priv->ip_config_4);
+		/* Ensure the MTU makes sense. If it was below 1280 the kernel would not
+		 * expose any ipv6 sysctls or allow presence of any addresses on the interface,
+		 * including LL, which * would make it impossible to autoconfigure MTU to a
+		 * correct value. */
+		_commit_mtu (self, priv->ip_config_4);
 
-	/* Any method past this point requires an IPv6LL address. Use NM-controlled
-	 * IPv6LL if this is not an assumed connection, since assumed connections
-	 * will already have IPv6 set up.
-	 */
-	if (!nm_device_sys_iface_state_is_external_or_assume (self))
-		set_nm_ipv6ll (self, TRUE);
+		/* Any method past this point requires an IPv6LL address. Use NM-controlled
+		 * IPv6LL if this is not an assumed connection, since assumed connections
+		 * will already have IPv6 set up.
+		 */
+		if (!nm_device_sys_iface_state_is_external_or_assume (self))
+			set_nm_ipv6ll (self, TRUE);
 
-	/* Re-enable IPv6 on the interface */
-	set_disable_ipv6 (self, "0");
+		/* Re-enable IPv6 on the interface */
+		set_disable_ipv6 (self, "0");
 
-	/* Synchronize external IPv6 configuration with kernel, since
-	 * linklocal6_start() uses the information there to determine if we can
-	 * proceed with the selected method (SLAAC, DHCP, link-local).
-	 */
-	nm_platform_process_events (nm_device_get_platform (self));
-	g_clear_object (&priv->ext_ip6_config_captured);
-	priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_multi_index (self),
-	                                                       nm_device_get_platform (self),
-	                                                       nm_device_get_ip_ifindex (self),
-	                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+		/* Synchronize external IPv6 configuration with kernel, since
+		 * linklocal6_start() uses the information there to determine if we can
+		 * proceed with the selected method (SLAAC, DHCP, link-local).
+		 */
+		nm_platform_process_events (nm_device_get_platform (self));
+		g_clear_object (&priv->ext_ip6_config_captured);
+		priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_multi_index (self),
+		                                                       nm_device_get_platform (self),
+		                                                       nm_device_get_ip_ifindex (self),
+		                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
 
-	ip6_privacy = _ip6_privacy_get (self);
+		ip6_privacy = _ip6_privacy_get (self);
 
-	if (   strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
-	    || strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0) {
-		if (!addrconf6_start (self, ip6_privacy)) {
-			/* IPv6 might be disabled; allow IPv4 to proceed */
-			ret = NM_ACT_STAGE_RETURN_IP_FAIL;
-		} else
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) {
-		ret = linklocal6_start (self)
-		      ? NM_ACT_STAGE_RETURN_SUCCESS
-		      : NM_ACT_STAGE_RETURN_POSTPONE;
-	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
-		priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_MANAGED;
-		if (!dhcp6_start (self, TRUE)) {
-			/* IPv6 might be disabled; allow IPv4 to proceed */
-			ret = NM_ACT_STAGE_RETURN_IP_FAIL;
-		} else
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL) == 0) {
-		ret = NM_ACT_STAGE_RETURN_SUCCESS;
-	} else
-		_LOGW (LOGD_IP6, "unhandled IPv6 config method '%s'; will fail", method);
-
-	if (   ret != NM_ACT_STAGE_RETURN_FAILURE
-	    && !nm_device_sys_iface_state_is_external_or_assume (self)) {
-		switch (ip6_privacy) {
-		case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN:
-		case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
-			ip6_privacy_str = "0";
-			break;
-		case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
-			ip6_privacy_str = "1";
-			break;
-		case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
-			ip6_privacy_str = "2";
-			break;
+		if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		                          NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
+			if (!addrconf6_start (self, ip6_privacy)) {
+				/* IPv6 might be disabled; allow IPv4 to proceed */
+				ret = NM_ACT_STAGE_RETURN_IP_FAIL;
+			} else
+				ret = NM_ACT_STAGE_RETURN_POSTPONE;
+		} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) {
+			ret = linklocal6_start (self)
+			      ? NM_ACT_STAGE_RETURN_SUCCESS
+			      : NM_ACT_STAGE_RETURN_POSTPONE;
+		} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
+			priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_MANAGED;
+			if (!dhcp6_start (self, TRUE)) {
+				/* IPv6 might be disabled; allow IPv4 to proceed */
+				ret = NM_ACT_STAGE_RETURN_IP_FAIL;
+			} else
+				ret = NM_ACT_STAGE_RETURN_POSTPONE;
+		} else if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
+			ret = NM_ACT_STAGE_RETURN_SUCCESS;
+		else
+			_LOGW (LOGD_IP6, "unhandled IPv6 config method '%s'; will fail", method);
+
+		if (   ret != NM_ACT_STAGE_RETURN_FAILURE
+		    && !nm_device_sys_iface_state_is_external_or_assume (self)) {
+			switch (ip6_privacy) {
+			case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN:
+			case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
+				ip6_privacy_str = "0";
+				break;
+			case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
+				ip6_privacy_str = "1";
+				break;
+			case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
+				ip6_privacy_str = "2";
+				break;
+			}
+			nm_device_sysctl_ip_conf_set (self, AF_INET6, "use_tempaddr", ip6_privacy_str);
 		}
-		nm_device_ipv6_sysctl_set (self, "use_tempaddr", ip6_privacy_str);
-	}
 
-	return ret;
+		return ret;
+	}
 }
 
 /**
@@ -9790,35 +9810,32 @@ nm_device_activate_stage3_ip4_start (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-	NMIP4Config *ip4_config = NULL;
+	gs_unref_object NMIP4Config *ip4_config = NULL;
 
-	g_assert (priv->ip4_state == IP_WAIT);
+	g_assert (priv->ip_state_4 == NM_DEVICE_IP_STATE_WAIT);
 
 	if (nm_device_sys_iface_state_is_external (self)) {
-		_set_ip_state (self, AF_INET, IP_DONE);
+		_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_DONE);
 		check_ip_state (self, FALSE, TRUE);
 		return TRUE;
 	}
 
-	_set_ip_state (self, AF_INET, IP_CONF);
-	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip4_config_start (self, &ip4_config, &failure_reason);
-	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
-		if (!ip4_config)
-			ip4_config = _ip4_config_new (self);
-		nm_device_activate_schedule_ip4_config_result (self, ip4_config);
-		g_object_unref (ip4_config);
-	} else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
-		_set_ip_state (self, AF_INET, IP_DONE);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_CONF);
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip_config_start (self, AF_INET, (gpointer *) &ip4_config, &failure_reason);
+	if (ret == NM_ACT_STAGE_RETURN_SUCCESS)
+		nm_device_activate_schedule_ip_config_result (self, AF_INET, NM_IP_CONFIG_CAST (ip4_config));
+	else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
+		_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_DONE);
 		check_ip_state (self, FALSE, TRUE);
 	} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
 		return FALSE;
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_FAIL) {
 		/* Activation not wanted */
-		_set_ip_state (self, AF_INET, IP_FAIL);
+		_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_FAIL);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_WAIT) {
 		/* Wait for something to try IP config again */
-		_set_ip_state (self, AF_INET, IP_WAIT);
+		_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_WAIT);
 	} else
 		g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE);
 
@@ -9839,37 +9856,37 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 	NMIP6Config *ip6_config = NULL;
 
-	g_assert (priv->ip6_state == IP_WAIT);
+	g_assert (priv->ip_state_6 == NM_DEVICE_IP_STATE_WAIT);
 
 	if (nm_device_sys_iface_state_is_external (self)) {
-		_set_ip_state (self, AF_INET6, IP_DONE);
+		_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
 		check_ip_state (self, FALSE, TRUE);
 		return TRUE;
 	}
 
-	_set_ip_state (self, AF_INET6, IP_CONF);
-	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip6_config_start (self, &ip6_config, &failure_reason);
+	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip_config_start (self, AF_INET6, (gpointer *) &ip6_config, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
 		if (!ip6_config)
-			ip6_config = _ip6_config_new (self);
+			ip6_config = nm_device_ip6_config_new (self);
 		/* Here we get a static IPv6 config, like for Shared where it's
 		 * autogenerated or from modems where it comes from ModemManager.
 		 */
 		nm_assert (!applied_config_get_current (&priv->ac_ip6_config));
 		applied_config_init (&priv->ac_ip6_config, ip6_config);
-		nm_device_activate_schedule_ip6_config_result (self);
+		nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
-		_set_ip_state (self, AF_INET6, IP_DONE);
+		_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
 		check_ip_state (self, FALSE, TRUE);
 	} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
 		return FALSE;
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_FAIL) {
 		/* Activation not wanted */
-		_set_ip_state (self, AF_INET6, IP_FAIL);
+		_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_FAIL);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_WAIT) {
 		/* Wait for something to try IP config again */
-		_set_ip_state (self, AF_INET6, IP_WAIT);
+		_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
 	} else
 		g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE);
 
@@ -9885,8 +9902,10 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 static void
 activate_stage3_ip_config_start (NMDevice *self)
 {
-	_set_ip_state (self, AF_INET, IP_WAIT);
-	_set_ip_state (self, AF_INET6, IP_WAIT);
+	int ifindex;
+
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_WAIT);
+	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
 
 	_active_connection_set_state_flags (self,
 	                                    NM_ACTIVATION_STATE_FLAG_LAYER2_READY);
@@ -9894,7 +9913,8 @@ activate_stage3_ip_config_start (NMDevice *self)
 	nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
 	/* Device should be up before we can do anything with it */
-	if (!nm_platform_link_is_up (nm_device_get_platform (self), nm_device_get_ip_ifindex (self)))
+	if (   (ifindex = nm_device_get_ip_ifindex (self)) > 0
+	    && !nm_platform_link_is_up (nm_device_get_platform (self), ifindex))
 		_LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", nm_device_get_ip_iface (self));
 
 	/* IPv4 */
@@ -9940,7 +9960,7 @@ fw_change_zone_cb (NMFirewallManager *firewall_manager,
 		break;
 	case FIREWALL_STATE_WAIT_IP_CONFIG:
 		priv->fw_state = FIREWALL_STATE_INITIALIZED;
-		if (priv->ip4_state == IP_DONE || priv->ip6_state == IP_DONE)
+		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE || priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE)
 			nm_device_start_ip_check (self);
 		break;
 	case FIREWALL_STATE_INITIALIZED:
@@ -10017,15 +10037,21 @@ nm_device_activate_schedule_stage3_ip_config_start (NMDevice *self)
 }
 
 static NMActStageReturn
-act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *out_failure_reason)
+act_stage4_ip_config_timeout (NMDevice *self,
+                              int addr_family,
+                              NMDeviceStateReason *out_failure_reason)
 {
-	if (!get_ip_config_may_fail (self, AF_INET)) {
+	nm_assert_addr_family (addr_family);
+
+	if (!get_ip_config_may_fail (self, addr_family)) {
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
+
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
+
 /*
  * nm_device_activate_stage4_ip4_config_timeout
  *
@@ -10033,12 +10059,12 @@ act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *out_failure_
  *
  */
 static void
-activate_stage4_ip4_config_timeout (NMDevice *self)
+activate_stage4_ip_config_timeout_4 (NMDevice *self)
 {
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip4_config_timeout (self, &failure_reason);
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip_config_timeout (self, AF_INET6, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
 		return;
 	else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
@@ -10047,54 +10073,35 @@ activate_stage4_ip4_config_timeout (NMDevice *self)
 	}
 	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 
-	_set_ip_state (self, AF_INET, IP_FAIL);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_FAIL);
 
 	check_ip_state (self, FALSE, TRUE);
 }
 
-/*
- * nm_device_activate_schedule_ip4_config_timeout
- *
- * Deal with a timeout of the IPv4 configuration
- *
- */
 void
-nm_device_activate_schedule_ip4_config_timeout (NMDevice *self)
+nm_device_activate_schedule_ip_config_timeout (NMDevice *self,
+                                               int addr_family)
 {
 	NMDevicePrivate *priv;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request.obj);
 
-	activation_source_schedule (self, activate_stage4_ip4_config_timeout, AF_INET);
-}
-
-static NMActStageReturn
-act_stage4_ip6_config_timeout (NMDevice *self, NMDeviceStateReason *out_failure_reason)
-{
-	if (!get_ip_config_may_fail (self, AF_INET6)) {
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-		return NM_ACT_STAGE_RETURN_FAILURE;
-	}
+	g_return_if_fail (priv->act_request.obj);
 
-	return NM_ACT_STAGE_RETURN_SUCCESS;
+	activation_source_schedule (self, activate_stage4_ip_config_timeout_x[IS_IPv4], addr_family);
 }
 
-/*
- * activate_stage4_ip6_config_timeout
- *
- * Time out on retrieving the IPv6 config.
- *
- */
 static void
-activate_stage4_ip6_config_timeout (NMDevice *self)
+activate_stage4_ip_config_timeout_6 (NMDevice *self)
 {
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip6_config_timeout (self, &failure_reason);
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip_config_timeout (self, AF_INET6, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
 		return;
 	if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
@@ -10103,30 +10110,11 @@ activate_stage4_ip6_config_timeout (NMDevice *self)
 	}
 	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 
-	_set_ip_state (self, AF_INET6, IP_FAIL);
+	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_FAIL);
 
 	check_ip_state (self, FALSE, TRUE);
 }
 
-/*
- * nm_device_activate_schedule_ip6_config_timeout
- *
- * Deal with a timeout of the IPv6 configuration
- *
- */
-void
-nm_device_activate_schedule_ip6_config_timeout (NMDevice *self)
-{
-	NMDevicePrivate *priv;
-
-	g_return_if_fail (NM_IS_DEVICE (self));
-
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request.obj);
-
-	activation_source_schedule (self, activate_stage4_ip6_config_timeout, AF_INET6);
-}
-
 static gboolean
 share_init (NMDevice *self, GError **error)
 {
@@ -10141,9 +10129,9 @@ share_init (NMDevice *self, GError **error)
 	} else if (!nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv4/ip_forward"), "1")) {
 		errsv = errno;
 		_LOGD (LOGD_SHARING, "share: error enabling IPv4 forwarding: (%d) %s",
-		       errsv, g_strerror (errsv));
+		       errsv, nm_strerror_native (errsv));
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "cannot set ipv4/ip_forward: %s", g_strerror (errsv));
+		             "cannot set ipv4/ip_forward: %s", nm_strerror_native (errsv));
 		return FALSE;
 	}
 
@@ -10152,7 +10140,7 @@ share_init (NMDevice *self, GError **error)
 	} else if (!nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv4/ip_dynaddr"), "1")) {
 		errsv = errno;
 		_LOGD (LOGD_SHARING, "share: error enabling dynamic addresses: (%d) %s",
-		       errsv, strerror (errsv));
+		       errsv, nm_strerror_native (errsv));
 	}
 
 	for (iter = modules; *iter; iter++)
@@ -10179,6 +10167,9 @@ start_sharing (NMDevice *self, NMIP4Config *config, GError **error)
 	const NMPlatformIP4Address *ip4_addr = NULL;
 	const char *ip_iface;
 	GError *local = NULL;
+	NMConnection *conn;
+	NMSettingConnection *s_con;
+	gboolean announce_android_metered;
 
 	g_return_val_if_fail (config, FALSE);
 
@@ -10200,7 +10191,7 @@ start_sharing (NMDevice *self, NMIP4Config *config, GError **error)
 		return FALSE;
 
 	req = nm_device_get_act_request (self);
-	g_assert (req);
+	g_return_val_if_fail (req, FALSE);
 
 	netmask = _nm_utils_ip4_prefix_to_netmask (ip4_addr->plen);
 	nm_utils_inet4_ntop (netmask, str_mask);
@@ -10221,7 +10212,35 @@ start_sharing (NMDevice *self, NMIP4Config *config, GError **error)
 
 	nm_act_request_set_shared (req, TRUE);
 
-	if (!nm_dnsmasq_manager_start (priv->dnsmasq_manager, config, &local)) {
+	conn = nm_act_request_get_applied_connection (req);
+	s_con = nm_connection_get_setting_connection (conn);
+
+	switch (nm_setting_connection_get_metered (s_con)) {
+	case NM_METERED_YES:
+		/* honor the metered flag. Note that reapply on the device does not affect
+		 * the metered setting. This is different from other profiles, where the
+		 * metered flag of an activated profile can be changed (reapplied). */
+		announce_android_metered = TRUE;
+		break;
+	case NM_METERED_UNKNOWN:
+		/* we pick up the current value and announce it. But again, we cannot update
+		 * the announced setting without restarting dnsmasq. That means, if the default
+		 * route changes w.r.t. being metered, then the shared connection does not get
+		 * updated before reactivating. */
+		announce_android_metered = NM_IN_SET (nm_manager_get_metered (nm_manager_get ()),
+		                                      NM_METERED_YES,
+		                                      NM_METERED_GUESS_YES);
+		break;
+	default:
+		announce_android_metered = FALSE;
+		break;
+	}
+
+
+	if (!nm_dnsmasq_manager_start (priv->dnsmasq_manager,
+	                               config,
+	                               announce_android_metered,
+	                               &local)) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "could not start dnsmasq due to %s", local->message);
 		g_error_free (local);
@@ -10240,10 +10259,7 @@ arp_cleanup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->acd.announcing) {
-		nm_acd_manager_destroy (priv->acd.announcing);
-		priv->acd.announcing = NULL;
-	}
+	nm_clear_pointer (&priv->acd.announcing, nm_acd_manager_free);
 }
 
 void
@@ -10278,7 +10294,11 @@ nm_device_arp_announce (NMDevice *self)
 	if (num == 0)
 		return;
 
-	priv->acd.announcing = nm_acd_manager_new (nm_device_get_ip_ifindex (self), hw_addr, hw_addr_len);
+	priv->acd.announcing = nm_acd_manager_new (nm_device_get_ip_ifindex (self),
+	                                           hw_addr,
+	                                           hw_addr_len,
+	                                           NULL,
+	                                           NULL);
 
 	for (i = 0; i < num; i++) {
 		NMIPAddress *ip = nm_setting_ip_config_get_address (s_ip4, i);
@@ -10294,18 +10314,16 @@ nm_device_arp_announce (NMDevice *self)
 }
 
 static void
-activate_stage5_ip4_config_result (NMDevice *self)
+activate_stage5_ip_config_result_4 (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActRequest *req;
 	const char *method;
-	NMConnection *connection;
 	int ip_ifindex;
+	gboolean do_announce = FALSE;
 
 	req = nm_device_get_act_request (self);
 	g_assert (req);
-	connection = nm_act_request_get_applied_connection (req);
-	g_assert (connection);
 
 	/* Interface must be IFF_UP before IP config can be applied */
 	ip_ifindex = nm_device_get_ip_ifindex (self);
@@ -10322,9 +10340,8 @@ activate_stage5_ip4_config_result (NMDevice *self)
 	}
 
 	/* Start IPv4 sharing if we need it */
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) {
+	method = nm_device_get_effective_ip_config_method (self, AF_INET);
+	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
 		gs_free_error GError *error = NULL;
 
 		if (!start_sharing (self, priv->ip_config_4, &error)) {
@@ -10346,45 +10363,77 @@ activate_stage5_ip4_config_result (NMDevice *self)
 		                           NULL, NULL, NULL);
 	}
 
-	nm_device_arp_announce (self);
+	/* Send ARP announcements */
+
+	if (nm_device_is_master (self)) {
+		CList *iter;
+		SlaveInfo *info;
+
+		/* Skip announcement if there are no device enslaved, for two reasons:
+		 * 1) the master has a temporary MAC address until the first slave comes
+		 * 2) announcements are going to be dropped anyway without slaves
+		 */
+		do_announce = FALSE;
+
+		c_list_for_each (iter, &priv->slaves) {
+			info = c_list_entry (iter, SlaveInfo, lst_slave);
+			if (info->slave_is_enslaved) {
+				do_announce = TRUE;
+				break;
+			}
+		}
+	} else
+		do_announce = TRUE;
+
+	if (do_announce)
+		nm_device_arp_announce (self);
+
 	nm_device_remove_pending_action (self, NM_PENDING_ACTION_DHCP4, FALSE);
 
 	/* Enter the IP_CHECK state if this is the first method to complete */
-	_set_ip_state (self, AF_INET, IP_DONE);
+	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_DONE);
 	check_ip_state (self, FALSE, TRUE);
 }
 
 void
-nm_device_activate_schedule_ip4_config_result (NMDevice *self, NMIP4Config *config)
+nm_device_activate_schedule_ip_config_result (NMDevice *self,
+                                              int addr_family,
+                                              NMIPConfig *config)
 {
 	NMDevicePrivate *priv;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
 	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	g_return_if_fail (   !config
+	                  || (   addr_family == AF_INET
+	                      && nm_ip_config_get_addr_family (config) == AF_INET));
+
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	applied_config_init (&priv->dev_ip4_config, config);
-	activation_source_schedule (self, activate_stage5_ip4_config_result, AF_INET);
-}
+	if (IS_IPv4) {
+		applied_config_init (&priv->dev_ip_config_4, config);
+	} else {
+		/* If IP had previously failed, move it back to NM_DEVICE_IP_STATE_CONF since we
+		 * clearly now have configuration.
+		 */
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
+			_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
+	}
 
-gboolean
-nm_device_activate_ip4_state_in_conf (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_CONF;
+	activation_source_schedule (self, activate_stage5_ip_config_result_x[IS_IPv4], addr_family);
 }
 
-gboolean
-nm_device_activate_ip4_state_in_wait (NMDevice *self)
+NMDeviceIPState
+nm_device_activate_get_ip_state (NMDevice *self,
+                                 int addr_family)
 {
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_WAIT;
-}
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-gboolean
-nm_device_activate_ip4_state_done (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_DONE;
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_IP_STATE_NONE);
+	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), NM_DEVICE_IP_STATE_NONE);
+
+	return NM_DEVICE_GET_PRIVATE (self)->ip_state_x[IS_IPv4];
 }
 
 static void
@@ -10407,7 +10456,7 @@ dad6_add_pending_address (NMDevice *self,
 		       nm_platform_ip6_address_to_string (pl_addr, NULL, 0));
 
 		if (!*dad6_config)
-			*dad6_config = _ip6_config_new (self);
+			*dad6_config = nm_device_ip6_config_new (self);
 
 		nm_ip6_config_add_address (*dad6_config, pl_addr);
 	}
@@ -10424,7 +10473,7 @@ dad6_get_pending_addresses (NMDevice *self)
 	NMIP6Config *confs[] = { (NMIP6Config *) applied_config_get_current (&priv->ac_ip6_config),
 	                         (NMIP6Config *) applied_config_get_current (&priv->dhcp6.ip6_config),
 	                         priv->con_ip_config_6,
-	                         (NMIP6Config *) applied_config_get_current (&priv->wwan_ip_config_6) };
+	                         (NMIP6Config *) applied_config_get_current (&priv->dev2_ip_config_6) };
 	const NMPlatformIP6Address *addr;
 	NMIP6Config *dad6_config = NULL;
 	NMDedupMultiIter ipconf_iter;
@@ -10465,19 +10514,16 @@ dad6_get_pending_addresses (NMDevice *self)
 }
 
 static void
-activate_stage5_ip6_config_commit (NMDevice *self)
+activate_stage5_ip_config_result_6 (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActRequest *req;
 	const char *method;
-	NMConnection *connection;
 	int ip_ifindex;
 	int errsv;
 
 	req = nm_device_get_act_request (self);
 	g_assert (req);
-	connection = nm_act_request_get_applied_connection (req);
-	g_assert (connection);
 
 	/* Interface must be IFF_UP before IP config can be applied */
 	ip_ifindex = nm_device_get_ip_ifindex (self);
@@ -10491,7 +10537,7 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 
 	if (ip_config_merge_and_apply (self, AF_INET6, TRUE)) {
 		if (   priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE
-		    && priv->ip6_state == IP_CONF) {
+		    && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
 			if (applied_config_get_current (&priv->dhcp6.ip6_config)) {
 				/* If IPv6 wasn't the first IP to complete, and DHCP was used,
 				 * then ensure dispatcher scripts get the DHCP lease information.
@@ -10509,18 +10555,17 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 		nm_device_remove_pending_action (self, NM_PENDING_ACTION_AUTOCONF6, FALSE);
 
 		/* Start IPv6 forwarding if we need it */
-		method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-
-		if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED) == 0) {
+		method = nm_device_get_effective_ip_config_method (self, AF_INET6);
+		if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
 			if (!nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/sys/net/ipv6/conf/all/forwarding"), "1")) {
 				errsv = errno;
-				_LOGE (LOGD_SHARING, "share: error enabling IPv6 forwarding: (%d) %s", errsv, strerror (errsv));
+				_LOGE (LOGD_SHARING, "share: error enabling IPv6 forwarding: (%d) %s", errsv, nm_strerror_native (errsv));
 				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
 			}
 		}
 
 		/* Check if we have to wait for DAD */
-		if (priv->ip6_state == IP_CONF && !priv->dad6_ip6_config) {
+		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF && !priv->dad6_ip6_config) {
 			if (!priv->carrier && priv->ignore_carrier && get_ip_config_may_fail (self, AF_INET6))
 				_LOGI (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: carrier missing and ignored, not delaying activation");
 			else
@@ -10529,7 +10574,7 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 			if (priv->dad6_ip6_config) {
 				_LOGD (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: awaiting termination");
 			} else {
-				_set_ip_state (self, AF_INET6, IP_DONE);
+				_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
 				check_ip_state (self, FALSE, TRUE);
 			}
 		}
@@ -10539,43 +10584,6 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 	}
 }
 
-void
-nm_device_activate_schedule_ip6_config_result (NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	g_return_if_fail (NM_IS_DEVICE (self));
-
-	/* If IP had previously failed, move it back to IP_CONF since we
-	 * clearly now have configuration.
-	 */
-	if (priv->ip6_state == IP_FAIL)
-		_set_ip_state (self, AF_INET6, IP_CONF);
-
-	activation_source_schedule (self, activate_stage5_ip6_config_commit, AF_INET6);
-}
-
-gboolean
-nm_device_activate_ip6_state_in_conf (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_CONF;
-}
-
-gboolean
-nm_device_activate_ip6_state_in_wait (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_WAIT;
-}
-
-gboolean
-nm_device_activate_ip6_state_done (NMDevice *self)
-{
-	g_return_val_if_fail (self != NULL, FALSE);
-	return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_DONE;
-}
-
 /*****************************************************************************/
 
 static void
@@ -10741,7 +10749,7 @@ _cleanup_ip_pre (NMDevice *self, int addr_family, CleanupType cleanup_type)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-	_set_ip_state (self, addr_family, IP_NONE);
+	_set_ip_state (self, addr_family, NM_DEVICE_IP_STATE_NONE);
 
 	if (nm_clear_g_source (&priv->queued_ip_config_id_x[IS_IPv4])) {
 		_LOGD (LOGD_DEVICE, "clearing queued IP%c config change",
@@ -10753,7 +10761,7 @@ _cleanup_ip_pre (NMDevice *self, int addr_family, CleanupType cleanup_type)
 		arp_cleanup (self);
 		dnsmasq_cleanup (self);
 		ipv4ll_cleanup (self);
-		g_slist_free_full (priv->acd.dad_list, (GDestroyNotify) nm_acd_manager_destroy);
+		g_slist_free_full (priv->acd.dad_list, (GDestroyNotify) nm_acd_manager_free);
 		priv->acd.dad_list = NULL;
 	} else {
 		g_slist_free_full (priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
@@ -10766,8 +10774,10 @@ _cleanup_ip_pre (NMDevice *self, int addr_family, CleanupType cleanup_type)
 }
 
 gboolean
-_nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
-                                    GError **error, const char **whitelist)
+_nm_device_hash_check_invalid_keys (GHashTable *hash,
+                                    const char *setting_name,
+                                    GError **error,
+                                    const char *const*whitelist)
 {
 	guint found_whitelisted_keys = 0;
 	guint i;
@@ -10841,12 +10851,12 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 	g_return_if_fail (NM_IS_DEVICE (self));
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->ip4_state != IP_NONE) {
+	if (priv->ip_state_4 != NM_DEVICE_IP_STATE_NONE) {
 		g_clear_object (&priv->con_ip_config_4);
 		g_clear_object (&priv->ext_ip_config_4);
-		g_clear_object (&priv->dev_ip4_config.current);
-		g_clear_object (&priv->wwan_ip_config_4.current);
-		priv->con_ip_config_4 = _ip4_config_new (self);
+		g_clear_object (&priv->dev_ip_config_4.current);
+		g_clear_object (&priv->dev2_ip_config_4.current);
+		priv->con_ip_config_4 = nm_device_ip4_config_new (self);
 		nm_ip4_config_merge_setting (priv->con_ip_config_4,
 		                             s_ip4_new,
 		                             _get_mdns (self),
@@ -10863,7 +10873,7 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 
 		if (!nm_streq0 (method_old, method_new)) {
 			_cleanup_ip_pre (self, AF_INET, CLEANUP_TYPE_DECONFIGURE);
-			_set_ip_state (self, AF_INET, IP_WAIT);
+			_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_WAIT);
 			if (!nm_device_activate_stage3_ip4_start (self))
 				_LOGW (LOGD_IP4, "Failed to apply IPv4 configuration");
 			return;
@@ -10882,12 +10892,12 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 			metric_new = nm_setting_ip_config_get_route_metric (s_ip4_new);
 
 			if (metric_old != metric_new) {
-				if (priv->dev_ip4_config.orig) {
-					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->dev_ip4_config.orig,
+				if (priv->dev_ip_config_4.orig) {
+					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->dev_ip_config_4.orig,
 					                                    nm_device_get_route_metric (self, AF_INET));
 				}
-				if (priv->wwan_ip_config_4.orig) {
-					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->wwan_ip_config_4.orig,
+				if (priv->dev2_ip_config_4.orig) {
+					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->dev2_ip_config_4.orig,
 					                                    nm_device_get_route_metric (self, AF_INET));
 				}
 				if (priv->dhcp4.client) {
@@ -10913,16 +10923,16 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 	g_return_if_fail (NM_IS_DEVICE (self));
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->ip6_state != IP_NONE) {
+	if (priv->ip_state_6 != NM_DEVICE_IP_STATE_NONE) {
 		g_clear_object (&priv->con_ip_config_6);
 		g_clear_object (&priv->ext_ip_config_6);
 		g_clear_object (&priv->ac_ip6_config.current);
 		g_clear_object (&priv->dhcp6.ip6_config.current);
-		g_clear_object (&priv->wwan_ip_config_6.current);
+		g_clear_object (&priv->dev2_ip_config_6.current);
 		if (   priv->ipv6ll_handle
 		    && !IN6_IS_ADDR_UNSPECIFIED (&priv->ipv6ll_addr))
 			priv->ipv6ll_has = TRUE;
-		priv->con_ip_config_6 = _ip6_config_new (self);
+		priv->con_ip_config_6 = nm_device_ip6_config_new (self);
 		nm_ip6_config_merge_setting (priv->con_ip_config_6,
 		                             s_ip6_new,
 		                             nm_device_get_route_table (self, AF_INET6, TRUE),
@@ -10937,7 +10947,7 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 
 		if (!nm_streq0 (method_old, method_new)) {
 			_cleanup_ip_pre (self, AF_INET6, CLEANUP_TYPE_DECONFIGURE);
-			_set_ip_state (self, AF_INET6, IP_WAIT);
+			_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
 			if (!nm_device_activate_stage3_ip6_start (self))
 				_LOGW (LOGD_IP6, "Failed to apply IPv6 configuration");
 			return;
@@ -10946,7 +10956,7 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 		if (s_ip6_old && s_ip6_new) {
 			gint64 metric_old, metric_new;
 
-			/* See comment in nm_device_reactivate_ip6_config() */
+			/* See comment in nm_device_reactivate_ip4_config() */
 			metric_old = nm_setting_ip_config_get_route_metric (s_ip6_old);
 			metric_new = nm_setting_ip_config_get_route_metric (s_ip6_new);
 
@@ -10959,8 +10969,8 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->dhcp6.ip6_config.orig,
 					                                    nm_device_get_route_metric (self, AF_INET6));
 				}
-				if (priv->wwan_ip_config_6.orig) {
-					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->wwan_ip_config_6.orig,
+				if (priv->dev2_ip_config_6.orig) {
+					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->dev2_ip_config_6.orig,
 					                                    nm_device_get_route_metric (self, AF_INET6));
 				}
 				if (priv->dhcp6.client) {
@@ -11070,7 +11080,6 @@ can_reapply_change (NMDevice *self, const char *setting_name,
 static void
 reapply_connection (NMDevice *self, NMConnection *con_old, NMConnection *con_new)
 {
-
 }
 
 /* check_and_reapply_connection:
@@ -11469,7 +11478,7 @@ _rt6_temporary_not_available_timeout (gpointer user_data)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	priv->rt6_temporary_not_available_id = 0;
-	nm_device_activate_schedule_ip6_config_result (self);
+	nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 
 	return G_SOURCE_REMOVE;
 }
@@ -11583,14 +11592,15 @@ disconnect_cb (NMDevice *self,
 }
 
 static void
-_clear_queued_act_request (NMDevicePrivate *priv)
+_clear_queued_act_request (NMDevicePrivate *priv,
+                           NMActiveConnectionStateReason active_reason)
 {
 	if (priv->queued_act_request) {
 		gs_unref_object NMActRequest *ac = NULL;
 
 		ac = g_steal_pointer (&priv->queued_act_request);
 		nm_active_connection_set_state_fail ((NMActiveConnection *) ac,
-		                                     NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED,
+		                                     active_reason,
 		                                     NULL);
 	}
 }
@@ -11732,7 +11742,8 @@ _carrier_wait_check_queued_act_request (NMDevice *self)
 	priv->queued_act_request_is_waiting_for_carrier = FALSE;
 	if (!priv->carrier) {
 		_LOGD (LOGD_DEVICE, "Cancel queued activation request as we have no carrier after timeout");
-		_clear_queued_act_request (priv);
+		_clear_queued_act_request (priv,
+		                           NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
 	} else {
 		gs_unref_object NMActRequest *queued_req = NULL;
 
@@ -11787,7 +11798,9 @@ _carrier_wait_check_act_request_must_queue (NMDevice *self, NMActRequest *req)
 }
 
 void
-nm_device_disconnect_active_connection (NMActiveConnection *active)
+nm_device_disconnect_active_connection (NMActiveConnection *active,
+                                        NMDeviceStateReason device_reason,
+                                        NMActiveConnectionStateReason active_reason)
 {
 	NMDevice *self;
 	NMDevicePrivate *priv;
@@ -11795,38 +11808,59 @@ nm_device_disconnect_active_connection (NMActiveConnection *active)
 	g_return_if_fail (NM_IS_ACTIVE_CONNECTION (active));
 
 	self = nm_active_connection_get_device (active);
-
 	if (!self) {
 		/* hm, no device? Just fail the active connection. */
-		nm_active_connection_set_state_fail (active,
-		                                     NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
-		                                     NULL);
-		return;
+		goto do_fail;
 	}
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (NM_ACTIVE_CONNECTION (priv->queued_act_request) == active) {
-		_clear_queued_act_request (priv);
+		_clear_queued_act_request (priv, active_reason);
 		return;
 	}
+
 	if (NM_ACTIVE_CONNECTION (priv->act_request.obj) == active) {
 		if (priv->state < NM_DEVICE_STATE_DEACTIVATING) {
 			nm_device_state_changed (self,
 			                         NM_DEVICE_STATE_DEACTIVATING,
-			                         NM_DEVICE_STATE_REASON_NEW_ACTIVATION);
+			                         device_reason);
 		} else {
-			/* it's going down already... */
+			/* @active is the current ac of @self, but it's going down already.
+			 * Nothing to do. */
 		}
+		return;
 	}
+
+	/* the active connection references this device, but it's neither the
+	 * queued_act_request nor the current act_request. Just set it to fail... */
+do_fail:
+	nm_active_connection_set_state_fail (active,
+	                                     active_reason,
+	                                     NULL);
 }
 
 void
 nm_device_queue_activation (NMDevice *self, NMActRequest *req)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
 	gboolean must_queue;
 
+	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	nm_keep_alive_arm (nm_active_connection_get_keep_alive (NM_ACTIVE_CONNECTION (req)));
+
+	if (nm_active_connection_get_state (NM_ACTIVE_CONNECTION (req)) >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
+		/* it's already deactivating. Nothing to do. */
+		nm_assert (NM_IN_SET (nm_active_connection_get_device (NM_ACTIVE_CONNECTION (req)), NULL, self));
+		return;
+	}
+
+	nm_assert (self == nm_active_connection_get_device (NM_ACTIVE_CONNECTION (req)));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
 	must_queue = _carrier_wait_check_act_request_must_queue (self, req);
 
 	if (   !priv->act_request.obj
@@ -11836,8 +11870,9 @@ nm_device_queue_activation (NMDevice *self, NMActRequest *req)
 		return;
 	}
 
-	/* supercede any already-queued request */
-	_clear_queued_act_request (priv);
+	/* supersede any already-queued request */
+	_clear_queued_act_request (priv,
+	                           NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
 	priv->queued_act_request = g_object_ref (req);
 	priv->queued_act_request_is_waiting_for_carrier = must_queue;
 
@@ -12027,16 +12062,11 @@ nm_device_set_ip_config (NMDevice *self,
 		       nm_dbus_object_get_path (NM_DBUS_OBJECT (old_config)));
 		if (IS_IPv4) {
 			/* Device config is invalid if combined config is invalid */
-			applied_config_clear (&priv->dev_ip4_config);
+			applied_config_clear (&priv->dev_ip_config_4);
 		} else
 			priv->needs_ip6_subnet = FALSE;
 	}
 
-	if (IS_IPv4 && FALSE /* rp_filter handling is disabled */) {
-		if (!nm_device_sys_iface_state_is_external_or_assume (self))
-			ip4_rp_filter_update (self);
-	}
-
 	if (has_changes) {
 
 		if (IS_IPv4)
@@ -12133,13 +12163,25 @@ nm_device_replace_vpn4_config (NMDevice *self, NMIP4Config *old, NMIP4Config *co
 }
 
 void
-nm_device_set_wwan_ip4_config (NMDevice *self, NMIP4Config *config)
+nm_device_set_dev2_ip_config (NMDevice *self,
+                              int addr_family,
+                              NMIPConfig *config)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-	applied_config_init (&priv->wwan_ip_config_4, config);
-	if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
-		_LOGW (LOGD_IP4, "failed to set WWAN IPv4 configuration");
+	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	g_return_if_fail (   !config
+	                  || nm_ip_config_get_addr_family (config) == addr_family);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	applied_config_init (&priv->dev2_ip_config_x[IS_IPv4], config);
+	if (!ip_config_merge_and_apply (self, addr_family, TRUE)) {
+		_LOGW (LOGD_IP, "failed to set extra device IPv%c configuration",
+		       nm_utils_addr_family_to_char (addr_family));
+	}
 }
 
 void
@@ -12160,16 +12202,6 @@ nm_device_replace_vpn6_config (NMDevice *self, NMIP6Config *old, NMIP6Config *co
 		_LOGW (LOGD_IP6, "failed to set VPN routes for device");
 }
 
-void
-nm_device_set_wwan_ip6_config (NMDevice *self, NMIP6Config *config)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	applied_config_init (&priv->wwan_ip_config_6, config);
-	if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
-		_LOGW (LOGD_IP6, "failed to set WWAN IPv6 configuration");
-}
-
 NMDhcp6Config *
 nm_device_get_dhcp6_config (NMDevice *self)
 {
@@ -12404,7 +12436,7 @@ nm_device_start_ip_check (NMDevice *self)
 	g_return_if_fail (!priv->gw_ping.watch);
 	g_return_if_fail (!priv->gw_ping.timeout);
 	g_return_if_fail (!priv->gw_ping.pid);
-	g_return_if_fail (priv->ip4_state == IP_DONE || priv->ip6_state == IP_DONE);
+	g_return_if_fail (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE || priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE);
 
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
@@ -12417,14 +12449,14 @@ nm_device_start_ip_check (NMDevice *self)
 	if (timeout) {
 		const NMPObject *gw;
 
-		if (priv->ip_config_4 && priv->ip4_state == IP_DONE) {
+		if (priv->ip_config_4 && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 			gw = nm_ip4_config_best_default_route_get (priv->ip_config_4);
 			if (gw) {
 				nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (gw)->gateway, buf);
 				ping_binary = nm_utils_find_helper ("ping", "/usr/bin/ping", NULL);
 				log_domain = LOGD_IP4;
 			}
-		} else if (priv->ip_config_6 && priv->ip6_state == IP_DONE) {
+		} else if (priv->ip_config_6 && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 			gw = nm_ip6_config_best_default_route_get (priv->ip_config_6);
 			if (gw) {
 				nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (gw)->gateway, buf);
@@ -12564,11 +12596,11 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 	_update_ip4_address (self);
 
 	/* when the link comes up, we must restore IP configuration if necessary. */
-	if (priv->ip4_state == IP_DONE) {
+	if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 		if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
 			_LOGW (LOGD_IP4, "failed applying IP4 config after bringing link up");
 	}
-	if (priv->ip6_state == IP_DONE) {
+	if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 		if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
 			_LOGW (LOGD_IP6, "failed applying IP6 config after bringing link up");
 	}
@@ -12637,6 +12669,7 @@ nm_device_get_firmware_missing (NMDevice *self)
 static void
 intersect_ext_config (NMDevice *self,
                       AppliedConfig *config,
+                      gboolean intersect_addresses,
                       gboolean intersect_routes)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -12653,11 +12686,16 @@ intersect_ext_config (NMDevice *self,
 	      ? (NMIPConfig *) priv->ext_ip_config_4
 	      : (NMIPConfig *) priv->ext_ip_config_6;
 
-	if (config->current)
-		nm_ip_config_intersect (config->current, ext, intersect_routes, penalty);
-	else {
+	if (config->current) {
+		nm_ip_config_intersect (config->current,
+		                        ext,
+		                        intersect_addresses,
+		                        intersect_routes,
+		                        penalty);
+	} else {
 		config->current = nm_ip_config_intersect_alloc (config->orig,
 		                                                ext,
+		                                                intersect_addresses,
 		                                                intersect_routes,
 		                                                penalty);
 	}
@@ -12693,15 +12731,16 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				 * by the user. */
 				if (priv->con_ip_config_4) {
 					nm_ip4_config_intersect (priv->con_ip_config_4, priv->ext_ip_config_4,
+					                         TRUE,
 					                         is_up,
 					                         default_route_metric_penalty_get (self, AF_INET));
 				}
 
-				intersect_ext_config (self, &priv->dev_ip4_config, is_up);
-				intersect_ext_config (self, &priv->wwan_ip_config_4, is_up);
+				intersect_ext_config (self, &priv->dev_ip_config_4, TRUE, is_up);
+				intersect_ext_config (self, &priv->dev2_ip_config_4, TRUE, is_up);
 
 				for (iter = priv->vpn_configs_4; iter; iter = iter->next)
-					nm_ip4_config_intersect (iter->data, priv->ext_ip_config_4, is_up, 0);
+					nm_ip4_config_intersect (iter->data, priv->ext_ip_config_4, TRUE, is_up, 0);
 			}
 
 			/* Remove parts from ext_ip_config_4 to only contain the information that
@@ -12711,14 +12750,14 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				nm_ip4_config_subtract (priv->ext_ip_config_4, priv->con_ip_config_4,
 				                        default_route_metric_penalty_get (self, AF_INET));
 			}
-			if (applied_config_get_current (&priv->dev_ip4_config)) {
+			if (applied_config_get_current (&priv->dev_ip_config_4)) {
 				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_4,
-				                       applied_config_get_current (&priv->dev_ip4_config),
+				                       applied_config_get_current (&priv->dev_ip_config_4),
 				                       default_route_metric_penalty_get (self, AF_INET));
 			}
-			if (applied_config_get_current (&priv->wwan_ip_config_4)) {
+			if (applied_config_get_current (&priv->dev2_ip_config_4)) {
 				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_4,
-				                       applied_config_get_current (&priv->wwan_ip_config_4),
+				                       applied_config_get_current (&priv->dev2_ip_config_4),
 				                       default_route_metric_penalty_get (self, AF_INET));
 			}
 			for (iter = priv->vpn_configs_4; iter; iter = iter->next)
@@ -12746,15 +12785,16 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				if (priv->con_ip_config_6) {
 					nm_ip6_config_intersect (priv->con_ip_config_6, priv->ext_ip_config_6,
 					                         is_up,
+					                         is_up,
 					                         default_route_metric_penalty_get (self, AF_INET6));
 				}
 
-				intersect_ext_config (self, &priv->ac_ip6_config, is_up);
-				intersect_ext_config (self, &priv->dhcp6.ip6_config, is_up);
-				intersect_ext_config (self, &priv->wwan_ip_config_6, is_up);
+				intersect_ext_config (self, &priv->ac_ip6_config, is_up, is_up);
+				intersect_ext_config (self, &priv->dhcp6.ip6_config, is_up, is_up);
+				intersect_ext_config (self, &priv->dev2_ip_config_6, is_up, is_up);
 
 				for (iter = priv->vpn_configs_6; iter; iter = iter->next)
-					nm_ip6_config_intersect (iter->data, priv->ext_ip_config_6, is_up, 0);
+					nm_ip6_config_intersect (iter->data, priv->ext_ip_config_6, is_up, is_up, 0);
 
 				if (   priv->ipv6ll_has
 				    && !nm_ip6_config_lookup_address (priv->ext_ip_config_6, &priv->ipv6ll_addr))
@@ -12778,9 +12818,9 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				                       applied_config_get_current (&priv->dhcp6.ip6_config),
 				                       default_route_metric_penalty_get (self, AF_INET6));
 			}
-			if (applied_config_get_current (&priv->wwan_ip_config_6)) {
+			if (applied_config_get_current (&priv->dev2_ip_config_6)) {
 				nm_ip_config_subtract ((NMIPConfig *) priv->ext_ip_config_6,
-				                       applied_config_get_current (&priv->wwan_ip_config_6),
+				                       applied_config_get_current (&priv->dev2_ip_config_6),
 				                       default_route_metric_penalty_get (self, AF_INET6));
 			}
 			for (iter = priv->vpn_configs_6; iter; iter = iter->next)
@@ -12844,9 +12884,7 @@ queued_ip_config_change (NMDevice *self, int addr_family)
 	 * update in such case.
 	 */
 	if (activation_source_is_scheduled (self,
-	                                    IS_IPv4
-	                                      ? activate_stage5_ip4_config_result
-	                                      : activate_stage5_ip6_config_commit,
+	                                    activate_stage5_ip_config_result_x[IS_IPv4],
 	                                    addr_family))
 		return G_SOURCE_CONTINUE;
 
@@ -12864,14 +12902,17 @@ queued_ip_config_change (NMDevice *self, int addr_family)
 	if (!IS_IPv4) {
 		NMPlatform *platform;
 		GSList *dad6_failed_addrs, *iter;
+		const NMPlatformLink *pllink;
 
 		dad6_failed_addrs = g_steal_pointer (&priv->dad6_failed_addrs);
 
 		if (   priv->state > NM_DEVICE_STATE_DISCONNECTED
 		    && priv->state < NM_DEVICE_STATE_DEACTIVATING
+		    && priv->ifindex > 0
 		    && !nm_device_sys_iface_state_is_external (self)
 		    && (platform = nm_device_get_platform (self))
-		    && nm_platform_link_get (platform, priv->ifindex)) {
+		    && (pllink = nm_platform_link_get (platform, priv->ifindex))
+		    && (pllink->n_ifi_flags & IFF_UP)) {
 			gboolean need_ipv6ll = FALSE;
 			NMNDiscConfigMap ndisc_config_changed = NM_NDISC_CONFIG_NONE;
 
@@ -12914,29 +12955,22 @@ queued_ip_config_change (NMDevice *self, int addr_family)
 
 	if (!IS_IPv4) {
 		/* Check if DAD is still pending */
-		if (   priv->ip6_state == IP_CONF
+		if (   priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF
 		    && priv->dad6_ip6_config
 		    && priv->ext_ip6_config_captured
 		    && !nm_ip6_config_has_any_dad_pending (priv->ext_ip6_config_captured,
 		                                           priv->dad6_ip6_config)) {
 			_LOGD (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD terminated");
 			g_clear_object (&priv->dad6_ip6_config);
-			_set_ip_state (self, addr_family, IP_DONE);
+			_set_ip_state (self, addr_family, NM_DEVICE_IP_STATE_DONE);
 			check_ip_state (self, FALSE, TRUE);
 			if (priv->rt6_temporary_not_available)
-				nm_device_activate_schedule_ip6_config_result (self);
+				nm_device_activate_schedule_ip_config_result (self, AF_INET6, NULL);
 		}
 	}
 
 	set_unmanaged_external_down (self, TRUE);
 
-	if (IS_IPv4 && FALSE /* rp_filter handling is disabled */) {
-		if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
-			priv->v4_has_shadowed_routes = _v4_has_shadowed_routes_detect (self);;
-			ip4_rp_filter_update (self);
-		}
-	}
-
 	return G_SOURCE_REMOVE;
 }
 
@@ -13089,10 +13123,10 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo
 
 		/* @for_user_request can make the result only ~more~ managed.
 		 * If the flags already indicate a managed state for a non-user-request,
-		 * then it is also managed for an explict user-request.
+		 * then it is also managed for an explicit user-request.
 		 *
 		 * Effectively, this check is redundant, as the code below already
-		 * already ensures that. Still, express this invariant explictly here. */
+		 * already ensures that. Still, express this invariant explicitly here. */
 		if (_get_managed_by_flags (flags, mask, FALSE))
 			return TRUE;
 
@@ -13148,7 +13182,7 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo
 /**
  * nm_device_get_managed:
  * @self: the #NMDevice
- * @for_user_request: whether to check the flags for an explict user-request
+ * @for_user_request: whether to check the flags for an explicit user-request
  *
  * Whether the device is unmanaged according to the unmanaged flags.
  *
@@ -13326,7 +13360,7 @@ _set_unmanaged_flags (NMDevice *self,
 	       flags,
 	       NM_PRINT_FMT_QUOTED (allow_state_transition,
 	                            ", reason ",
-	                            reason_to_string (reason),
+	                            reason_to_string_a (reason),
 	                            transition_state ? ", transition-state" : "",
 	                            ""));
 
@@ -13603,7 +13637,7 @@ nm_device_update_metered (NMDevice *self)
 	/* Try to guess a value using the metered flag in IP configuration */
 	if (value == NM_METERED_INVALID) {
 		if (   priv->ip_config_4
-		    && priv->ip4_state == IP_DONE
+		    && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
 		    && nm_ip4_config_get_metered (priv->ip_config_4))
 			value = NM_METERED_GUESS_YES;
 	}
@@ -14101,22 +14135,32 @@ nm_device_remove_pending_action (NMDevice *self, const char *action, gboolean as
 	return FALSE;
 }
 
-gboolean
-nm_device_has_pending_action (NMDevice *self)
+const char *
+nm_device_has_pending_action_reason (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->pending_actions)
-		return TRUE;
+	if (priv->pending_actions) {
+
+		if (   !priv->pending_actions->next
+		    && nm_device_get_state (self) == NM_DEVICE_STATE_ACTIVATED
+		    && nm_streq (priv->pending_actions->data, NM_PENDING_ACTION_CARRIER_WAIT)) {
+			/* if the device is already in activated state, and the only reason
+			 * why it appears still busy is "carrier-wait", then we are already complete. */
+			return NULL;
+		}
+
+		return priv->pending_actions->data;
+	}
 
 	if (   nm_device_is_real (self)
 	    && nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) {
 		/* as long as the platform link is not yet initialized, we have a pending
 		 * action. */
-		return TRUE;
+		return NM_PENDING_ACTION_LINK_INIT;
 	}
 
-	return FALSE;
+	return NULL;
 }
 
 /*****************************************************************************/
@@ -14194,15 +14238,15 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	nm_device_set_ip_config (self, AF_INET6, NULL, TRUE, NULL);
 	g_clear_object (&priv->proxy_config);
 	g_clear_object (&priv->con_ip_config_4);
-	applied_config_clear (&priv->dev_ip4_config);
-	applied_config_clear (&priv->wwan_ip_config_4);
+	applied_config_clear (&priv->dev_ip_config_4);
+	applied_config_clear (&priv->dev2_ip_config_4);
 	g_clear_object (&priv->ext_ip_config_4);
 	g_clear_object (&priv->ip_config_4);
 	g_clear_object (&priv->con_ip_config_6);
 	applied_config_clear (&priv->ac_ip6_config);
 	g_clear_object (&priv->ext_ip_config_6);
 	g_clear_object (&priv->ext_ip6_config_captured);
-	applied_config_clear (&priv->wwan_ip_config_6);
+	applied_config_clear (&priv->dev2_ip_config_6);
 	g_clear_object (&priv->ip_config_6);
 	g_clear_object (&priv->dad6_ip6_config);
 	priv->ipv6ll_has = FALSE;
@@ -14242,7 +14286,7 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 		delete_on_deactivate_check_and_schedule (self, nm_device_get_ip_ifindex (self));
 	}
 
-	/* ip_iface should be cleared after flushing all routes and addreses, since
+	/* ip_iface should be cleared after flushing all routes and addresses, since
 	 * those are identified by ip_iface, not by iface (which might be a tty
 	 * or ATM device).
 	 */
@@ -14266,7 +14310,7 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 	if (reason == NM_DEVICE_STATE_REASON_NOW_MANAGED)
 		_LOGD (LOGD_DEVICE, "preparing device");
 	else
-		_LOGD (LOGD_DEVICE, "deactivating device (reason '%s') [%d]", reason_to_string (reason), reason);
+		_LOGD (LOGD_DEVICE, "deactivating device (reason '%s') [%d]", reason_to_string_a (reason), reason);
 
 	/* Save whether or not we tried IPv6 for later */
 	priv = NM_DEVICE_GET_PRIVATE (self);
@@ -14276,8 +14320,8 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 	/* Turn off kernel IPv6 */
 	if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
 		set_disable_ipv6 (self, "1");
-		nm_device_ipv6_sysctl_set (self, "accept_ra", "0");
-		nm_device_ipv6_sysctl_set (self, "use_tempaddr", "0");
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra", "0");
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "use_tempaddr", "0");
 	}
 
 	/* Call device type-specific deactivation */
@@ -14306,6 +14350,7 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 
 	/* slave: mark no longer enslaved */
 	if (   priv->master
+	    && priv->ifindex > 0
 	    && nm_platform_link_get_master (nm_device_get_platform (self), priv->ifindex) <= 0)
 		nm_device_master_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
 
@@ -14342,8 +14387,8 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 			if (priv->ip6_mtu_initial) {
 				char sbuf[64];
 
-				nm_device_ipv6_sysctl_set (self, "mtu",
-				                           nm_sprintf_buf (sbuf, "%u", (unsigned) priv->ip6_mtu_initial));
+				nm_device_sysctl_ip_conf_set (self, AF_INET6, "mtu",
+				                              nm_sprintf_buf (sbuf, "%u", (unsigned) priv->ip6_mtu_initial));
 			}
 		}
 		priv->mtu_initial = 0;
@@ -14373,7 +14418,7 @@ find_dhcp4_address (NMDevice *self)
 
 	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, priv->ip_config_4, &a) {
 		if (a->addr_source == NM_IP_CONFIG_SOURCE_DHCP)
-			return g_strdup (nm_utils_inet4_ntop (a->address, NULL));
+			return nm_utils_inet4_ntop_dup (a->address);
 	}
 	return NULL;
 }
@@ -14398,7 +14443,8 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		return;
 
 	connection = nm_device_get_applied_connection (self);
-	g_assert (connection);
+
+	g_return_if_fail (connection);
 
 	argv = g_ptr_array_sized_new (10);
 	g_ptr_array_set_free_func (argv, g_free);
@@ -14432,12 +14478,12 @@ nm_device_spawn_iface_helper (NMDevice *self)
 
 	dhcp4_address = find_dhcp4_address (self);
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	if (g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) {
+	method = nm_device_get_effective_ip_config_method (self, AF_INET);
+	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
 		NMSettingIPConfig *s_ip4;
 
 		s_ip4 = nm_connection_get_setting_ip4_config (connection);
-		g_assert (s_ip4);
+		nm_assert (s_ip4);
 
 		g_ptr_array_add (argv, g_strdup ("--priority4"));
 		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_route_metric (self, AF_INET)));
@@ -14455,11 +14501,11 @@ nm_device_spawn_iface_helper (NMDevice *self)
 			if (client_id) {
 				g_ptr_array_add (argv, g_strdup ("--dhcp4-clientid"));
 				g_ptr_array_add (argv,
-				                 _nm_utils_bin2hexstr_full (g_bytes_get_data (client_id, NULL),
-				                                            g_bytes_get_size (client_id),
-				                                            ':',
-				                                            FALSE,
-				                                            NULL));
+				                 nm_utils_bin2hexstr_full (g_bytes_get_data (client_id, NULL),
+				                                           g_bytes_get_size (client_id),
+				                                           ':',
+				                                           FALSE,
+				                                           NULL));
 			}
 
 			hostname = nm_dhcp_client_get_hostname (priv->dhcp4.client);
@@ -14475,8 +14521,8 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		configured = TRUE;
 	}
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-	if (g_strcmp0 (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) {
+	method = nm_utils_get_ip_config_method (connection, AF_INET6);
+	if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
 		NMSettingIPConfig *s_ip6;
 		NMUtilsIPv6IfaceId iid = NM_UTILS_IPV6_IFACE_ID_INIT;
 
@@ -14497,11 +14543,11 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		if (nm_device_get_ip_iface_identifier (self, &iid, FALSE)) {
 			g_ptr_array_add (argv, g_strdup ("--iid"));
 			g_ptr_array_add (argv,
-			                 _nm_utils_bin2hexstr_full (iid.id_u8,
-			                                            sizeof (NMUtilsIPv6IfaceId),
-			                                            ':',
-			                                            FALSE,
-			                                            NULL));
+			                 nm_utils_bin2hexstr_full (iid.id_u8,
+			                                           sizeof (NMUtilsIPv6IfaceId),
+			                                           ':',
+			                                           FALSE,
+			                                           NULL));
 		}
 
 		g_ptr_array_add (argv, g_strdup ("--addr-gen-mode"));
@@ -14560,39 +14606,34 @@ ip6_managed_setup (NMDevice *self)
 {
 	set_nm_ipv6ll (self, TRUE);
 	set_disable_ipv6 (self, "1");
-	nm_device_ipv6_sysctl_set (self, "accept_ra_defrtr", "0");
-	nm_device_ipv6_sysctl_set (self, "accept_ra_pinfo", "0");
-	nm_device_ipv6_sysctl_set (self, "accept_ra_rtr_pref", "0");
-	nm_device_ipv6_sysctl_set (self, "use_tempaddr", "0");
-	nm_device_ipv6_sysctl_set (self, "forwarding", "0");
+	nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra_defrtr", "0");
+	nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra_pinfo", "0");
+	nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra_rtr_pref", "0");
+	nm_device_sysctl_ip_conf_set (self, AF_INET6, "use_tempaddr", "0");
+	nm_device_sysctl_ip_conf_set (self, AF_INET6, "forwarding", "0");
 }
 
 static void
 deactivate_async_ready (NMDevice *self,
-                        GAsyncResult *res,
+                        GError *error,
                         gpointer user_data)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMDeviceStateReason reason = GPOINTER_TO_UINT (user_data);
-	GError *error = NULL;
 
-	NM_DEVICE_GET_CLASS (self)->deactivate_async_finish (self, res, &error);
-
-	/* If operation cancelled, just return */
-	if (   g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)
-	    || (priv->deactivating_cancellable && g_cancellable_is_cancelled (priv->deactivating_cancellable))) {
-		_LOGW (LOGD_DEVICE, "Deactivation cancelled");
-	} else {
-		/* In every other case, transition to the DISCONNECTED state */
-		if (error) {
-			_LOGW (LOGD_DEVICE, "Deactivation failed: %s",
-			       error->message);
-		}
-		nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		_LOGD (LOGD_DEVICE, "Deactivation cancelled");
+		return;
 	}
 
 	g_clear_object (&priv->deactivating_cancellable);
-	g_clear_error (&error);
+
+	/* In every other case, transition to the DISCONNECTED state */
+	if (error) {
+		_LOGW (LOGD_DEVICE, "Deactivation failed: %s",
+		       error->message);
+	}
+	nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
 }
 
 static void
@@ -14612,14 +14653,13 @@ deactivate_dispatcher_complete (guint call_id, gpointer user_data)
 	priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
 
 	if (nm_clear_g_cancellable (&priv->deactivating_cancellable))
-		g_warn_if_reached ();
+		nm_assert_not_reached ();
 
-	if (   NM_DEVICE_GET_CLASS (self)->deactivate_async
-	    && NM_DEVICE_GET_CLASS (self)->deactivate_async_finish) {
+	if (NM_DEVICE_GET_CLASS (self)->deactivate_async) {
 		priv->deactivating_cancellable = g_cancellable_new ();
 		NM_DEVICE_GET_CLASS (self)->deactivate_async (self,
 		                                              priv->deactivating_cancellable,
-		                                              (GAsyncReadyCallback) deactivate_async_ready,
+		                                              deactivate_async_ready,
 		                                              GUINT_TO_POINTER (reason));
 	} else
 		nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
@@ -14657,7 +14697,7 @@ _set_state_full (NMDevice *self,
 		_LOGD (LOGD_DEVICE, "state change: %s -> %s (reason '%s', sys-iface-state: '%s'%s)",
 		       nm_device_state_to_str (old_state),
 		       nm_device_state_to_str (state),
-		       reason_to_string (reason),
+		       reason_to_string_a (reason),
 		       _sys_iface_state_to_str (priv->sys_iface_state),
 		       priv->firmware_missing ? ", missing firmware" : "");
 		return;
@@ -14666,7 +14706,7 @@ _set_state_full (NMDevice *self,
 	_LOGI (LOGD_DEVICE, "state change: %s -> %s (reason '%s', sys-iface-state: '%s')",
 	       nm_device_state_to_str (old_state),
 	       nm_device_state_to_str (state),
-	       reason_to_string (reason),
+	       reason_to_string_a (reason),
 	       _sys_iface_state_to_str (priv->sys_iface_state));
 
 	priv->in_state_changed = TRUE;
@@ -14677,8 +14717,8 @@ _set_state_full (NMDevice *self,
 	queued_state_clear (self);
 
 	dispatcher_cleanup (self);
-	if (priv->deactivating_cancellable)
-		g_cancellable_cancel (priv->deactivating_cancellable);
+
+	nm_clear_g_cancellable (&priv->deactivating_cancellable);
 
 	/* Cache the activation request for the dispatcher */
 	req = nm_g_object_ref (priv->act_request.obj);
@@ -14702,8 +14742,10 @@ _set_state_full (NMDevice *self,
 	if (state <= NM_DEVICE_STATE_UNAVAILABLE) {
 		if (available_connections_del_all (self))
 			_notify (self, PROP_AVAILABLE_CONNECTIONS);
-		if (old_state > NM_DEVICE_STATE_UNAVAILABLE)
-			_clear_queued_act_request (priv);
+		if (old_state > NM_DEVICE_STATE_UNAVAILABLE) {
+			_clear_queued_act_request (priv,
+			                           NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
+		}
 	}
 
 	/* Update the available connections list when a device first becomes available */
@@ -14833,7 +14875,7 @@ _set_state_full (NMDevice *self,
 		}
 		break;
 	case NM_DEVICE_STATE_DEACTIVATING:
-		if (   (s_sriov = (NMSettingSriov *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))
+		if (   (s_sriov = nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))
 		    && priv->ifindex > 0) {
 			nm_platform_link_set_sriov_params (nm_device_get_platform (self),
 			                                   priv->ifindex, 0, NM_TERNARY_TRUE);
@@ -14968,8 +15010,8 @@ _set_state_full (NMDevice *self,
 	if (ip_config_valid (old_state) && !ip_config_valid (state))
 	    notify_ip_properties (self);
 
-	concheck_update_interval (self,
-	                          state == NM_DEVICE_STATE_ACTIVATED);
+	concheck_update_interval (self, AF_INET, state == NM_DEVICE_STATE_ACTIVATED);
+	concheck_update_interval (self, AF_INET6, state == NM_DEVICE_STATE_ACTIVATED);
 
 	/* Dispose of the cached activation request */
 	if (req)
@@ -15001,7 +15043,7 @@ queued_state_set (gpointer user_data)
 
 	_LOGD (LOGD_DEVICE, "queue-state[%s, reason:%s, id:%u]: %s",
 	       nm_device_state_to_str (priv->queued_state.state),
-	       reason_to_string (priv->queued_state.reason),
+	       reason_to_string_a (priv->queued_state.reason),
 	       priv->queued_state.id,
 	       "change state");
 
@@ -15032,23 +15074,23 @@ nm_device_queue_state (NMDevice *self,
 	if (priv->queued_state.id && priv->queued_state.state == state) {
 		_LOGD (LOGD_DEVICE, "queue-state[%s, reason:%s, id:%u]: %s%s%s%s",
 		       nm_device_state_to_str (priv->queued_state.state),
-		       reason_to_string (priv->queued_state.reason),
+		       reason_to_string_a (priv->queued_state.reason),
 		       priv->queued_state.id,
 		       "ignore queuing same state change",
 		       NM_PRINT_FMT_QUOTED (priv->queued_state.reason != reason,
-		                            " (reason differs: ", reason_to_string (reason), ")", ""));
+		                            " (reason differs: ", reason_to_string_a (reason), ")", ""));
 		return;
 	}
 
 	/* Add pending action for the new state before clearing the queued states, so
-	 * that we don't accidently pop all pending states and reach 'startup complete'  */
+	 * that we don't accidentally pop all pending states and reach 'startup complete'  */
 	nm_device_add_pending_action (self, queued_state_to_string (state), TRUE);
 
 	/* We should only ever have one delayed state transition at a time */
 	if (priv->queued_state.id) {
 		_LOGW (LOGD_DEVICE, "queue-state[%s, reason:%s, id:%u]: %s",
 		       nm_device_state_to_str (priv->queued_state.state),
-		       reason_to_string (priv->queued_state.reason),
+		       reason_to_string_a (priv->queued_state.reason),
 		       priv->queued_state.id,
 		       "replace previously queued state change");
 		nm_clear_g_source (&priv->queued_state.id);
@@ -15061,7 +15103,7 @@ nm_device_queue_state (NMDevice *self,
 
 	_LOGD (LOGD_DEVICE, "queue-state[%s, reason:%s, id:%u]: %s",
 	       nm_device_state_to_str (state),
-	       reason_to_string (reason),
+	       reason_to_string_a (reason),
 	       priv->queued_state.id,
 	       "queue state change");
 }
@@ -15076,7 +15118,7 @@ queued_state_clear (NMDevice *self)
 
 	_LOGD (LOGD_DEVICE, "queue-state[%s, reason:%s, id:%u]: %s",
 	       nm_device_state_to_str (priv->queued_state.state),
-	       reason_to_string (priv->queued_state.reason),
+	       reason_to_string_a (priv->queued_state.reason),
 	       priv->queued_state.id,
 	       "clear queued state change");
 	nm_clear_g_source (&priv->queued_state.id);
@@ -15166,7 +15208,7 @@ nm_device_update_hw_address (NMDevice *self)
 	        && priv->state < NM_DEVICE_STATE_PREPARE
 	        && !nm_device_is_activating (self))) {
 		/* when we get a hw_addr the first time or while the device
-		 * is not activated (with no explict hw address set), always
+		 * is not activated (with no explicit hw address set), always
 		 * update our initial hw-address as well. */
 		nm_device_update_initial_hw_address (self);
 	}
@@ -15183,7 +15225,7 @@ nm_device_update_initial_hw_address (NMDevice *self)
 		if (   priv->hw_addr_initial
 		    && priv->hw_addr_type != HW_ADDR_TYPE_UNSET) {
 			/* once we have the initial hw address set, we only allow
-			 * update if the currenty type is "unset". */
+			 * update if the currently type is "unset". */
 			return;
 		}
 		g_free (priv->hw_addr_initial);
@@ -15308,7 +15350,9 @@ _get_cloned_mac_address_setting (NMDevice *self, NMConnection *connection, gbool
 		gs_free char *a = NULL;
 
 		a = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-		                                           is_wifi ? "wifi.cloned-mac-address" : "ethernet.cloned-mac-address",
+		                                           is_wifi
+		                                               ? NM_CON_DEFAULT ("wifi.cloned-mac-address")
+		                                               : NM_CON_DEFAULT ("ethernet.cloned-mac-address"),
 		                                           self);
 
 		addr = NM_CLONED_MAC_PRESERVE;
@@ -15319,7 +15363,7 @@ _get_cloned_mac_address_setting (NMDevice *self, NMConnection *connection, gbool
 
 				/* for backward compatibility, read the deprecated wifi.mac-address-randomization setting. */
 				a = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-				                                           "wifi." NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION,
+				                                           NM_CON_DEFAULT ("wifi.mac-address-randomization"),
 				                                           self);
 				v = _nm_utils_ascii_str_to_int64 (a, 10,
 				                                  NM_SETTING_MAC_RANDOMIZATION_DEFAULT,
@@ -15356,7 +15400,9 @@ _get_generate_mac_address_mask_setting (NMDevice *self, NMConnection *connection
 	}
 
 	a = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                           is_wifi ? "wifi.generate-mac-address-mask" : "ethernet.generate-mac-address-mask",
+	                                           is_wifi
+	                                               ? NM_CON_DEFAULT ("wifi.generate-mac-address-mask")
+	                                               : NM_CON_DEFAULT ("ethernet.generate-mac-address-mask"),
 	                                           self);
 	if (!a)
 		return NULL;
@@ -15394,7 +15440,7 @@ _hw_addr_set (NMDevice *self,
 {
 	NMDevicePrivate *priv;
 	gboolean success = FALSE;
-	NMPlatformError plerr;
+	int r;
 	guint8 addr_bytes[NM_UTILS_HWADDR_LEN_MAX];
 	gsize addr_len;
 	gboolean was_taken_down = FALSE;
@@ -15431,21 +15477,21 @@ _hw_addr_set (NMDevice *self,
 	}
 
 again:
-	plerr = nm_platform_link_set_address (nm_device_get_platform (self), nm_device_get_ip_ifindex (self), addr_bytes, addr_len);
-	success = (plerr == NM_PLATFORM_ERROR_SUCCESS);
+	r = nm_platform_link_set_address (nm_device_get_platform (self), nm_device_get_ip_ifindex (self), addr_bytes, addr_len);
+	success = (r >= 0);
 	if (!success) {
 		retry_down =    !was_taken_down
-		             && plerr != NM_PLATFORM_ERROR_NOT_FOUND
+		             && r != -NME_PL_NOT_FOUND
 		             && nm_platform_link_is_up (nm_device_get_platform (self),
 		                                        nm_device_get_ip_ifindex (self));
-		_NMLOG (     retry_down
-		          || plerr == NM_PLATFORM_ERROR_NOT_FOUND
+		_NMLOG (  (   retry_down
+		           || r == -NME_PL_NOT_FOUND)
 		        ? LOGL_DEBUG
 		        : LOGL_WARN,
 		        LOGD_DEVICE,
 		        "set-hw-addr: failed to %s MAC address to %s (%s) (%s)%s",
 		        operation, addr, detail,
-		        nm_platform_error_to_string_a (plerr),
+		        nm_strerror (r),
 		        retry_down ? " (retry with taking down)" : "");
 	} else {
 		/* MAC address successfully changed; update the current MAC to match */
@@ -15468,7 +15514,7 @@ again:
 			 * that is rather complicated and it is not expected that this case
 			 * happens for regular drivers.
 			 * Note that brcmfmac can block NetworkManager for 500 msec while
-			 * taking down the device. Let's add annother 100 msec to that.
+			 * taking down the device. Let's add another 100 msec to that.
 			 *
 			 * wait/poll up to 100 msec until it changes. */
 
@@ -15870,7 +15916,9 @@ nm_device_get_supplicant_timeout (NMDevice *self)
 	g_return_val_if_fail (NM_IS_DEVICE (self), SUPPLICANT_DEFAULT_TIMEOUT);
 
 	connection = nm_device_get_applied_connection (self);
+
 	g_return_val_if_fail (connection, SUPPLICANT_DEFAULT_TIMEOUT);
+
 	s_8021x = nm_connection_get_setting_802_1x (connection);
 	if (s_8021x) {
 		timeout = nm_setting_802_1x_get_auth_timeout (s_8021x);
@@ -15879,7 +15927,7 @@ nm_device_get_supplicant_timeout (NMDevice *self)
 	}
 
 	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-	                                                    "802-1x.auth-timeout",
+	                                                    NM_CON_DEFAULT ("802-1x.auth-timeout"),
 	                                                    self,
 	                                                    1,
 	                                                    G_MAXINT32,
@@ -15901,13 +15949,13 @@ nm_device_auth_retries_try_next (NMDevice *self)
 	if (G_UNLIKELY (auth_retries == NM_DEVICE_AUTH_RETRIES_UNSET)) {
 		auth_retries = -1;
 
-		s_con = NM_SETTING_CONNECTION (nm_device_get_applied_setting (self, NM_TYPE_SETTING_CONNECTION));
+		s_con = nm_device_get_applied_setting (self, NM_TYPE_SETTING_CONNECTION);
 		if (s_con)
 			auth_retries = nm_setting_connection_get_auth_retries (s_con);
 
 		if (auth_retries == -1) {
 			auth_retries = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-			                                                            "connection.auth-retries",
+			                                                            NM_CON_DEFAULT ("connection.auth-retries"),
 			                                                            self,
 			                                                            -1, G_MAXINT32, -1);
 		}
@@ -15945,10 +15993,10 @@ _activation_func_to_string (ActivationHandleFunc func)
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage1_device_prepare);
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage2_device_config);
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage3_ip_config_start);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip4_config_timeout);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip6_config_timeout);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip4_config_result);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip6_config_commit);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip_config_timeout_4);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip_config_timeout_6);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip_config_result_4);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip_config_result_6);
 	g_return_val_if_reached ("unknown");
 }
 
@@ -15967,7 +16015,8 @@ nm_device_init (NMDevice *self)
 	c_list_init (&self->devices_lst);
 	c_list_init (&priv->slaves);
 
-	priv->connectivity_state = NM_CONNECTIVITY_UNKNOWN;
+	priv->concheck_x[0].state = NM_CONNECTIVITY_UNKNOWN;
+	priv->concheck_x[1].state = NM_CONNECTIVITY_UNKNOWN;
 
 	nm_dbus_track_obj_path_init (&priv->parent_device, G_OBJECT (self), obj_properties[PROP_PARENT]);
 	nm_dbus_track_obj_path_init (&priv->act_request, G_OBJECT (self), obj_properties[PROP_ACTIVE_CONNECTION]);
@@ -16155,7 +16204,8 @@ dispose (GObject *object)
 	if (nm_clear_g_source (&priv->carrier_wait_id))
 		nm_device_remove_pending_action (self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
 
-	_clear_queued_act_request (priv);
+	_clear_queued_act_request (priv,
+	                           NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
 
 	nm_clear_g_source (&priv->device_link_changed_id);
 	nm_clear_g_source (&priv->device_ip_link_changed_id);
@@ -16168,12 +16218,13 @@ dispose (GObject *object)
 		g_clear_object (&priv->lldp_listener);
 	}
 
-	nm_clear_g_source (&priv->concheck_p_cur_id);
+	nm_clear_g_source (&priv->concheck_x[0].p_cur_id);
+	nm_clear_g_source (&priv->concheck_x[1].p_cur_id);
 
 	G_OBJECT_CLASS (nm_device_parent_class)->dispose (object);
 
 	if (nm_clear_g_source (&priv->queued_state.id)) {
-		/* FIXME: we'd expect the queud_state to be alredy cleared and this statement
+		/* FIXME: we'd expect the queud_state to be already cleared and this statement
 		 * not being necessary. Add this check here to hopefully investigate crash
 		 * rh#1270247. */
 		g_return_if_reached ();
@@ -16293,6 +16344,8 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		nm_assert (priv->type == NM_DEVICE_TYPE_UNKNOWN);
 		priv->type = g_value_get_uint (value);
+		nm_assert (priv->type > NM_DEVICE_TYPE_UNKNOWN);
+		nm_assert (priv->type <= NM_DEVICE_TYPE_WIFI_P2P);
 		break;
 	case PROP_LINK_TYPE:
 		/* construct-only */
@@ -16505,8 +16558,11 @@ get_property (GObject *object, guint prop_id,
 	case PROP_RX_BYTES:
 		g_value_set_uint64 (value, priv->stats.rx_bytes);
 		break;
-	case PROP_CONNECTIVITY:
-		g_value_set_uint (value, priv->connectivity_state);
+	case PROP_IP4_CONNECTIVITY:
+		g_value_set_uint (value, priv->concheck_x[1].state);
+		break;
+	case PROP_IP6_CONNECTIVITY:
+		g_value_set_uint (value, priv->concheck_x[0].state);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -16594,6 +16650,8 @@ static const NMDBusInterfaceInfoExtended interface_info_device = {
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Metered",              "u",      NM_DEVICE_METERED),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("LldpNeighbors",        "aa{sv}", NM_DEVICE_LLDP_NEIGHBORS),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Real",                 "b",      NM_DEVICE_REAL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE       ("Ip4Connectivity",      "u",      NM_DEVICE_IP4_CONNECTIVITY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE       ("Ip6Connectivity",      "u",      NM_DEVICE_IP6_CONNECTIVITY),
 		),
 	),
 };
@@ -16636,10 +16694,8 @@ nm_device_class_init (NMDeviceClass *klass)
 	klass->is_available = is_available;
 	klass->act_stage1_prepare = act_stage1_prepare;
 	klass->act_stage2_config = act_stage2_config;
-	klass->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	klass->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
-	klass->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
-	klass->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
+	klass->act_stage3_ip_config_start = act_stage3_ip_config_start;
+	klass->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
 
 	klass->get_type_description = get_type_description;
 	klass->can_auto_connect = can_auto_connect;
@@ -16870,8 +16926,13 @@ nm_device_class_init (NMDeviceClass *klass)
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_CONNECTIVITY] =
-	     g_param_spec_uint (NM_DEVICE_CONNECTIVITY, "", "",
+	obj_properties[PROP_IP4_CONNECTIVITY] =
+	     g_param_spec_uint (NM_DEVICE_IP4_CONNECTIVITY, "", "",
+	                        NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_IP6_CONNECTIVITY] =
+	     g_param_spec_uint (NM_DEVICE_IP6_CONNECTIVITY, "", "",
 	                        NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
 	                        G_PARAM_READABLE |
 	                        G_PARAM_STATIC_STRINGS);
@@ -16951,12 +17012,12 @@ nm_device_class_init (NMDeviceClass *klass)
 	                  G_SIGNAL_RUN_FIRST,
 	                  0, NULL, NULL, NULL,
 	                  G_TYPE_NONE, 0);
-
-	signals[CONNECTIVITY_CHANGED] =
-	    g_signal_new (NM_DEVICE_CONNECTIVITY_CHANGED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL,
-	                  g_cclosure_marshal_VOID__VOID,
-	                  G_TYPE_NONE, 0);
 }
+
+/* Connection defaults from plugins */
+NM_CON_DEFAULT_NOP ("cdma.mtu");
+NM_CON_DEFAULT_NOP ("gsm.mtu");
+NM_CON_DEFAULT_NOP ("wifi.powersave");
+NM_CON_DEFAULT_NOP ("wifi.wake-on-wlan");
+NM_CON_DEFAULT_NOP ("wifi-sec.pmf");
+NM_CON_DEFAULT_NOP ("wifi-sec.fils");
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 3c703564..45c9dda0 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -54,7 +54,7 @@ typedef enum {
 static inline NMDeviceStateReason
 nm_device_state_reason_check (NMDeviceStateReason reason)
 {
-	/* the device-state-reason serves mostly informational purpse during a state
+	/* the device-state-reason serves mostly informational purpose during a state
 	 * change. In some cases however, decisions are made based on the reason.
 	 * I tend to think that interpreting the state reason to derive some behaviors
 	 * is confusing, because the cause and effect are so far apart.
@@ -76,6 +76,7 @@ nm_device_state_reason_check (NMDeviceStateReason reason)
 #define NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT    "waiting-for-supplicant"
 #define NM_PENDING_ACTION_WIFI_SCAN                 "wifi-scan"
 #define NM_PENDING_ACTION_WAITING_FOR_COMPANION     "waiting-for-companion"
+#define NM_PENDING_ACTION_LINK_INIT                 "link-init"
 
 #define NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "queued-state-change-"
 #define NM_PENDING_ACTIONPREFIX_ACTIVATION          "activation-"
@@ -142,13 +143,13 @@ nm_device_state_reason_check (NMDeviceStateReason reason)
 #define NM_DEVICE_STATE_CHANGED         "state-changed"
 #define NM_DEVICE_LINK_INITIALIZED      "link-initialized"
 #define NM_DEVICE_AUTOCONNECT_ALLOWED   "autoconnect-allowed"
-#define NM_DEVICE_CONNECTIVITY_CHANGED  "connectivity-changed"
 
 #define NM_DEVICE_STATISTICS_REFRESH_RATE_MS "refresh-rate-ms"
 #define NM_DEVICE_STATISTICS_TX_BYTES        "tx-bytes"
 #define NM_DEVICE_STATISTICS_RX_BYTES        "rx-bytes"
 
-#define NM_DEVICE_CONNECTIVITY               "connectivity"
+#define NM_DEVICE_IP4_CONNECTIVITY           "ip4-connectivity"
+#define NM_DEVICE_IP6_CONNECTIVITY           "ip6-connectivity"
 
 #define NM_TYPE_DEVICE            (nm_device_get_type ())
 #define NM_DEVICE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE, NMDevice))
@@ -182,13 +183,13 @@ typedef enum { /*< skip >*/
 	_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP           = (1L << 2),
 
 	/* a device can be marked as unmanaged for various reasons. Some of these reasons
-	 * are authorative, others not. Non-authoritative reasons can be overruled by
+	 * are authoritative, others not. Non-authoritative reasons can be overruled by
 	 * `nmcli device set $DEVICE managed yes`. Also, for an explicit user activation
 	 * request we may want to consider the device as managed. This flag makes devices
 	 * that are unmanaged appear available. */
 	_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED  = (1L << 3),
 
-	/* a collection of flags, that are commonly set for an explict user-request. */
+	/* a collection of flags, that are commonly set for an explicit user-request. */
 	NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST                      = _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST
 	                                                                    | _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER
 	                                                                    | _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP
@@ -221,6 +222,10 @@ typedef enum { /*< skip >*/
 	NM_DEVICE_CHECK_DEV_AVAILABLE_ALL                                   = (1L << 1) - 1,
 } NMDeviceCheckDevAvailableFlags;
 
+typedef void (*NMDeviceDeactivateCallback) (NMDevice *self,
+                                            GError *error,
+                                            gpointer user_data);
+
 typedef struct _NMDeviceClass {
 	NMDBusObjectClass parent;
 
@@ -278,7 +283,7 @@ typedef struct _NMDeviceClass {
 	 * Hook for derived classes to be notfied during realize_start_setup()
 	 * and perform additional setup.
 	 *
-	 * The default implemention of NMDevice calls link_changed().
+	 * The default implementation of NMDevice calls link_changed().
 	 */
 	void        (*realize_start_notify) (NMDevice *self,
 	                                     const NMPlatformLink *pllink);
@@ -327,9 +332,11 @@ typedef struct _NMDeviceClass {
 
 	guint32     (*get_configured_mtu) (NMDevice *self, NMDeviceMtuSource *out_source);
 
+	const char *(*get_auto_ip_config_method) (NMDevice *self, int addr_family);
+
 	/* Checks whether the connection is compatible with the device using
 	 * only the devices type and characteristics.  Does not use any live
-	 * network information like WiFi scan lists etc.
+	 * network information like Wi-Fi scan lists etc.
 	 */
 	gboolean    (* check_connection_compatible) (NMDevice *self,
 	                                             NMConnection *connection,
@@ -362,27 +369,21 @@ typedef struct _NMDeviceClass {
 	                                             NMDeviceStateReason *out_failure_reason);
 	NMActStageReturn    (* act_stage2_config)   (NMDevice *self,
 	                                             NMDeviceStateReason *out_failure_reason);
-	NMActStageReturn    (* act_stage3_ip4_config_start) (NMDevice *self,
-	                                                     NMIP4Config **out_config,
-	                                                     NMDeviceStateReason *out_failure_reason);
-	NMActStageReturn    (* act_stage3_ip6_config_start) (NMDevice *self,
-	                                                     NMIP6Config **out_config,
-	                                                     NMDeviceStateReason *out_failure_reason);
-	NMActStageReturn    (* act_stage4_ip4_config_timeout)   (NMDevice *self,
-	                                                         NMDeviceStateReason *out_failure_reason);
-	NMActStageReturn    (* act_stage4_ip6_config_timeout)   (NMDevice *self,
-	                                                         NMDeviceStateReason *out_failure_reason);
+	NMActStageReturn    (* act_stage3_ip_config_start) (NMDevice *self,
+	                                                    int addr_family,
+	                                                    gpointer *out_config,
+	                                                    NMDeviceStateReason *out_failure_reason);
+	NMActStageReturn    (* act_stage4_ip_config_timeout)   (NMDevice *self,
+	                                                        int addr_family,
+	                                                        NMDeviceStateReason *out_failure_reason);
 
 	void                (* ip4_config_pre_commit) (NMDevice *self, NMIP4Config *config);
 
 	/* Async deactivating (in the DEACTIVATING phase) */
 	void            (* deactivate_async)        (NMDevice *self,
 	                                             GCancellable *cancellable,
-	                                             GAsyncReadyCallback callback,
+	                                             NMDeviceDeactivateCallback callback,
 	                                             gpointer user_data);
-	gboolean        (* deactivate_async_finish) (NMDevice *self,
-	                                             GAsyncResult *res,
-	                                             GError **error);
 
 	void            (* deactivate_reset_hw_addr) (NMDevice *self);
 
@@ -452,6 +453,11 @@ typedef struct _NMDeviceClass {
 
 	guint32         (* get_dhcp_timeout) (NMDevice *self,
 	                                      int addr_family);
+
+	/* Controls, whether to call act_stage2_config() callback also for assuming
+	 * a device or for external activations. In this case, act_stage2_config() must
+	 * take care not to touch the device's configuration. */
+	bool act_stage2_config_also_for_external_or_assume:1;
 } NMDeviceClass;
 
 typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device,
@@ -481,6 +487,7 @@ int             nm_device_get_ifindex           (NMDevice *dev);
 gboolean        nm_device_is_software           (NMDevice *dev);
 gboolean        nm_device_is_real               (NMDevice *dev);
 const char *    nm_device_get_ip_iface          (NMDevice *dev);
+const char *    nm_device_get_ip_iface_from_platform (NMDevice *dev);
 int             nm_device_get_ip_ifindex        (const NMDevice *dev);
 const char *    nm_device_get_driver            (NMDevice *dev);
 const char *    nm_device_get_driver_version    (NMDevice *dev);
@@ -541,7 +548,10 @@ NMConnection *  nm_device_get_settings_connection_get_connection (NMDevice *self
 NMConnection *  nm_device_get_applied_connection (NMDevice *dev);
 gboolean        nm_device_has_unmodified_applied_connection (NMDevice *self,
                                                              NMSettingCompareFlags compare_flags);
-NMSetting *     nm_device_get_applied_setting   (NMDevice *dev, GType setting_type);
+NMActivationStateFlags nm_device_get_activation_state_flags (NMDevice *self);
+
+gpointer /* (NMSetting *) */ nm_device_get_applied_setting   (NMDevice *dev,
+                                                              GType setting_type);
 
 void            nm_device_removed               (NMDevice *self, gboolean unconfigure_ip_config);
 
@@ -617,7 +627,7 @@ void nm_device_copy_ip6_dns_config (NMDevice *self, NMDevice *from_device);
  *   the settings plugin (for example keyfile.unmanaged-devices or ifcfg-rh's
  *   NM_CONTROLLED=no). Although this is user-configuration (provided from
  *   the settings plugins, such as NM_CONTROLLED=no in ifcfg-rh), it cannot
- *   be overruled and is authorative. That is because users may depend on
+ *   be overruled and is authoritative. That is because users may depend on
  *   dropping a ifcfg-rh file to ensure the device is unmanaged.
  * @NM_UNMANAGED_USER_CONF: %TRUE when unmanaged by user decision via
  *   the NetworkManager.conf ("unmanaged" in the [device] section).
@@ -634,7 +644,7 @@ void nm_device_copy_ip6_dns_config (NMDevice *self, NMDevice *from_device);
 typedef enum { /*< skip >*/
 	NM_UNMANAGED_NONE          = 0,
 
-	/* these flags are authorative. If one of them is set,
+	/* these flags are authoritative. If one of them is set,
 	 * the device cannot be managed. */
 	NM_UNMANAGED_SLEEPING      = (1LL <<  0),
 	NM_UNMANAGED_QUITTING      = (1LL <<  1),
@@ -767,7 +777,9 @@ void nm_device_queue_state   (NMDevice *self,
 
 gboolean nm_device_get_firmware_missing (NMDevice *self);
 
-void nm_device_disconnect_active_connection (NMActiveConnection *active);
+void nm_device_disconnect_active_connection (NMActiveConnection *active,
+                                             NMDeviceStateReason device_reason,
+                                             NMActiveConnectionStateReason active_reason);
 
 void nm_device_queue_activation (NMDevice *device, NMActRequest *req);
 
@@ -775,7 +787,13 @@ gboolean nm_device_supports_vlans (NMDevice *device);
 
 gboolean nm_device_add_pending_action    (NMDevice *device, const char *action, gboolean assert_not_yet_pending);
 gboolean nm_device_remove_pending_action (NMDevice *device, const char *action, gboolean assert_is_pending);
-gboolean nm_device_has_pending_action    (NMDevice *device);
+const char *nm_device_has_pending_action_reason (NMDevice *device);
+
+static inline gboolean
+nm_device_has_pending_action (NMDevice *device)
+{
+	return !!nm_device_has_pending_action_reason (device);
+}
 
 NMSettingsConnection *nm_device_get_best_connection (NMDevice *device,
                                                      const char *specific_object,
@@ -838,12 +856,13 @@ typedef void (*NMDeviceConnectivityCallback) (NMDevice *self,
 void nm_device_check_connectivity_update_interval (NMDevice *self);
 
 NMDeviceConnectivityHandle *nm_device_check_connectivity (NMDevice *self,
+                                                          int addr_family,
                                                           NMDeviceConnectivityCallback callback,
                                                           gpointer user_data);
 
 void nm_device_check_connectivity_cancel (NMDeviceConnectivityHandle *handle);
 
-NMConnectivityState nm_device_get_connectivity_state (NMDevice *self);
+NMConnectivityState nm_device_get_connectivity_state (NMDevice *self, int addr_family);
 
 typedef struct _NMBtVTableNetworkServer NMBtVTableNetworkServer;
 struct _NMBtVTableNetworkServer {
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index c0484ed3..cb3b5907 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -23,7 +23,6 @@
 #include "nm-lldp-listener.h"
 
 #include <net/ethernet.h>
-#include <errno.h>
 
 #include "platform/nm-platform.h"
 #include "nm-utils.h"
@@ -128,7 +127,7 @@ typedef struct {
             int _ifindex = (self) ? NM_LLDP_LISTENER_GET_PRIVATE (self)->ifindex : 0; \
             \
             _nm_log (_level, _NMLOG_DOMAIN, 0, \
-                     nm_platform_link_get_name (NM_PLATFORM_GET, _ifindex), \
+                     _ifindex > 0 ? nm_platform_link_get_name (NM_PLATFORM_GET, _ifindex) : NULL, \
                      NULL, \
                      "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                      _NMLOG_PREFIX_NAME, \
@@ -384,7 +383,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	                                     &chassis_id, &chassis_id_len);
 	if (r < 0) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading chassis-id: %s", g_strerror (-r));
+		             "failed reading chassis-id: %s", nm_strerror_native (-r));
 		return NULL;
 	}
 	if (chassis_id_len < 1) {
@@ -397,7 +396,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	                                  &port_id, &port_id_len);
 	if (r < 0) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading port-id: %s", g_strerror (-r));
+		             "failed reading port-id: %s", nm_strerror_native (-r));
 		return NULL;
 	}
 	if (port_id_len < 1) {
@@ -413,7 +412,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	r = sd_lldp_neighbor_get_destination_address (neighbor_sd, &neigh->destination_address);
 	if (r < 0) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed getting destination address: %s", g_strerror (-r));
+		             "failed getting destination address: %s", nm_strerror_native (-r));
 		goto out;
 	}
 
@@ -464,7 +463,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 	r = sd_lldp_neighbor_tlv_rewind (neighbor_sd);
 	if (r < 0) {
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading tlv (rewind): %s", g_strerror (-r));
+		             "failed reading tlv (rewind): %s", nm_strerror_native (-r));
 		goto out;
 	}
 	do {
@@ -476,7 +475,7 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 			if (r == -ENXIO)
 				continue;
 			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-			             "failed reading tlv: %s", g_strerror (-r));
+			             "failed reading tlv: %s", nm_strerror_native (-r));
 			goto out;
 		}
 
diff --git a/src/devices/ovs/meson.build b/src/devices/ovs/meson.build
index 9d637fe9..834b27b0 100644
--- a/src/devices/ovs/meson.build
+++ b/src/devices/ovs/meson.build
@@ -3,12 +3,12 @@ sources = files(
   'nm-device-ovs-interface.c',
   'nm-device-ovs-port.c',
   'nm-ovsdb.c',
-  'nm-ovs-factory.c'
+  'nm-ovs-factory.c',
 )
 
 deps = [
   jansson_dep,
-  nm_dep
+  nm_dep,
 ]
 
 libnm_device_plugin_ovs = shared_module(
@@ -18,7 +18,7 @@ libnm_device_plugin_ovs = shared_module(
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_ovs
diff --git a/src/devices/ovs/nm-device-ovs-bridge.c b/src/devices/ovs/nm-device-ovs-bridge.c
index eff355a3..be707e7a 100644
--- a/src/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/devices/ovs/nm-device-ovs-bridge.c
@@ -77,17 +77,10 @@ get_generic_capabilities (NMDevice *device)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	return NM_ACT_STAGE_RETURN_IP_FAIL;
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	return NM_ACT_STAGE_RETURN_IP_FAIL;
 }
@@ -146,8 +139,7 @@ nm_device_ovs_bridge_class_init (NMDeviceOvsBridgeClass *klass)
 	device_class->create_and_realize = create_and_realize;
 	device_class->unrealize = unrealize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->enslave_slave = enslave_slave;
 	device_class->release_slave = release_slave;
 }
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index 2b48fae6..e3d3f9ee 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -121,48 +121,32 @@ link_changed (NMDevice *device,
 static gboolean
 _is_internal_interface (NMDevice *device)
 {
-	NMConnection *connection = nm_device_get_applied_connection (device);
-	NMSettingOvsInterface *s_ovs_iface = nm_connection_get_setting_ovs_interface (connection);
-
-	g_return_val_if_fail (s_ovs_iface, FALSE);
-
-	return strcmp (nm_setting_ovs_interface_get_interface_type (s_ovs_iface), "internal") == 0;
-}
-
-static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE (device);
+	NMSettingOvsInterface *s_ovs_iface;
 
-	if (!_is_internal_interface (device))
-		return NM_ACT_STAGE_RETURN_IP_FAIL;
+	s_ovs_iface = nm_device_get_applied_setting (device, NM_TYPE_SETTING_OVS_INTERFACE);
 
-	if (!nm_device_get_ip_ifindex (device)) {
-		priv->waiting_for_interface = TRUE;
-		return NM_ACT_STAGE_RETURN_POSTPONE;
-	}
+	g_return_val_if_fail (s_ovs_iface, FALSE);
 
-	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
+	return nm_streq (nm_setting_ovs_interface_get_interface_type (s_ovs_iface), "internal");
 }
 
 static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE (device);
 
 	if (!_is_internal_interface (device))
 		return NM_ACT_STAGE_RETURN_IP_FAIL;
 
-	if (!nm_device_get_ip_ifindex (device)) {
+	if (nm_device_get_ip_ifindex (device) <= 0) {
 		priv->waiting_for_interface = TRUE;
 		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
-	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static gboolean
@@ -206,7 +190,6 @@ nm_device_ovs_interface_class_init (NMDeviceOvsInterfaceClass *klass)
 	device_class->is_available = is_available;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->link_changed = link_changed;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->can_unmanaged_external_down = can_unmanaged_external_down;
 }
diff --git a/src/devices/ovs/nm-device-ovs-port.c b/src/devices/ovs/nm-device-ovs-port.c
index 1f9afbab..b96eba68 100644
--- a/src/devices/ovs/nm-device-ovs-port.c
+++ b/src/devices/ovs/nm-device-ovs-port.c
@@ -71,17 +71,10 @@ get_generic_capabilities (NMDevice *device)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	return NM_ACT_STAGE_RETURN_IP_FAIL;
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
 	return NM_ACT_STAGE_RETURN_IP_FAIL;
 }
@@ -186,8 +179,7 @@ nm_device_ovs_port_class_init (NMDeviceOvsPortClass *klass)
 	device_class->get_type_description = get_type_description;
 	device_class->create_and_realize = create_and_realize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->enslave_slave = enslave_slave;
 	device_class->release_slave = release_slave;
 }
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index 494e2ea0..9d73c3ac 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -21,7 +21,6 @@
 
 #include "nm-ovsdb.h"
 
-#include <string.h>
 #include <gmodule.h>
 #include <gio/gunixsocketaddress.h>
 
@@ -1118,7 +1117,7 @@ ovsdb_got_msg (NMOvsdb *self, json_t *msg)
 			ovsdb_disconnect (self, FALSE);
 			return;
 		}
-		/* Cool, we found a corresponsing call. Finish it. */
+		/* Cool, we found a corresponding call. Finish it. */
 
 		_call_trace ("response", call, msg);
 
diff --git a/src/devices/team/meson.build b/src/devices/team/meson.build
index 0f0763bd..3f755012 100644
--- a/src/devices/team/meson.build
+++ b/src/devices/team/meson.build
@@ -1,12 +1,12 @@
 sources = files(
   'nm-device-team.c',
-  'nm-team-factory.c'
+  'nm-team-factory.c',
 )
 
 deps = [
   jansson_dep,
   libteamdctl_dep,
-  nm_dep
+  nm_dep,
 ]
 
 libnm_device_plugin_team = shared_module(
@@ -16,7 +16,7 @@ libnm_device_plugin_team = shared_module(
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_team
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index 899932fd..38a6dd8c 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -34,6 +34,7 @@
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
 #include "platform/nm-platform.h"
+#include "nm-config.h"
 #include "nm-core-internal.h"
 #include "nm-ip4-config.h"
 #include "nm-dbus-compat.h"
@@ -479,11 +480,25 @@ teamd_child_setup (gpointer user_data)
 	signal (SIGPIPE, SIG_IGN);
 }
 
+static const char **
+teamd_env (void)
+{
+	const char **env = g_new0 (const char *, 2);
+
+	if (nm_config_get_is_debug (nm_config_get ()))
+		env[0] = "TEAM_LOG_OUTPUT=stderr";
+	else
+		env[0] = "TEAM_LOG_OUTPUT=syslog";
+
+	return env;
+}
+
 static gboolean
 teamd_kill (NMDeviceTeam *self, const char *teamd_binary, GError **error)
 {
 	gs_unref_ptrarray GPtrArray *argv = NULL;
 	gs_free char *tmp_str = NULL;
+	gs_free const char **envp = NULL;
 
 	if (!teamd_binary) {
 		teamd_binary = nm_utils_find_helper ("teamd", NULL, error);
@@ -500,8 +515,11 @@ teamd_kill (NMDeviceTeam *self, const char *teamd_binary, GError **error)
 	g_ptr_array_add (argv, (gpointer) nm_device_get_iface (NM_DEVICE (self)));
 	g_ptr_array_add (argv, NULL);
 
+	envp = teamd_env ();
+
 	_LOGD (LOGD_TEAM, "running: %s", (tmp_str = g_strjoinv (" ", (char **) argv->pdata)));
-	return g_spawn_sync ("/", (char **) argv->pdata, NULL, 0, teamd_child_setup, NULL, NULL, NULL, NULL, error);
+	return g_spawn_sync ("/", (char **) argv->pdata, (char **) envp, 0,
+	                     teamd_child_setup, NULL, NULL, NULL, NULL, error);
 }
 
 static gboolean
@@ -518,6 +536,7 @@ teamd_start (NMDevice *device, NMConnection *connection)
 	nm_auto_free const char *config_free = NULL;
 	NMSettingTeam *s_team;
 	gs_free char *cloned_mac = NULL;
+	gs_free const char **envp = NULL;
 
 	s_team = nm_connection_get_setting_team (connection);
 	g_return_val_if_fail (s_team, FALSE);
@@ -588,8 +607,10 @@ teamd_start (NMDevice *device, NMConnection *connection)
 		g_ptr_array_add (argv, (gpointer) "-gg");
 	g_ptr_array_add (argv, NULL);
 
+	envp = teamd_env ();
+
 	_LOGD (LOGD_TEAM, "running: %s", (tmp_str = g_strjoinv (" ", (char **) argv->pdata)));
-	if (!g_spawn_async ("/", (char **) argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
+	if (!g_spawn_async ("/", (char **) argv->pdata, (char **) envp, G_SPAWN_DO_NOT_REAP_CHILD,
 	                    teamd_child_setup, NULL, &priv->teamd_pid, &error)) {
 		_LOGW (LOGD_TEAM, "Activation: (team) failed to start teamd: %s", error->message);
 		teamd_cleanup (device, TRUE);
@@ -626,6 +647,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	connection = nm_device_get_applied_connection (device);
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
 	s_team = nm_connection_get_setting_team (connection);
 	g_return_val_if_fail (s_team, NM_ACT_STAGE_RETURN_FAILURE);
 
@@ -782,15 +804,15 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformError plerr;
+	int r;
 
-	plerr = nm_platform_link_team_add (nm_device_get_platform (device), iface, out_plink);
-	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+	r = nm_platform_link_team_add (nm_device_get_platform (device), iface, out_plink);
+	if (r < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create team master interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string_a (plerr));
+		             nm_strerror (r));
 		return FALSE;
 	}
 
diff --git a/src/devices/team/nm-team-factory.c b/src/devices/team/nm-team-factory.c
index d062429f..f18b943d 100644
--- a/src/devices/team/nm-team-factory.c
+++ b/src/devices/team/nm-team-factory.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <gmodule.h>
 
 #include "nm-manager.h"
diff --git a/src/devices/tests/meson.build b/src/devices/tests/meson.build
index 02c61ced..4702c656 100644
--- a/src/devices/tests/meson.build
+++ b/src/devices/tests/meson.build
@@ -1,18 +1,19 @@
 test_units = [
   'test-acd',
-  'test-lldp'
+  'test-lldp',
 ]
 
 foreach test_unit: test_units
   exe = executable(
     test_unit,
     test_unit + '.c',
-    dependencies: test_nm_dep
+    dependencies: test_nm_dep,
   )
 
   test(
     'devices/' + test_unit,
     test_script,
-    args: test_args + [exe.full_path()]
+    args: test_args + [exe.full_path()],
+    timeout: default_test_timeout,
   )
 endforeach
diff --git a/src/devices/tests/test-acd.c b/src/devices/tests/test-acd.c
index 8a2852a2..aff71825 100644
--- a/src/devices/tests/test-acd.c
+++ b/src/devices/tests/test-acd.c
@@ -20,6 +20,8 @@
 
 #include "nm-default.h"
 
+#include "n-acd/src/n-acd.h"
+
 #include "devices/nm-acd-manager.h"
 #include "platform/tests/test-common.h"
 
@@ -31,6 +33,46 @@
 #define ADDR3 0x03030303
 #define ADDR4 0x04040404
 
+/*****************************************************************************/
+
+static gboolean
+_skip_acd_test_check (void)
+{
+	NAcd *acd;
+	NAcdConfig *config;
+	const guint8 hwaddr[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
+	int r;
+	static int skip = -1;
+
+	if (skip == -1) {
+		r = n_acd_config_new (&config);
+		g_assert (r == 0);
+
+		n_acd_config_set_ifindex (config, 1);
+		n_acd_config_set_transport (config, N_ACD_TRANSPORT_ETHERNET);
+		n_acd_config_set_mac (config, hwaddr, sizeof (hwaddr));
+
+		r = n_acd_new (&acd, config);
+		n_acd_config_free (config);
+		if (r == 0)
+			n_acd_unref (acd);
+
+		skip = (r != 0);
+	}
+	return skip;
+}
+
+#define _skip_acd_test() \
+	({ \
+		gboolean _skip = _skip_acd_test_check (); \
+		\
+		if (_skip) \
+			g_test_skip ("Cannot create NAcd. Running under valgind?"); \
+		_skip; \
+	})
+
+/*****************************************************************************/
+
 typedef struct {
 	int ifindex0;
 	int ifindex1;
@@ -61,20 +103,26 @@ typedef struct {
 } TestInfo;
 
 static void
-acd_manager_probe_terminated (NMAcdManager *acd_manager, GMainLoop *loop)
+acd_manager_probe_terminated (NMAcdManager *acd_manager, gpointer user_data)
 {
-	g_main_loop_quit (loop);
+	g_main_loop_quit (user_data);
 }
 
 static void
 test_acd_common (test_fixture *fixture, TestInfo *info)
 {
-	gs_unref_object NMAcdManager *manager = NULL;
+	NMAcdManager *manager;
 	GMainLoop *loop;
 	int i;
 	const guint WAIT_TIME_OPTIMISTIC = 50;
 	guint wait_time;
-	gulong signal_id;
+	static const NMAcdCallbacks callbacks = {
+		.probe_terminated_callback = acd_manager_probe_terminated,
+		.user_data_destroy         = (GDestroyNotify) g_main_loop_unref,
+	};
+
+	if (_skip_acd_test ())
+		return;
 
 	/* first, try with a short waittime. We hope that this is long enough
 	 * to successfully complete the test. Only if that's not the case, we
@@ -83,7 +131,13 @@ test_acd_common (test_fixture *fixture, TestInfo *info)
 	wait_time = WAIT_TIME_OPTIMISTIC;
 again:
 
-	manager = nm_acd_manager_new (fixture->ifindex0, fixture->hwaddr0, fixture->hwaddr0_len);
+	loop = g_main_loop_new (NULL, FALSE);
+
+	manager = nm_acd_manager_new (fixture->ifindex0,
+	                              fixture->hwaddr0,
+	                              fixture->hwaddr0_len,
+	                              &callbacks,
+	                              g_main_loop_ref (loop));
 	g_assert (manager != NULL);
 
 	for (i = 0; info->addresses[i]; i++)
@@ -94,16 +148,13 @@ again:
 		                        24, 0, 3600, 1800, 0, NULL);
 	}
 
-	loop = g_main_loop_new (NULL, FALSE);
-	signal_id = g_signal_connect (manager, NM_ACD_MANAGER_PROBE_TERMINATED,
-	                              G_CALLBACK (acd_manager_probe_terminated), loop);
 	g_assert (nm_acd_manager_start_probe (manager, wait_time));
 	g_assert (nmtst_main_loop_run (loop, 2000));
-	g_signal_handler_disconnect (manager, signal_id);
 	g_main_loop_unref (loop);
 
 	for (i = 0; info->addresses[i]; i++) {
 		gboolean val;
+		char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 		val = nm_acd_manager_check_address (manager, info->addresses[i]);
 		if (val == info->expected_result[i])
@@ -113,14 +164,16 @@ again:
 			/* probably we just had a glitch and the system took longer than
 			 * expected. Re-verify with a large timeout this time. */
 			wait_time = 1000;
-			g_clear_object (&manager);
+			nm_clear_pointer (&manager, nm_acd_manager_free);
 			goto again;
 		}
 
 		g_error ("expected check for address #%d (%s) to %s, but it didn't",
-		         i, nm_utils_inet4_ntop (info->addresses[i], NULL),
+		         i, nm_utils_inet4_ntop (info->addresses[i], sbuf),
 		         info->expected_result[i] ? "detect no duplicated" : "detect a duplicate");
 	}
+
+	nm_acd_manager_free (manager);
 }
 
 static void
@@ -146,10 +199,17 @@ test_acd_probe_2 (test_fixture *fixture, gconstpointer user_data)
 static void
 test_acd_announce (test_fixture *fixture, gconstpointer user_data)
 {
-	gs_unref_object NMAcdManager *manager = NULL;
+	NMAcdManager *manager;
 	GMainLoop *loop;
 
-	manager = nm_acd_manager_new (fixture->ifindex0, fixture->hwaddr0, fixture->hwaddr0_len);
+	if (_skip_acd_test ())
+		return;
+
+	manager = nm_acd_manager_new (fixture->ifindex0,
+	                              fixture->hwaddr0,
+	                              fixture->hwaddr0_len,
+	                              NULL,
+	                              NULL);
 	g_assert (manager != NULL);
 
 	g_assert (nm_acd_manager_add_address (manager, ADDR1));
@@ -159,6 +219,8 @@ test_acd_announce (test_fixture *fixture, gconstpointer user_data)
 	nm_acd_manager_announce_addresses (manager);
 	g_assert (!nmtst_main_loop_run (loop, 200));
 	g_main_loop_unref (loop);
+
+	nm_acd_manager_free (manager);
 }
 
 static void
diff --git a/src/devices/wifi/meson.build b/src/devices/wifi/meson.build
index 2745040b..4dfbe4c8 100644
--- a/src/devices/wifi/meson.build
+++ b/src/devices/wifi/meson.build
@@ -1,13 +1,15 @@
 common_sources = files(
   'nm-wifi-ap.c',
-  'nm-wifi-utils.c'
+  'nm-wifi-p2p-peer.c',
+  'nm-wifi-utils.c',
 )
 
 sources = common_sources + files(
-  'nm-wifi-factory.c',
-  'nm-wifi-common.c',
+  'nm-device-olpc-mesh.c',
+  'nm-device-wifi-p2p.c',
   'nm-device-wifi.c',
-  'nm-device-olpc-mesh.c'
+  'nm-wifi-common.c',
+  'nm-wifi-factory.c',
 )
 
 if enable_iwd
@@ -18,7 +20,7 @@ if enable_iwd
 endif
 
 deps = [
-  nm_dep
+  nm_dep,
 ]
 
 libnm_device_plugin_wifi = shared_module(
@@ -28,7 +30,7 @@ libnm_device_plugin_wifi = shared_module(
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_wifi
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index 1d1be742..dcc161d2 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -22,8 +22,6 @@
 
 #include "nm-device-iwd.h"
 
-#include <string.h>
-
 #include "nm-common-macros.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-private.h"
@@ -41,6 +39,7 @@
 #include "nm-config.h"
 #include "nm-iwd-manager.h"
 #include "nm-dbus-manager.h"
+#include "nm-dbus-compat.h"
 
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceIwd);
@@ -69,6 +68,8 @@ typedef struct {
 	GDBusObject *   dbus_obj;
 	GDBusProxy *    dbus_device_proxy;
 	GDBusProxy *    dbus_station_proxy;
+	GDBusProxy *    dbus_ap_proxy;
+	GDBusProxy *    dbus_adhoc_proxy;
 	CList           aps_lst_head;
 	NMWifiAP *      current_ap;
 	GCancellable *  cancellable;
@@ -80,6 +81,7 @@ typedef struct {
 	bool            can_connect:1;
 	bool            scanning:1;
 	bool            scan_requested:1;
+	bool            act_mode_switch:1;
 	gint64          last_scan;
 } NMDeviceIwdPrivate;
 
@@ -104,7 +106,7 @@ G_DEFINE_TYPE (NMDeviceIwd, nm_device_iwd, NM_TYPE_DEVICE)
 /*****************************************************************************/
 
 static void schedule_periodic_scan (NMDeviceIwd *self,
-                                    NMDeviceState current_state);
+                                    gboolean initial_scan);
 
 /*****************************************************************************/
 
@@ -229,7 +231,11 @@ vardict_from_network_type (const char *type)
 }
 
 static void
-insert_ap_from_network (GHashTable *aps, const char *path, int16_t signal, uint32_t ap_id)
+insert_ap_from_network (NMDeviceIwd *self,
+                        GHashTable *aps,
+                        const char *path,
+                        int16_t signal,
+                        uint32_t ap_id)
 {
 	gs_unref_object GDBusProxy *network_proxy = NULL;
 	gs_unref_variant GVariant *name_value = NULL, *type_value = NULL;
@@ -240,6 +246,11 @@ insert_ap_from_network (GHashTable *aps, const char *path, int16_t signal, uint3
 	uint8_t bssid[6];
 	NMWifiAP *ap;
 
+	if (g_hash_table_lookup (aps, path)) {
+		_LOGD (LOGD_WIFI, "Duplicate network at %s", path);
+		return;
+	}
+
 	network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
 	                                                   path,
 	                                                   NM_IWD_NETWORK_INTERFACE);
@@ -304,7 +315,7 @@ static void
 get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 {
 	NMDeviceIwd *self = user_data;
-	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDeviceIwdPrivate *priv;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *variant = NULL;
 	GVariantIter *networks;
@@ -314,33 +325,42 @@ get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	gboolean changed = FALSE;
 	GHashTableIter ap_iter;
 	gs_unref_hashtable GHashTable *new_aps = NULL;
-	/* Depending on whether we're using the Station interface or the Device
-	 * interface for compatibility with IWD <= 0.7, the return signature of
-	 * GetOrderedNetworks will be different.
-	 */
-	gboolean compat = priv->dbus_station_proxy == priv->dbus_device_proxy;
-	const char *return_sig = compat ? "(a(osns))" : "(a(on))";
+	gboolean compat;
+	const char *return_sig;
 	static uint32_t ap_id = 0;
 
-	variant = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
-	                                      G_VARIANT_TYPE (return_sig),
-	                                      &error);
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
 	if (!variant) {
 		_LOGE (LOGD_WIFI, "Station.GetOrderedNetworks failed: %s",
 		       error->message);
 		return;
 	}
 
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	/* Depending on whether we're using the Station interface or the Device
+	 * interface for compatibility with IWD <= 0.7, the return signature of
+	 * GetOrderedNetworks will be different.
+	 */
+	compat = priv->dbus_station_proxy == priv->dbus_device_proxy;
+	return_sig = compat ? "(a(osns))" : "(a(on))";
+
+	if (!g_variant_is_of_type (variant, G_VARIANT_TYPE (return_sig))) {
+		_LOGE (LOGD_WIFI, "Station.GetOrderedNetworks returned type %s instead of %s",
+		       g_variant_get_type_string (variant), return_sig);
+		return;
+	}
+
 	new_aps = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_object_unref);
 
 	g_variant_get (variant, return_sig, &networks);
 
 	if (compat) {
 		while (g_variant_iter_next (networks, "(&o&sn&s)", &path, &name, &signal, &type))
-			insert_ap_from_network (new_aps, path, signal, ap_id++);
+			insert_ap_from_network (self, new_aps, path, signal, ap_id++);
 	} else {
 		while (g_variant_iter_next (networks, "(&on)", &path, &signal))
-			insert_ap_from_network (new_aps, path, signal, ap_id++);
+			insert_ap_from_network (self, new_aps, path, signal, ap_id++);
 	}
 
 	g_variant_iter_free (networks);
@@ -393,7 +413,7 @@ update_aps (NMDeviceIwd *self)
 		priv->cancellable = g_cancellable_new ();
 
 	g_dbus_proxy_call (priv->dbus_station_proxy, "GetOrderedNetworks",
-	                   g_variant_new ("()"), G_DBUS_CALL_FLAGS_NONE,
+	                   NULL, G_DBUS_CALL_FLAGS_NONE,
 	                   2000, priv->cancellable,
 	                   get_ordered_networks_cb, self);
 }
@@ -403,8 +423,8 @@ send_disconnect (NMDeviceIwd *self)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
-	g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect", g_variant_new ("()"),
-	                   G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+	g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect",
+	                   NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 }
 
 static void
@@ -431,55 +451,104 @@ cleanup_association_attempt (NMDeviceIwd *self, gboolean disconnect)
 }
 
 static void
+reset_mode (NMDeviceIwd *self,
+            GCancellable *cancellable,
+            GAsyncReadyCallback callback,
+            gpointer user_data)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	g_dbus_proxy_call (priv->dbus_device_proxy,
+	                   DBUS_INTERFACE_PROPERTIES ".Set",
+	                   g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE,
+	                                  "Mode",
+	                                  g_variant_new_string ("station")),
+	                   G_DBUS_CALL_FLAGS_NONE, 2000,
+	                   cancellable,
+	                   callback,
+	                   user_data);
+}
+
+static void
 deactivate (NMDevice *device)
 {
-	cleanup_association_attempt (NM_DEVICE_IWD (device), TRUE);
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (!priv->dbus_obj)
+		return;
+
+	cleanup_association_attempt (self, TRUE);
+	priv->act_mode_switch = FALSE;
+
+	if (!priv->dbus_station_proxy)
+		reset_mode (self, NULL, NULL, NULL);
 }
 
-static gboolean
-deactivate_async_finish (NMDevice *device, GAsyncResult *res, GError **error)
+static void
+disconnect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 {
-	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (NM_DEVICE_IWD (device));
+	gs_unref_object NMDeviceIwd *self = NULL;
+	NMDeviceDeactivateCallback callback;
+	gpointer callback_user_data;
 	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
 
-	variant = g_dbus_proxy_call_finish (priv->dbus_station_proxy, res, error);
-	return variant != NULL;
-}
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
 
-typedef struct {
-	NMDeviceIwd *self;
-	GAsyncReadyCallback callback;
-	gpointer user_data;
-} DeactivateContext;
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	callback (NM_DEVICE (self), error, callback_user_data);
+}
 
 static void
-disconnect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+disconnect_cb_on_idle (gpointer user_data,
+                       GCancellable *cancellable)
 {
-	DeactivateContext *ctx = user_data;
+	gs_unref_object NMDeviceIwd *self = NULL;
+	NMDeviceDeactivateCallback callback;
+	gpointer callback_user_data;
+	gs_free_error GError *cancelled_error = NULL;
 
-	ctx->callback (G_OBJECT (ctx->self), res, ctx->user_data);
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
 
-	g_object_unref (ctx->self);
-	g_slice_free (DeactivateContext, ctx);
+	g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error);
+	callback (NM_DEVICE (self), cancelled_error, callback_user_data);
 }
 
 static void
 deactivate_async (NMDevice *device,
                   GCancellable *cancellable,
-                  GAsyncReadyCallback callback,
-                  gpointer user_data)
+                  NMDeviceDeactivateCallback callback,
+                  gpointer callback_user_data)
 {
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
-	DeactivateContext *ctx;
+	gpointer user_data;
 
-	ctx = g_slice_new0 (DeactivateContext);
-	ctx->self = g_object_ref (self);
-	ctx->callback = callback;
-	ctx->user_data = user_data;
+	nm_assert (G_IS_CANCELLABLE (cancellable));
+	nm_assert (callback);
 
-	g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect", g_variant_new ("()"),
-	                   G_DBUS_CALL_FLAGS_NONE, -1, cancellable, disconnect_cb, ctx);
+	user_data = nm_utils_user_data_pack (g_object_ref (self), callback, callback_user_data);
+
+	if (!priv->dbus_obj) {
+		nm_utils_invoke_on_idle (disconnect_cb_on_idle, user_data, cancellable);
+		return;
+	}
+
+	cleanup_association_attempt (self, FALSE);
+	priv->act_mode_switch = FALSE;
+
+	if (priv->dbus_station_proxy) {
+		g_dbus_proxy_call (priv->dbus_station_proxy,
+		                   "Disconnect",
+		                   NULL,
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   cancellable,
+		                   disconnect_cb,
+		                   user_data);
+	} else
+		reset_mode (self, cancellable, disconnect_cb, user_data);
 }
 
 static gboolean
@@ -510,13 +579,37 @@ is_connection_known_network (NMConnection *connection)
 }
 
 static gboolean
+is_ap_known_network (NMWifiAP *ap)
+{
+	GDBusProxy *network_proxy;
+	gs_unref_variant GVariant *known_network = NULL;
+
+	network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
+	                                                   nm_wifi_ap_get_supplicant_path (ap),
+	                                                   NM_IWD_NETWORK_INTERFACE);
+	if (!network_proxy)
+		return FALSE;
+
+	known_network = g_dbus_proxy_get_cached_property (network_proxy, "KnownNetwork");
+	g_object_unref (network_proxy);
+
+	return    known_network
+	       && g_variant_is_of_type (known_network, G_VARIANT_TYPE_OBJECT_PATH);
+}
+
+static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMSettingWireless *s_wireless;
 	const char *mac;
 	const char * const *mac_blacklist;
 	int i;
 	const char *perm_hw_addr;
+	const char *mode;
+	NMIwdNetworkSecurity security;
+	gboolean mapped;
 
 	if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
@@ -549,23 +642,65 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 		return FALSE;
 	}
 
-	if (!NM_IN_STRSET (nm_setting_wireless_get_mode (s_wireless),
-	                   NULL,
-	                   NM_SETTING_WIRELESS_MODE_INFRA)) {
+	/* Hidden SSIDs not supported in any mode (client or AP) */
+	if (nm_setting_wireless_get_hidden (s_wireless)) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "IWD only support infrastructure type profiles");
+		                            "hidden networks not supported by the IWD backend");
 		return FALSE;
 	}
 
-	/* 8021x networks can only be used if they've been provisioned on the IWD side and
-	 * thus are Known Networks.
-	 */
-	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection)) {
+	security = nm_wifi_connection_get_iwd_security (connection, &mapped);
+	if (!mapped) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "connection authentication type not supported by IWD backend");
+		return FALSE;
+	}
+
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		/* 8021x networks can only be used if they've been provisioned on the IWD side and
+		 * thus are Known Networks.
+		 */
+		if (security == NM_IWD_NETWORK_SECURITY_8021X) {
+			if (!is_connection_known_network (connection)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "802.1x connections must have IWD provisioning files");
+				return FALSE;
+			}
+		} else if (!NM_IN_SET (security, NM_IWD_NETWORK_SECURITY_NONE, NM_IWD_NETWORK_SECURITY_PSK)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "IWD backend only supports Open, PSK and 802.1x network "
+			                            "authentication in Infrastructure mode");
+			return FALSE;
+		}
+	} else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_AP)) {
+		if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "device does not support Access Point mode");
+			return FALSE;
+		}
+
+		if (!NM_IN_SET (security, NM_IWD_NETWORK_SECURITY_PSK)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "IWD backend only supports PSK authentication in AP mode");
+			return FALSE;
+		}
+	} else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) {
+		if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "device does not support Ad-Hoc mode");
+			return FALSE;
+		}
+
+		if (!NM_IN_SET (security, NM_IWD_NETWORK_SECURITY_NONE, NM_IWD_NETWORK_SECURITY_PSK)) {
 			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-			                            "802.1x profile is not a known network");
+			                            "IWD backend only supports Open and PSK authentication in Ad-Hoc mode");
 			return FALSE;
 		}
+	} else {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "%s type profiles not supported by IWD backend");
+		return FALSE;
 	}
 
 	return TRUE;
@@ -582,42 +717,15 @@ check_connection_available (NMDevice *device,
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
 	const char *mode;
+	NMWifiAP *ap = NULL;
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wifi, FALSE);
 
-	/* Only Infrastrusture mode at this time */
-	mode = nm_setting_wireless_get_mode (s_wifi);
-	if (!NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "iwd only supports infrastructure mode connections");
-		return FALSE;
-	}
-
-	/* Hidden SSIDs not supported yet */
-	if (nm_setting_wireless_get_hidden (s_wifi)) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "hidden networks not supported by iwd");
-		return FALSE;
-	}
-
-	/* 8021x networks can only be used if they've been provisioned on the IWD side and
-	 * thus are Known Networks.
-	 */
-	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection)) {
-			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-			                            "network is not known to iwd");
-			return FALSE;
-		}
-	}
-
 	/* a connection that is available for a certain @specific_object, MUST
 	 * also be available in general (without @specific_object). */
 
 	if (specific_object) {
-		NMWifiAP *ap;
-
 		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
 		if (!ap) {
 			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
@@ -629,18 +737,36 @@ check_connection_available (NMDevice *device,
 			                            "requested access point is not compatible with profile");
 			return FALSE;
 		}
-		return TRUE;
 	}
 
+	/* AP and Ad-Hoc connections can be activated independent of the scan list */
+	mode = nm_setting_wireless_get_mode (s_wifi);
+	if (NM_IN_STRSET (mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC))
+		return TRUE;
+
 	if (NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP))
 		return TRUE;
 
-	if (!nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection)) {
+	if (!ap)
+		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+
+	if (!ap) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
 		                            "no compatible access point found");
 		return FALSE;
 	}
 
+	/* 8021x networks can only be used if they've been provisioned on the IWD side and
+	 * thus are Known Networks.
+	 */
+	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_ap_known_network (ap)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "802.1x network is not an IWD Known Network (missing provisioning file?)");
+			return FALSE;
+		}
+	}
+
 	return TRUE;
 }
 
@@ -666,15 +792,11 @@ complete_connection (NMDevice *device,
 
 	mode = s_wifi ? nm_setting_wireless_get_mode (s_wifi) : NULL;
 
-	if (mode && !nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_INFRA)) {
-		g_set_error_literal (error,
-		                     NM_DEVICE_ERROR,
-		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
-		                     "Only Infrastructure mode is supported.");
-		return FALSE;
-	}
-
-	if (!specific_object) {
+	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) {
+		if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
+			return FALSE;
+		ap = NULL;
+	} else if (!specific_object) {
 		/* If not given a specific object, we need at minimum an SSID */
 		if (!s_wifi) {
 			g_set_error_literal (error,
@@ -696,11 +818,16 @@ complete_connection (NMDevice *device,
 		/* Find a compatible AP in the scan list */
 		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 		if (!ap) {
-			g_set_error_literal (error,
-			                     NM_DEVICE_ERROR,
-			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
-			                     "No compatible AP in the scan list and hidden SSIDs not supported.");
-			return FALSE;
+			if (!nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) {
+				g_set_error_literal (error,
+				                     NM_DEVICE_ERROR,
+				                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+				                     "No compatible AP in the scan list and hidden SSIDs not supported.");
+				return FALSE;
+			}
+
+			if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
+				return FALSE;
 		}
 	} else {
 		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
@@ -720,7 +847,10 @@ complete_connection (NMDevice *device,
 		nm_connection_add_setting (connection, NM_SETTING (s_wifi));
 	}
 
-	ssid = nm_wifi_ap_get_ssid (ap);
+	ssid = nm_setting_wireless_get_ssid (s_wifi);
+	if (!ssid && ap)
+		ssid = nm_wifi_ap_get_ssid (ap);
+
 	if (!ssid) {
 		g_set_error_literal (error,
 		                     NM_DEVICE_ERROR,
@@ -729,11 +859,13 @@ complete_connection (NMDevice *device,
 		return FALSE;
 	}
 
-	if (!nm_wifi_ap_complete_connection (ap,
-	                                     connection,
-	                                     nm_wifi_utils_is_manf_default_ssid (ssid),
-	                                     error))
-		return FALSE;
+	if (ap) {
+		if (!nm_wifi_ap_complete_connection (ap,
+		                                     connection,
+		                                     nm_wifi_utils_is_manf_default_ssid (ssid),
+		                                     error))
+			return FALSE;
+	}
 
 	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
 	nm_utils_complete_generic (nm_device_get_platform (device),
@@ -745,19 +877,6 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           TRUE);
 
-	/* 8021x networks can only be used if they've been provisioned on the IWD side and
-	 * thus are Known Networks.
-	 */
-	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection)) {
-			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
-			                     "This 8021x network has not been provisioned on this machine");
-			return FALSE;
-		}
-	}
-
 	perm_hw_addr = nm_device_get_permanent_hw_address (device);
 	if (perm_hw_addr) {
 		setting_mac = nm_setting_wireless_get_mac_address (s_wifi);
@@ -820,8 +939,21 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 {
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
-
-	return priv->enabled && priv->dbus_station_proxy;
+	NMDeviceState state = nm_device_get_state (device);
+
+	/* Available if either the device is UP and in station mode
+	 * or in AP/Ad-Hoc modes while activating or activated.  Device
+	 * may be temporarily DOWN while activating or deactivating and
+	 * we don't want it to be marked unavailable because of this.
+	 *
+	 * For reference:
+	 * We call nm_device_queue_recheck_available whenever
+	 * priv->enabled changes or priv->dbus_station_proxy changes.
+	 */
+	return    priv->dbus_obj
+	       && priv->enabled
+	       && (   priv->dbus_station_proxy
+	           || (state >= NM_DEVICE_STATE_CONFIG && state <= NM_DEVICE_STATE_DEACTIVATING));
 }
 
 static gboolean
@@ -829,8 +961,7 @@ get_autoconnect_allowed (NMDevice *device)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (NM_DEVICE_IWD (device));
 
-	return is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE)
-	       && priv->can_connect;
+	return priv->can_connect;
 }
 
 static gboolean
@@ -856,7 +987,9 @@ can_auto_connect (NMDevice *device,
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wifi, FALSE);
 
-	/* Only Infrastrusture mode */
+	/* Don't auto-activate AP or Ad-Hoc connections.
+	 * Note the wpa_supplicant backend has the opposite policy.
+	 */
 	mode = nm_setting_wireless_get_mode (s_wifi);
 	if (mode && g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
 		return FALSE;
@@ -870,14 +1003,6 @@ can_auto_connect (NMDevice *device,
 			return FALSE;
 	}
 
-	/* 8021x networks can only be used if they've been provisioned on the IWD side and
-	 * thus are Known Networks.
-	 */
-	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection))
-			return FALSE;
-	}
-
 	ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 	if (ap) {
 		/* All good; connection is usable */
@@ -908,11 +1033,11 @@ scan_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 {
 	NMDeviceIwd *self = user_data;
 	NMDeviceIwdPrivate *priv;
+	gs_unref_variant GVariant *variant = NULL;
 	gs_free_error GError *error = NULL;
 
-	if (   !_nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
-	                                   G_VARIANT_TYPE ("()"), &error)
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant && nm_utils_error_is_cancelled (error, FALSE))
 		return;
 
 	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
@@ -925,11 +1050,8 @@ scan_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	 * scheduled when priv->scanning goes back to false.  On error,
 	 * schedule a retry now.
 	 */
-	if (error && !priv->scanning) {
-		NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
-
-		schedule_periodic_scan (self, state);
-	}
+	if (error && !priv->scanning)
+		schedule_periodic_scan (self, FALSE);
 }
 
 static void
@@ -958,9 +1080,7 @@ dbus_request_scan_cb (NMDevice *device,
 
 	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
-	if (   !priv->can_scan
-	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
-	    || nm_device_is_activating (device)) {
+	if (!priv->can_scan) {
 		g_dbus_method_invocation_return_error_literal (context,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
@@ -982,8 +1102,7 @@ dbus_request_scan_cb (NMDevice *device,
 
 	if (!priv->scanning && !priv->scan_requested) {
 		g_dbus_proxy_call (priv->dbus_station_proxy, "Scan",
-		                   g_variant_new ("()"),
-		                   G_DBUS_CALL_FLAGS_NONE, -1,
+		                   NULL, G_DBUS_CALL_FLAGS_NONE, -1,
 		                   priv->cancellable, scan_cb, self);
 		priv->scan_requested = TRUE;
 	}
@@ -999,9 +1118,7 @@ _nm_device_iwd_request_scan (NMDeviceIwd *self,
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
 
-	if (   !priv->can_scan
-	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
-	    || nm_device_is_activating (device)) {
+	if (!priv->can_scan) {
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
@@ -1013,7 +1130,7 @@ _nm_device_iwd_request_scan (NMDeviceIwd *self,
 	                       NM_DEVICE_AUTH_REQUEST,
 	                       invocation,
 	                       NULL,
-	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                       NM_AUTH_PERMISSION_WIFI_SCAN,
 	                       TRUE,
 	                       dbus_request_scan_cb,
 	                       options ? g_variant_ref (options) : NULL);
@@ -1041,8 +1158,6 @@ scanning_prohibited (NMDeviceIwd *self, gboolean periodic)
 		return TRUE;
 	case NM_DEVICE_STATE_DISCONNECTED:
 	case NM_DEVICE_STATE_FAILED:
-		/* Can always scan when disconnected */
-		return FALSE;
 	case NM_DEVICE_STATE_ACTIVATED:
 		break;
 	}
@@ -1199,7 +1314,7 @@ wifi_secrets_cb (NMActRequest *req,
 
 	priv->wifi_secrets_id = NULL;
 
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+	if (nm_utils_error_is_cancelled (error, FALSE)) {
 		g_dbus_method_invocation_return_error_literal (invocation, NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                                               "NM secrets request cancelled");
@@ -1263,7 +1378,7 @@ wifi_secrets_get_one (NMDeviceIwd *self,
 	                                                    TRUE,
 	                                                    setting_name,
 	                                                    flags,
-	                                                    setting_key,
+	                                                    NM_MAKE_STRV (setting_key),
 	                                                    wifi_secrets_cb,
 	                                                    nm_utils_user_data_pack (self, invocation));
 }
@@ -1274,6 +1389,7 @@ network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	NMDeviceIwd *self = user_data;
 	NMDevice *device = NM_DEVICE (self);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	gs_unref_variant GVariant *variant = NULL;
 	gs_free_error GError *error = NULL;
 	NMConnection *connection;
 	NMSettingWireless *s_wifi;
@@ -1282,9 +1398,8 @@ network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED;
 	GVariant *value;
 
-	if (!_nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
-	                                 G_VARIANT_TYPE ("()"),
-	                                 &error)) {
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant) {
 		gs_free char *dbus_error = NULL;
 
 		/* Connection failed; radio problems or if the network wasn't
@@ -1295,7 +1410,7 @@ network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 		       "Activation: (wifi) Network.Connect failed: %s",
 		       error->message);
 
-		if (nm_utils_error_is_cancelled (error, TRUE))
+		if (nm_utils_error_is_cancelled (error, FALSE))
 			return;
 
 		if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH))
@@ -1359,12 +1474,262 @@ failed:
 }
 
 static void
+act_failed_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDevice *device = NM_DEVICE (self);
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant && nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	/* Change state to FAILED unless already done by state_changed
+	 * which may have been triggered by the station interface
+	 * appearing on DBus.
+	 */
+	if (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG)
+		nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+}
+
+static void
+act_start_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+	NMSettingWireless *s_wireless;
+	GBytes *ssid;
+	gs_free char *ssid_utf8 = NULL;
+
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant) {
+		_LOGE (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi) Network.Connect failed: %s",
+		       error->message);
+
+		if (nm_utils_error_is_cancelled (error, FALSE))
+			return;
+
+		if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG))
+			return;
+
+		goto error;
+	}
+
+	nm_assert (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG);
+
+	s_wireless = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+	if (!s_wireless)
+		goto error;
+
+	ssid = nm_setting_wireless_get_ssid (s_wireless);
+	if (!ssid)
+		goto error;
+
+	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) Stage 2 of 5 (Device Configure) successful.  Started '%s'.",
+	       ssid_utf8);
+
+	nm_device_activate_schedule_stage3_ip_config_start (device);
+	return;
+
+error:
+	reset_mode (self, priv->cancellable, act_failed_cb, self);
+}
+
+/* Check if we're activating an AP/AdHoc connection and if the target
+ * DBus interface has appeared already.  If so proceed to call Start or
+ * StartOpen on that interface.
+ */
+static void
+act_check_interface (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	NMSettingWireless *s_wireless;
+	NMSettingWirelessSecurity *s_wireless_sec;
+	GDBusProxy *proxy = NULL;
+	GBytes *ssid;
+	gs_free char *ssid_utf8 = NULL;
+	const char *mode;
+
+	if (!priv->act_mode_switch)
+		return;
+
+	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP))
+		proxy = priv->dbus_ap_proxy;
+	else if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC))
+		proxy = priv->dbus_adhoc_proxy;
+
+	if (!proxy)
+		return;
+
+	priv->act_mode_switch = FALSE;
+
+	if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG))
+		return;
+
+	ssid = nm_setting_wireless_get_ssid (s_wireless);
+	if (!ssid)
+		goto failed;
+
+	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
+
+	s_wireless_sec = (NMSettingWirelessSecurity *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS_SECURITY);
+
+	if (!s_wireless_sec) {
+		g_dbus_proxy_call (proxy, "StartOpen",
+		                   g_variant_new ("(s)", ssid_utf8),
+		                   G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
+		                   priv->cancellable, act_start_cb, self);
+	} else {
+		const char *psk = nm_setting_wireless_security_get_psk (s_wireless_sec);
+
+		if (!psk) {
+			_LOGE (LOGD_DEVICE | LOGD_WIFI,
+			       "Activation: (wifi) No PSK for '%s'.",
+			       ssid_utf8);
+			goto failed;
+		}
+
+		g_dbus_proxy_call (proxy, "Start",
+		                   g_variant_new ("(ss)", ssid_utf8, psk),
+		                   G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
+		                   priv->cancellable, act_start_cb, self);
+	}
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) Called Start('%s').",
+	       ssid_utf8);
+	return;
+
+failed:
+	reset_mode (self, priv->cancellable, act_failed_cb, self);
+}
+
+static void
+act_set_mode_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant) {
+		_LOGE (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi) Setting Device.Mode failed: %s",
+		       error->message);
+
+		if (nm_utils_error_is_cancelled (error, FALSE))
+			return;
+
+		if (   !NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG)
+		    || !priv->act_mode_switch)
+			return;
+
+		priv->act_mode_switch = FALSE;
+		nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return;
+	}
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) IWD Device.Mode set successfully");
+
+	act_check_interface (self);
+}
+
+static void
+act_set_mode (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	const char *iwd_mode;
+	const char *mode;
+	NMSettingWireless *s_wireless;
+
+	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+	mode = nm_setting_wireless_get_mode (s_wireless);
+
+	/* We need to first set interface mode (Device.Mode) to ap or ad-hoc.
+	 * We can't directly queue a call to the Start/StartOpen method on
+	 * the DBus interface that's going to be created after the property
+	 * set call returns.
+	 */
+	iwd_mode = nm_streq (mode, NM_SETTING_WIRELESS_MODE_AP) ? "ap" : "ad-hoc";
+
+	if (!priv->cancellable)
+		priv->cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_call (priv->dbus_device_proxy,
+	                   DBUS_INTERFACE_PROPERTIES ".Set",
+	                   g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE,
+	                                  "Mode",
+	                                  g_variant_new ("s", iwd_mode)),
+	                   G_DBUS_CALL_FLAGS_NONE, 2000,
+	                   priv->cancellable, act_set_mode_cb, self);
+	priv->act_mode_switch = TRUE;
+}
+
+static void
+act_psk_cb (NMActRequest *req,
+            NMActRequestGetSecretsCallId *call_id,
+            NMSettingsConnection *s_connection,
+            GError *error,
+            gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv;
+	NMDevice *device;
+
+	if (nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	device = NM_DEVICE (self);
+
+	g_return_if_fail (priv->wifi_secrets_id == call_id);
+	priv->wifi_secrets_id = NULL;
+
+	g_return_if_fail (req == nm_device_get_act_request (device));
+	g_return_if_fail (nm_act_request_get_settings_connection (req) == s_connection);
+
+	if (nm_device_get_state (device) != NM_DEVICE_STATE_NEED_AUTH)
+		goto secrets_error;
+
+	if (error) {
+		_LOGW (LOGD_WIFI, "%s", error->message);
+		goto secrets_error;
+	}
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) missing PSK request completed");
+
+	/* Change state back to what it was before NEED_AUTH */
+	nm_device_state_changed (device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+	act_set_mode (self);
+	return;
+
+secrets_error:
+	nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS);
+	cleanup_association_attempt (self, FALSE);
+}
+
+static void
 set_powered (NMDeviceIwd *self, gboolean powered)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
 	g_dbus_proxy_call (priv->dbus_device_proxy,
-	                   "org.freedesktop.DBus.Properties.Set",
+	                   DBUS_INTERFACE_PROPERTIES ".Set",
 	                   g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE,
 	                                  "Powered",
 	                                  g_variant_new ("b", powered)),
@@ -1384,6 +1749,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMActRequest *req;
 	NMConnection *connection;
 	NMSettingWireless *s_wireless;
+	const char *mode;
 	const char *ap_path;
 
 	ret = NM_DEVICE_CLASS (nm_device_iwd_parent_class)->act_stage1_prepare (device, out_failure_reason);
@@ -1399,20 +1765,51 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
 
+	/* AP mode never uses a specific object or existing scanned AP */
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP))
+		goto add_new;
+
 	ap_path = nm_active_connection_get_specific_object (NM_ACTIVE_CONNECTION (req));
 	ap = ap_path ? nm_wifi_ap_lookup_for_device (NM_DEVICE (self), ap_path) : NULL;
-	if (!ap) {
-		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
-		if (!ap) {
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
-			return NM_ACT_STAGE_RETURN_FAILURE;
-		}
+	if (ap) {
+		set_current_ap (self, ap, TRUE);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
 
+	ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+	if (ap) {
 		nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
 		                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
+		set_current_ap (self, ap, TRUE);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		/* Hidden networks not supported at this time */
+		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
+add_new:
+	/* If the user is trying to connect to an AP that NM doesn't yet know about
+	 * (hidden network or something) or starting a Hotspot, create an fake AP
+	 * from the security settings in the connection.  This "fake" AP gets used
+	 * until the real one is found in the scan list (Ad-Hoc or Hidden), or until
+	 * the device is deactivated (Ad-Hoc or Hotspot).
+	 */
+	ap = nm_wifi_ap_new_fake_from_connection (connection);
+	g_return_val_if_fail (ap != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+	if (nm_wifi_ap_is_hotspot (ap))
+		nm_wifi_ap_set_address (ap, nm_device_get_hw_address (device));
+
+	g_object_freeze_notify (G_OBJECT (self));
+	ap_add_remove (self, TRUE, ap, FALSE);
+	g_object_thaw_notify (G_OBJECT (self));
 	set_current_ap (self, ap, FALSE);
+	nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
+	                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
+	g_object_unref (ap);
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
@@ -1423,60 +1820,85 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMActRequest *req;
-	NMWifiAP *ap;
 	NMConnection *connection;
-	GDBusProxy *network_proxy;
+	NMSettingWireless *s_wireless;
+	const char *mode;
 
 	req = nm_device_get_act_request (device);
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
 
-	ap = priv->current_ap;
-	if (!ap) {
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		goto out;
-	}
-
 	connection = nm_act_request_get_applied_connection (req);
 	g_assert (connection);
 
-	/* 802.1x networks that are not IWD Known Networks will definitely
-	 * fail, for other combinations we will let the Connect call fail
-	 * or ask us for any missing secrets through the Agent.
-	 */
-	if (   !is_connection_known_network (connection)
-	    && nm_connection_get_setting_802_1x (connection)) {
-		_LOGI (LOGD_DEVICE | LOGD_WIFI,
-		       "Activation: (wifi) access point '%s' has 802.1x security, but is not configured.",
-		       nm_connection_get_id (connection));
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
 
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-		ret = NM_ACT_STAGE_RETURN_FAILURE;
-		goto out;
-	}
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		GDBusProxy *network_proxy;
+		NMWifiAP *ap = priv->current_ap;
 
-	network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
-	                                                   nm_wifi_ap_get_supplicant_path (ap),
-	                                                   NM_IWD_NETWORK_INTERFACE);
-	if (!network_proxy) {
-		_LOGE (LOGD_DEVICE | LOGD_WIFI,
-		       "Activation: (wifi) could not get Network interface proxy for %s",
-		       nm_wifi_ap_get_supplicant_path (ap));
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		goto out;
-	}
+		if (!ap) {
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+			goto out;
+		}
 
-	if (!priv->cancellable)
-		priv->cancellable = g_cancellable_new ();
+		/* 802.1x networks that are not IWD Known Networks will definitely
+		 * fail, for other combinations we will let the Connect call fail
+		 * or ask us for any missing secrets through the Agent.
+		 */
+		if (   nm_connection_get_setting_802_1x (connection)
+		    && !is_ap_known_network (ap)) {
+			_LOGI (LOGD_DEVICE | LOGD_WIFI,
+			       "Activation: (wifi) access point '%s' has 802.1x security but is not configured in IWD.",
+			       nm_connection_get_id (connection));
+
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+			goto out;
+		}
 
-	/* Call Network.Connect.  No timeout because IWD already handles
-	 * timeouts.
-	 */
-	g_dbus_proxy_call (network_proxy, "Connect",
-	                   g_variant_new ("()"),
-	                   G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
-	                   priv->cancellable, network_connect_cb, self);
+		network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
+		                                                   nm_wifi_ap_get_supplicant_path (ap),
+		                                                   NM_IWD_NETWORK_INTERFACE);
+		if (!network_proxy) {
+			_LOGE (LOGD_DEVICE | LOGD_WIFI,
+			       "Activation: (wifi) could not get Network interface proxy for %s",
+			       nm_wifi_ap_get_supplicant_path (ap));
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+			goto out;
+		}
 
-	g_object_unref (network_proxy);
+		if (!priv->cancellable)
+			priv->cancellable = g_cancellable_new ();
+
+		/* Call Network.Connect.  No timeout because IWD already handles
+		 * timeouts.
+		 */
+		g_dbus_proxy_call (network_proxy, "Connect",
+		                   NULL, G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
+		                   priv->cancellable, network_connect_cb, self);
+
+		g_object_unref (network_proxy);
+	} else if (NM_IN_STRSET (mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC)) {
+		NMSettingWirelessSecurity *s_wireless_sec;
+
+		s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
+		if (s_wireless_sec && !nm_setting_wireless_security_get_psk (s_wireless_sec)) {
+			/* PSK is missing from the settings, have to request it */
+
+			wifi_secrets_cancel (self);
+
+			priv->wifi_secrets_id = nm_act_request_get_secrets (req,
+			                                                    TRUE,
+			                                                    NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+			                                                    NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION,
+			                                                    NM_MAKE_STRV (NM_SETTING_WIRELESS_SECURITY_PSK),
+			                                                    act_psk_cb,
+			                                                    self);
+			nm_device_state_changed (device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
+		} else
+			act_set_mode (self);
+	}
 
 	/* We'll get stage3 started when the supplicant connects */
 	ret = NM_ACT_STAGE_RETURN_POSTPONE;
@@ -1507,8 +1929,8 @@ periodic_scan_timeout_cb (gpointer user_data)
 	if (priv->scanning || priv->scan_requested)
 		return FALSE;
 
-	g_dbus_proxy_call (priv->dbus_station_proxy, "Scan", g_variant_new ("()"),
-	                   G_DBUS_CALL_FLAGS_NONE, -1,
+	g_dbus_proxy_call (priv->dbus_station_proxy, "Scan",
+	                   NULL, G_DBUS_CALL_FLAGS_NONE, -1,
 	                   priv->cancellable, scan_cb, self);
 	priv->scan_requested = TRUE;
 
@@ -1516,18 +1938,37 @@ periodic_scan_timeout_cb (gpointer user_data)
 }
 
 static void
-schedule_periodic_scan (NMDeviceIwd *self, NMDeviceState current_state)
+schedule_periodic_scan (NMDeviceIwd *self, gboolean initial_scan)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	GVariant *value;
+	gboolean disconnected;
 	guint interval;
 
-	if (current_state <= NM_DEVICE_STATE_UNAVAILABLE)
+	if (!priv->can_scan || priv->scan_requested)
 		return;
 
-	if (current_state == NM_DEVICE_STATE_DISCONNECTED)
-		interval = 10;
+	value = g_dbus_proxy_get_cached_property (priv->dbus_station_proxy, "State");
+	disconnected = nm_streq0 (get_variant_state (value), "disconnected");
+	g_variant_unref (value);
+
+	/* Start scan immediately after a disconnect, mode change or
+	 * device UP, otherwise wait a period dependent on the current
+	 * state.
+	 *
+	 * (initial_scan && disconnected) override priv->scanning below
+	 * because of an IWD quirk where a device will often be in the
+	 * autoconnect state and scanning at the time of our initial_scan,
+	 * but our logic will the send it a Disconnect() causeing IWD to
+	 * exit autoconnect and interrupt the ongoing scan, meaning that
+	 * we still want a new scan ASAP.
+	 */
+	if (initial_scan && disconnected)
+		interval = 0;
+	else if (!priv->periodic_scan_id && !priv->scanning)
+		interval = disconnected ? 10 : 20;
 	else
-		interval = 20;
+		return;
 
 	nm_clear_g_source (&priv->periodic_scan_id);
 	priv->periodic_scan_id = g_timeout_add_seconds (interval,
@@ -1536,6 +1977,19 @@ schedule_periodic_scan (NMDeviceIwd *self, NMDeviceState current_state)
 }
 
 static void
+set_can_scan (NMDeviceIwd *self, gboolean can_scan)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (priv->can_scan == can_scan)
+		return;
+
+	priv->can_scan = can_scan;
+
+	schedule_periodic_scan (self, TRUE);
+}
+
+static void
 device_state_changed (NMDevice *device,
                       NMDeviceState new_state,
                       NMDeviceState old_state,
@@ -1544,14 +1998,6 @@ device_state_changed (NMDevice *device,
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
-	if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) {
-		remove_all_aps (self);
-		nm_clear_g_source (&priv->periodic_scan_id);
-	} else if (old_state <= NM_DEVICE_STATE_UNAVAILABLE) {
-		update_aps (self);
-		schedule_periodic_scan (self, new_state);
-	}
-
 	switch (new_state) {
 	case NM_DEVICE_STATE_UNMANAGED:
 		break;
@@ -1669,10 +2115,13 @@ get_property (GObject *object, guint prop_id,
 
 	switch (prop_id) {
 	case PROP_MODE:
-		if (priv->current_ap)
-			g_value_set_uint (value, NM_802_11_MODE_INFRA);
-		else
+		if (!priv->current_ap)
 			g_value_set_uint (value, NM_802_11_MODE_UNKNOWN);
+		else if (nm_wifi_ap_is_hotspot (priv->current_ap))
+			g_value_set_uint (value, NM_802_11_MODE_AP);
+		else
+			g_value_set_uint (value, nm_wifi_ap_get_mode (priv->current_ap));
+
 		break;
 	case PROP_BITRATE:
 		g_value_set_uint (value, 65000);
@@ -1702,24 +2151,6 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMDeviceIwd *device = NM_DEVICE_IWD (object);
-	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (device);
-
-	switch (prop_id) {
-	case PROP_CAPABILITIES:
-		/* construct-only */
-		priv->capabilities = g_value_get_uint (value);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
 /*****************************************************************************/
 
 static void
@@ -1729,7 +2160,7 @@ state_changed (NMDeviceIwd *self, const char *new_state)
 	NMDevice *device = NM_DEVICE (self);
 	NMDeviceState dev_state = nm_device_get_state (device);
 	gboolean iwd_connection = FALSE;
-	gboolean can_connect;
+	gboolean can_connect = priv->can_connect;
 
 	_LOGI (LOGD_DEVICE | LOGD_WIFI, "new IWD device state is %s", new_state);
 
@@ -1738,7 +2169,9 @@ state_changed (NMDeviceIwd *self, const char *new_state)
 		iwd_connection = TRUE;
 
 	/* Don't allow scanning while connecting, disconnecting or roaming */
-	priv->can_scan = NM_IN_STRSET (new_state, "connected", "disconnected");
+	set_can_scan (self, NM_IN_STRSET (new_state, "connected", "disconnected"));
+
+	priv->can_connect = FALSE;
 
 	if (NM_IN_STRSET (new_state, "connecting", "connected", "roaming")) {
 		/* If we were connecting, do nothing, the confirmation of
@@ -1766,7 +2199,7 @@ state_changed (NMDeviceIwd *self, const char *new_state)
 		 * callback will have more information on the specific failure
 		 * reason.
 		 */
-		if (dev_state == NM_DEVICE_STATE_CONFIG)
+		if (NM_IN_SET (dev_state, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH))
 			return;
 
 		if (iwd_connection)
@@ -1781,10 +2214,10 @@ state_changed (NMDeviceIwd *self, const char *new_state)
 	/* Don't allow new connection until iwd exits disconnecting and no
 	 * Connect callback is pending.
 	 */
-	can_connect = NM_IN_STRSET (new_state, "disconnected");
-	if (can_connect != priv->can_connect) {
-		priv->can_connect = can_connect;
-		nm_device_emit_recheck_auto_activate (device);
+	if (NM_IN_STRSET (new_state, "disconnected")) {
+		priv->can_connect = TRUE;
+		if (!can_connect)
+			nm_device_emit_recheck_auto_activate (device);
 	}
 }
 
@@ -1792,7 +2225,6 @@ static void
 scanning_changed (NMDeviceIwd *self, gboolean new_scanning)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
-	NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
 
 	if (new_scanning == priv->scanning)
 		return;
@@ -1805,7 +2237,7 @@ scanning_changed (NMDeviceIwd *self, gboolean new_scanning)
 		update_aps (self);
 
 		if (!priv->scan_requested)
-			schedule_periodic_scan (self, state);
+			schedule_periodic_scan (self, FALSE);
 	}
 }
 
@@ -1814,40 +2246,81 @@ station_properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
                             GStrv invalidate_properties, gpointer user_data)
 {
 	NMDeviceIwd *self = user_data;
-	GVariantIter *iter;
-	const char *key;
-	GVariant *value;
+	const char *new_str;
+	gboolean new_bool;
 
-	g_variant_get (changed_properties, "a{sv}", &iter);
-	while (g_variant_iter_next (iter, "{&sv}", &key, &value)) {
-		if (!strcmp (key, "State"))
-			state_changed (self, get_variant_state (value));
+	if (g_variant_lookup (changed_properties, "State", "&s", &new_str))
+		state_changed (self, new_str);
 
-		if (!strcmp (key, "Scanning"))
-			scanning_changed (self, get_variant_boolean (value, "Scanning"));
+	if (g_variant_lookup (changed_properties, "Scanning", "b", &new_bool))
+		scanning_changed (self, new_bool);
+}
 
-		g_variant_unref (value);
-	}
+static void
+ap_adhoc_properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
+                             GStrv invalidate_properties, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	gboolean new_bool;
 
-	g_variant_iter_free (iter);
+	if (g_variant_lookup (changed_properties, "Started", "b", &new_bool))
+		_LOGI (LOGD_DEVICE | LOGD_WIFI, "IWD AP/AdHoc state is now %s", new_bool ? "Started" : "Stopped");
 }
 
 static void
 powered_changed (NMDeviceIwd *self, gboolean new_powered)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	GDBusInterface *interface;
+	GVariant *value;
 
 	nm_device_queue_recheck_available (NM_DEVICE (self),
 	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
 	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
 
-	if (new_powered) {
-		GDBusInterface *interface;
-		GVariant *value;
+	interface = new_powered ? g_dbus_object_get_interface (priv->dbus_obj, NM_IWD_AP_INTERFACE) : NULL;
 
-		if (priv->dbus_station_proxy)
-			return;
+	if (priv->dbus_ap_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_ap_proxy,
+		                                      ap_adhoc_properties_changed, self);
+		g_clear_object (&priv->dbus_ap_proxy);
+	}
+
+	if (interface) {
+		priv->dbus_ap_proxy = G_DBUS_PROXY (interface);
+		g_signal_connect (priv->dbus_ap_proxy, "g-properties-changed",
+		                  G_CALLBACK (ap_adhoc_properties_changed), self);
+
+		if (priv->act_mode_switch)
+			act_check_interface (self);
+		else
+			reset_mode (self, NULL, NULL, NULL);
+	}
+
+	interface = new_powered ? g_dbus_object_get_interface (priv->dbus_obj, NM_IWD_ADHOC_INTERFACE) : NULL;
+
+	if (priv->dbus_adhoc_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_adhoc_proxy,
+		                                      ap_adhoc_properties_changed, self);
+		g_clear_object (&priv->dbus_adhoc_proxy);
+	}
+
+	if (interface) {
+		priv->dbus_adhoc_proxy = G_DBUS_PROXY (interface);
+		g_signal_connect (priv->dbus_adhoc_proxy, "g-properties-changed",
+		                  G_CALLBACK (ap_adhoc_properties_changed), self);
 
+		if (priv->act_mode_switch)
+			act_check_interface (self);
+		else
+			reset_mode (self, NULL, NULL, NULL);
+	}
+
+	/* We expect one of the three interfaces to always be present when
+	 * device is Powered so if AP and AdHoc are not present we should
+	 * be in station mode.
+	 */
+	if (new_powered && !priv->dbus_ap_proxy && !priv->dbus_adhoc_proxy) {
 		interface = g_dbus_object_get_interface (priv->dbus_obj, NM_IWD_STATION_INTERFACE);
 		if (!interface) {
 			/* No Station interface on the device object.  Check if the
@@ -1858,18 +2331,30 @@ powered_changed (NMDeviceIwd *self, gboolean new_powered)
 			 * priv->dbus_station_proxy both point at the Device interface.
 			 */
 			value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "State");
-			if (!value) {
+			if (value) {
+				g_variant_unref (value);
+				interface = g_object_ref (G_DBUS_INTERFACE (priv->dbus_device_proxy));
+			} else {
 				_LOGE (LOGD_WIFI, "Interface %s not found on obj %s",
 				       NM_IWD_STATION_INTERFACE,
 				       g_dbus_object_get_object_path (priv->dbus_obj));
-				return;
+				interface = NULL;
 			}
 
-			g_variant_unref (value);
-			interface = g_object_ref (priv->dbus_device_proxy);
 		}
+	} else
+		interface = NULL;
+
+	if (priv->dbus_station_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_station_proxy,
+		                                      station_properties_changed, self);
+		g_clear_object (&priv->dbus_station_proxy);
+	}
 
+	if (interface) {
 		priv->dbus_station_proxy = G_DBUS_PROXY (interface);
+		g_signal_connect (priv->dbus_station_proxy, "g-properties-changed",
+		                  G_CALLBACK (station_properties_changed), self);
 
 		value = g_dbus_proxy_get_cached_property (priv->dbus_station_proxy, "Scanning");
 		priv->scanning = get_variant_boolean (value, "Scanning");
@@ -1879,27 +2364,15 @@ powered_changed (NMDeviceIwd *self, gboolean new_powered)
 		state_changed (self, get_variant_state (value));
 		g_variant_unref (value);
 
-		g_signal_connect (priv->dbus_station_proxy, "g-properties-changed",
-		                  G_CALLBACK (station_properties_changed), self);
-
-		/* Call Disconnect to make sure IWD's autoconnect is disabled.
-		 * Autoconnect is the default state after device is brought UP.
-		 */
-		if (priv->enabled)
-			send_disconnect (self);
+		update_aps (self);
 	} else {
-		if (!priv->dbus_station_proxy)
-			return;
-
-		g_signal_handlers_disconnect_by_func (priv->dbus_station_proxy,
-		                                      station_properties_changed, self);
-		g_clear_object (&priv->dbus_station_proxy);
-
-		priv->can_scan = FALSE;
+		set_can_scan (self, FALSE);
+		nm_clear_g_source (&priv->periodic_scan_id);
 		priv->scanning = FALSE;
 		priv->scan_requested = FALSE;
 		priv->can_connect = FALSE;
 		cleanup_association_attempt (self, FALSE);
+		remove_all_aps (self);
 	}
 }
 
@@ -1908,19 +2381,10 @@ device_properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
                            GStrv invalidate_properties, gpointer user_data)
 {
 	NMDeviceIwd *self = user_data;
-	GVariantIter *iter;
-	const char *key;
-	GVariant *value;
-
-	g_variant_get (changed_properties, "a{sv}", &iter);
-	while (g_variant_iter_next (iter, "{&sv}", &key, &value)) {
-		if (!strcmp (key, "Powered"))
-			powered_changed (self, get_variant_boolean (value, "Powered"));
+	gboolean new_bool;
 
-		g_variant_unref (value);
-	}
-
-	g_variant_iter_free (iter);
+	if (g_variant_lookup (changed_properties, "Powered", "b", &new_bool))
+		powered_changed (self, new_bool);
 }
 
 void
@@ -1928,24 +2392,24 @@ nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	GDBusInterface *interface;
-	GVariant *value;
+	gs_unref_variant GVariant *value = NULL;
+	gs_unref_object GDBusProxy *adapter_proxy = NULL;
+	GVariantIter *iter;
+	const char *mode;
 	gboolean powered;
+	NMDeviceWifiCapabilities capabilities;
 
-	if (!nm_g_object_ref_set ((GObject **) &priv->dbus_obj, (GObject *) object))
+	if (!nm_g_object_ref_set (&priv->dbus_obj, object))
 		return;
 
-	if (priv->enabled && priv->dbus_station_proxy) {
-		nm_device_queue_recheck_available (NM_DEVICE (self),
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-	}
-
 	if (priv->dbus_device_proxy) {
 		g_signal_handlers_disconnect_by_func (priv->dbus_device_proxy,
 		                                      device_properties_changed, self);
 		g_clear_object (&priv->dbus_device_proxy);
 
 		powered_changed (self, FALSE);
+
+		priv->act_mode_switch = FALSE;
 	}
 
 	if (!object)
@@ -1965,14 +2429,63 @@ nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object)
 	g_signal_connect (priv->dbus_device_proxy, "g-properties-changed",
 	                  G_CALLBACK (device_properties_changed), self);
 
+	/* Parse list of interface modes supported by adapter (wiphy) */
+
+	value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "Adapter");
+	if (!value || !g_variant_is_of_type (value, G_VARIANT_TYPE_OBJECT_PATH)) {
+		nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+		             "Adapter property not cached or not an object path");
+		goto error;
+	}
+
+	adapter_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
+	                                                   g_variant_get_string (value, NULL),
+	                                                   NM_IWD_WIPHY_INTERFACE);
+	if (!adapter_proxy) {
+		nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+		             "Can't get DBus proxy for IWD Adapter for IWD Device");
+		goto error;
+	}
+
+	g_variant_unref (value);
+	value = g_dbus_proxy_get_cached_property (adapter_proxy, "SupportedModes");
+	if (!value || !g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) {
+		nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+		             "SupportedModes property not cached or not a string array");
+		goto error;
+	}
+
+	capabilities = NM_WIFI_DEVICE_CAP_CIPHER_CCMP | NM_WIFI_DEVICE_CAP_RSN;
+
+	g_variant_get (value, "as", &iter);
+	while (g_variant_iter_next (iter, "&s", &mode)) {
+		if (nm_streq (mode, "ap"))
+			capabilities |= NM_WIFI_DEVICE_CAP_AP;
+		else if (nm_streq (mode, "ad-hoc"))
+			capabilities |= NM_WIFI_DEVICE_CAP_ADHOC;
+	}
+	g_variant_iter_free (iter);
+
+	if (priv->capabilities != capabilities) {
+		priv->capabilities = capabilities;
+		_notify (self, PROP_CAPABILITIES);
+	}
+
+	g_variant_unref (value);
 	value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "Powered");
 	powered = get_variant_boolean (value, "Powered");
-	g_variant_unref (value);
 
 	if (powered != priv->enabled)
 		set_powered (self, priv->enabled);
 	else if (powered)
 		powered_changed (self, TRUE);
+
+	return;
+
+error:
+	g_signal_handlers_disconnect_by_func (priv->dbus_device_proxy,
+	                                      device_properties_changed, self);
+	g_clear_object (&priv->dbus_device_proxy);
 }
 
 gboolean
@@ -2041,15 +2554,14 @@ nm_device_iwd_init (NMDeviceIwd *self)
 }
 
 NMDevice *
-nm_device_iwd_new (const char *iface, NMDeviceWifiCapabilities capabilities)
+nm_device_iwd_new (const char *iface)
 {
 	return g_object_new (NM_TYPE_DEVICE_IWD,
 	                     NM_DEVICE_IFACE, iface,
-	                     NM_DEVICE_TYPE_DESC, "802.11 WiFi",
+	                     NM_DEVICE_TYPE_DESC, "802.11 Wi-Fi",
 	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI,
 	                     NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIFI,
 	                     NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN,
-	                     NM_DEVICE_IWD_CAPABILITIES, (guint) capabilities,
 	                     NULL);
 }
 
@@ -2067,6 +2579,8 @@ dispose (GObject *object)
 
 	g_clear_object (&priv->dbus_device_proxy);
 	g_clear_object (&priv->dbus_station_proxy);
+	g_clear_object (&priv->dbus_ap_proxy);
+	g_clear_object (&priv->dbus_adhoc_proxy);
 	g_clear_object (&priv->dbus_obj);
 
 	remove_all_aps (self);
@@ -2084,7 +2598,6 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->get_property = get_property;
-	object_class->set_property = set_property;
 	object_class->dispose = dispose;
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&nm_interface_info_device_wireless);
@@ -2108,7 +2621,6 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 	device_class->get_configured_mtu = get_configured_mtu;
 	device_class->deactivate = deactivate;
 	device_class->deactivate_async = deactivate_async;
-	device_class->deactivate_async_finish = deactivate_async_finish;
 	device_class->can_reapply_change = can_reapply_change;
 
 	device_class->state_changed = device_state_changed;
@@ -2144,8 +2656,7 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 	obj_properties[PROP_CAPABILITIES] =
 	    g_param_spec_uint (NM_DEVICE_IWD_CAPABILITIES, "", "",
 	                       0, G_MAXUINT32, NM_WIFI_DEVICE_CAP_NONE,
-	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_READABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_SCANNING] =
diff --git a/src/devices/wifi/nm-device-iwd.h b/src/devices/wifi/nm-device-iwd.h
index 825123b1..4a2bd31e 100644
--- a/src/devices/wifi/nm-device-iwd.h
+++ b/src/devices/wifi/nm-device-iwd.h
@@ -47,7 +47,7 @@ typedef struct _NMDeviceIwdClass NMDeviceIwdClass;
 
 GType nm_device_iwd_get_type (void);
 
-NMDevice *nm_device_iwd_new (const char *iface, NMDeviceWifiCapabilities capabilities);
+NMDevice *nm_device_iwd_new (const char *iface);
 
 void nm_device_iwd_set_dbus_object (NMDeviceIwd *device, GDBusObject *object);
 
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 4c42e7d3..1172a613 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -29,13 +29,11 @@
 #include "nm-device-olpc-mesh.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <signal.h>
 #include <unistd.h>
 #include <sys/ioctl.h>
-#include <errno.h>
 
 #include "devices/nm-device.h"
 #include "nm-device-wifi.h"
@@ -166,7 +164,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		       nm_device_get_iface (priv->companion));
 	}
 
-	/* wait with continuing configuration untill the companion device is done scanning */
+	/* wait with continuing configuration until the companion device is done scanning */
 	g_object_get (priv->companion, NM_DEVICE_WIFI_SCANNING, &scanning, NULL);
 	if (scanning) {
 		priv->stage1_waiting = TRUE;
@@ -193,16 +191,13 @@ static NMActStageReturn
 act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (device);
-	NMConnection *connection;
 	NMSettingOlpcMesh *s_mesh;
 	guint32 channel;
 	GBytes *ssid;
 	const char *anycast_addr;
 
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+	s_mesh = nm_device_get_applied_setting (device, NM_TYPE_SETTING_OLPC_MESH);
 
-	s_mesh = nm_connection_get_setting_olpc_mesh (connection);
 	g_return_val_if_fail (s_mesh, NM_ACT_STAGE_RETURN_FAILURE);
 
 	channel = nm_setting_olpc_mesh_get_channel (s_mesh);
@@ -333,7 +328,7 @@ check_companion (NMDeviceOlpcMesh *self, NMDevice *other)
 	g_assert (priv->companion == NULL);
 	priv->companion = g_object_ref (other);
 
-	_LOGI (LOGD_OLPC, "found companion WiFi device %s",
+	_LOGI (LOGD_OLPC, "found companion Wi-Fi device %s",
 	       nm_device_get_iface (other));
 
 	g_signal_connect (G_OBJECT (other), NM_DEVICE_STATE_CHANGED,
diff --git a/src/devices/wifi/nm-device-wifi-p2p.c b/src/devices/wifi/nm-device-wifi-p2p.c
new file mode 100644
index 00000000..8381ebc7
--- /dev/null
+++ b/src/devices/wifi/nm-device-wifi-p2p.c
@@ -0,0 +1,1316 @@
+/* NetworkManager -- Wi-Fi P2P Device
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-wifi-p2p.h"
+
+#include <sys/socket.h>
+
+#include "supplicant/nm-supplicant-manager.h"
+#include "supplicant/nm-supplicant-interface.h"
+
+#include "nm-manager.h"
+#include "nm-utils.h"
+#include "nm-wifi-p2p-peer.h"
+#include "NetworkManagerUtils.h"
+#include "devices/nm-device-private.h"
+#include "settings/nm-settings.h"
+#include "nm-setting-wifi-p2p.h"
+#include "nm-act-request.h"
+#include "nm-ip4-config.h"
+#include "platform/nm-platform.h"
+#include "nm-manager.h"
+#include "nm-core-internal.h"
+#include "platform/nmp-object.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceWifiP2P);
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifiP2P,
+	PROP_PEERS,
+);
+
+typedef struct {
+	NMSupplicantManager *sup_mgr;
+
+	/* NOTE: In theory management and group ifaces could be identical. However,
+	 * in practice, this cannot happen currently as NMDeviceWifiP2P is only
+	 * created for existing non-P2P interfaces.
+	 * (i.e. a single standalone P2P interface is not supported at this point)
+	 */
+	NMSupplicantInterface *mgmt_iface;
+	NMSupplicantInterface *group_iface;
+
+	CList peers_lst_head;
+
+	guint sup_timeout_id;
+	guint peer_dump_id;
+	guint peer_missing_id;
+
+	bool is_waiting_for_supplicant:1;
+} NMDeviceWifiP2PPrivate;
+
+struct _NMDeviceWifiP2P {
+	NMDevice parent;
+	NMDeviceWifiP2PPrivate _priv;
+};
+
+struct _NMDeviceWifiP2PClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceWifiP2P, nm_device_wifi_p2p, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_WIFI_P2P_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceWifiP2P, NM_IS_DEVICE_WIFI_P2P, NMDevice)
+
+/*****************************************************************************/
+
+static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p;
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added;
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed;
+
+static void supplicant_group_interface_release (NMDeviceWifiP2P *self);
+static void supplicant_interfaces_release (NMDeviceWifiP2P *self, gboolean set_is_waiting);
+
+/*****************************************************************************/
+
+static void
+_peer_dump (NMDeviceWifiP2P *self,
+            NMLogLevel log_level,
+            const NMWifiP2PPeer *peer,
+            const char *prefix,
+            gint32 now_s)
+{
+	char buf[1024];
+
+	_NMLOG (log_level, LOGD_WIFI_SCAN, "wifi-peer: %-7s %s",
+	        prefix,
+	        nm_wifi_p2p_peer_to_string (peer, buf, sizeof (buf), now_s));
+}
+
+static gboolean
+peer_list_dump (gpointer user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	priv->peer_dump_id = 0;
+
+	if (_LOGD_ENABLED (LOGD_WIFI_SCAN)) {
+		NMWifiP2PPeer *peer;
+		gint32 now_s = nm_utils_get_monotonic_timestamp_s ();
+
+		_LOGD (LOGD_WIFI_SCAN, "P2P Peers: [now:%u]", now_s);
+		c_list_for_each_entry (peer, &priv->peers_lst_head, peers_lst)
+			_peer_dump (self, LOGL_DEBUG, peer, "dump", now_s);
+	}
+	return G_SOURCE_REMOVE;
+}
+
+static void
+schedule_peer_list_dump (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (   !priv->peer_dump_id
+	    && _LOGD_ENABLED (LOGD_WIFI_SCAN))
+		priv->peer_dump_id = g_timeout_add_seconds (1, peer_list_dump, self);
+}
+
+/*****************************************************************************/
+
+static void
+_set_is_waiting_for_supplicant (NMDeviceWifiP2P *self, gboolean is_waiting)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (priv->is_waiting_for_supplicant == (!!is_waiting))
+		return;
+
+	priv->is_waiting_for_supplicant = is_waiting;
+
+	if (is_waiting)
+		nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
+	else
+		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
+}
+
+/*****************************************************************************/
+
+static gboolean
+check_connection_peer_joined (NMDeviceWifiP2P *device)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (device);
+	NMConnection *conn = nm_device_get_applied_connection (NM_DEVICE (device));
+	NMWifiP2PPeer *peer;
+	const char* group;
+	const char * const * groups;
+
+	if (!conn || !priv->group_iface)
+		return FALSE;
+
+	/* Comparing the object path found on the group_iface with the peers
+	 * found on the mgmt_iface is legal. */
+	group = nm_supplicant_interface_get_p2p_group_path (priv->group_iface);
+	if (!group)
+		return FALSE;
+
+	/* NOTE: We currently only support connections to a specific peer */
+	peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, conn);
+	if (!peer)
+		return FALSE;
+
+	groups = nm_wifi_p2p_peer_get_groups (peer);
+	if (   !groups
+	    || !g_strv_contains (groups, group))
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+disconnect_on_connection_peer_missing_cb (gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	_LOGW (LOGD_WIFI, "Peer requested in connection is missing for too long, failing connection.");
+
+	priv->peer_missing_id = 0;
+
+	nm_device_state_changed (device,
+	                         NM_DEVICE_STATE_FAILED,
+	                         NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+	return FALSE;
+}
+
+static void
+update_disconnect_on_connection_peer_missing (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMDeviceState state;
+
+	state = nm_device_get_state (NM_DEVICE (self));
+	if (   state < NM_DEVICE_STATE_IP_CONFIG
+	    || state > NM_DEVICE_STATE_ACTIVATED) {
+		nm_clear_g_source (&priv->peer_missing_id);
+		return;
+	}
+
+	if (check_connection_peer_joined (self)) {
+		if (nm_clear_g_source (&priv->peer_missing_id))
+			_LOGD (LOGD_WIFI, "Peer requested in connection is joined, removing timeout");
+		return;
+	}
+
+	if (priv->peer_missing_id == 0) {
+		_LOGD (LOGD_WIFI, "Peer requested in connection is missing, adding timeout");
+		priv->peer_missing_id = g_timeout_add_seconds (5, disconnect_on_connection_peer_missing_cb, self);
+	}
+}
+
+static gboolean
+is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMSupplicantInterfaceState supplicant_state;
+
+	if (!priv->mgmt_iface)
+		return FALSE;
+
+	supplicant_state = nm_supplicant_interface_get_state (priv->mgmt_iface);
+	if (   supplicant_state < NM_SUPPLICANT_INTERFACE_STATE_READY
+	    || supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
+{
+	if (!NM_DEVICE_CLASS (nm_device_wifi_p2p_parent_class)->check_connection_compatible (device, connection, error))
+		return FALSE;
+
+	/* TODO: Allow limitting the interface using the HW-address? */
+
+	/* We don't need to check anything else here. The P2P device will only
+	 * exists if we are able to establish a P2P connection, and there should
+	 * be no further restrictions necessary.
+	 */
+
+	return TRUE;
+}
+
+static gboolean
+complete_connection (NMDevice *device,
+                     NMConnection *connection,
+                     const char *specific_object,
+                     NMConnection *const*existing_connections,
+                     GError **error)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	gs_free char *setting_name = NULL;
+	NMSettingWifiP2P *s_wifi_p2p;
+	NMWifiP2PPeer *peer;
+	const char *setting_peer;
+
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+
+	if (!specific_object) {
+		/* If not given a specific object, we need at minimum a peer address */
+		if (!s_wifi_p2p) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_INVALID_CONNECTION,
+			             "A '%s' setting is required if no Peer path was given",
+			             NM_SETTING_WIFI_P2P_SETTING_NAME);
+			return FALSE;
+		}
+
+		setting_peer = nm_setting_wifi_p2p_get_peer (s_wifi_p2p);
+		if (!setting_peer) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_INVALID_CONNECTION,
+			             "A '%s' setting with a valid Peer is required if no Peer path was given",
+			             NM_SETTING_WIFI_P2P_SETTING_NAME);
+			return FALSE;
+		}
+
+	} else {
+		peer = nm_wifi_p2p_peer_lookup_for_device (NM_DEVICE (self), specific_object);
+		if (!peer) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND,
+			             "The P2P peer %s is unknown",
+			             specific_object);
+			return FALSE;
+		}
+
+		setting_peer = nm_wifi_p2p_peer_get_address (peer);
+		g_return_val_if_fail (setting_peer, FALSE);
+	}
+
+	/* Add a Wi-Fi P2P setting if one doesn't exist yet */
+	if (!s_wifi_p2p) {
+		s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_setting_wifi_p2p_new ());
+		nm_connection_add_setting (connection, NM_SETTING (s_wifi_p2p));
+	}
+
+	g_object_set (G_OBJECT (s_wifi_p2p), NM_SETTING_WIFI_P2P_PEER, setting_peer, NULL);
+
+	setting_name = g_strdup_printf ("Wi-Fi P2P Peer %s", setting_peer);
+	nm_utils_complete_generic (nm_device_get_platform (device),
+	                           connection,
+	                           NM_SETTING_WIFI_P2P_SETTING_NAME,
+	                           existing_connections,
+	                           setting_name,
+	                           setting_name,
+	                           NULL,
+	                           TRUE);
+
+	return TRUE;
+}
+
+/*
+ * supplicant_find_timeout_cb
+ *
+ * Called when the supplicant has been unable to find the peer we want to connect to.
+ */
+static gboolean
+supplicant_find_timeout_cb (gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	priv->sup_timeout_id = 0;
+
+	nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (nm_device_is_activating (device)) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi-p2p) could not find peer, failing activation");
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static NMActStageReturn
+act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMActStageReturn ret;
+	NMActRequest *req;
+	NMConnection *connection;
+	NMSettingWifiP2P *s_wifi_p2p;
+	NMWifiP2PPeer *peer;
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+
+	ret = NM_DEVICE_CLASS (nm_device_wifi_p2p_parent_class)->act_stage1_prepare (device, out_failure_reason);
+	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
+		return ret;
+
+	if (!priv->mgmt_iface) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
+
+	connection = nm_act_request_get_applied_connection (req);
+	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+	g_return_val_if_fail (s_wifi_p2p, NM_ACT_STAGE_RETURN_FAILURE);
+
+	peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, connection);
+	if (!peer) {
+		/* Set up a timeout on the find attempt and run a find for the same period of time */
+		priv->sup_timeout_id = g_timeout_add_seconds (10,
+		                                              supplicant_find_timeout_cb,
+		                                              self);
+
+		nm_supplicant_interface_p2p_start_find (priv->mgmt_iface, 10);
+
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+	}
+
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static void
+cleanup_p2p_connect_attempt (NMDeviceWifiP2P *self, gboolean disconnect)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+	nm_clear_g_source (&priv->peer_missing_id);
+
+	if (priv->mgmt_iface)
+		nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (disconnect && priv->group_iface)
+		nm_supplicant_interface_p2p_disconnect (priv->group_iface);
+}
+
+/*
+ * supplicant_connection_timeout_cb
+ *
+ * Called when the supplicant has been unable to connect to a peer
+ * within a specified period of time.
+ */
+static gboolean
+supplicant_connection_timeout_cb (gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	priv->sup_timeout_id = 0;
+
+	nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (nm_device_is_activating (device)) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi-p2p) connecting took too long, failing activation");
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static NMActStageReturn
+act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMConnection *connection;
+	NMSettingWifiP2P *s_wifi_p2p;
+	NMWifiP2PPeer *peer;
+	GBytes *wfd_ies;
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+
+	if (!priv->mgmt_iface) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	connection = nm_device_get_applied_connection (device);
+	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+	nm_assert (NM_IS_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P)));
+
+	/* The prepare stage ensures that the peer has been found */
+	peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, connection);
+	if (!peer) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	/* Set the WFD IEs before trying to establish the connection. */
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+	wfd_ies = nm_setting_wifi_p2p_get_wfd_ies (s_wifi_p2p);
+	nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, wfd_ies);
+
+	/* TODO: Grab secrets if we don't have them yet! */
+
+	/* TODO: Fix "pbc" being hardcoded here! */
+	nm_supplicant_interface_p2p_connect (priv->mgmt_iface,
+	                                     nm_wifi_p2p_peer_get_supplicant_path (peer),
+	                                     "pbc", NULL);
+
+	/* Set up a timeout on the connect attempt */
+	priv->sup_timeout_id = g_timeout_add_seconds (45,
+	                                              supplicant_connection_timeout_cb,
+	                                              self);
+
+	/* We'll get stage3 started when the P2P group has been started */
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+/*****************************************************************************/
+
+static void
+emit_signal_p2p_peer_add_remove (NMDeviceWifiP2P *device,
+                                 NMWifiP2PPeer *peer,
+                                 gboolean is_added /* or else is_removed */)
+{
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (device),
+	                            &interface_info_device_wifi_p2p,
+	                            is_added
+	                              ? &nm_signal_info_wifi_p2p_peer_added
+	                              : &nm_signal_info_wifi_p2p_peer_removed,
+	                            "(o)",
+	                            nm_dbus_object_get_path (NM_DBUS_OBJECT (peer)));
+}
+
+static void
+peer_add_remove (NMDeviceWifiP2P *self,
+                 gboolean is_adding, /* or else removing */
+                 NMWifiP2PPeer *peer,
+                 gboolean recheck_available_connections)
+{
+	NMDevice *device = NM_DEVICE (self);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (is_adding) {
+		g_object_ref (peer);
+		peer->wifi_device = device;
+		c_list_link_tail (&priv->peers_lst_head, &peer->peers_lst);
+		nm_dbus_object_export (NM_DBUS_OBJECT (peer));
+		_peer_dump (self, LOGL_DEBUG, peer, "added", 0);
+
+		emit_signal_p2p_peer_add_remove (self, peer, TRUE);
+	} else {
+		peer->wifi_device = NULL;
+		c_list_unlink (&peer->peers_lst);
+		_peer_dump (self, LOGL_DEBUG, peer, "removed", 0);
+	}
+
+	_notify (self, PROP_PEERS);
+
+	if (!is_adding) {
+		emit_signal_p2p_peer_add_remove (self, peer, FALSE);
+		nm_dbus_object_clear_and_unexport (&peer);
+	}
+
+	if (is_adding) {
+		/* If we are in prepare state, then we are currently runnign a find
+		 * to search for the requested peer. */
+		if (nm_device_get_state (device) == NM_DEVICE_STATE_PREPARE) {
+			NMConnection *connection;
+
+			connection = nm_device_get_applied_connection (device);
+			g_assert (connection);
+
+			peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, connection);
+			if (peer) {
+				/* A peer for the connection was found, cancel the timeout and go to configure state. */
+				nm_clear_g_source (&priv->sup_timeout_id);
+				nm_device_activate_schedule_stage2_device_config (device);
+			}
+		}
+
+		/* TODO: We may want to re-check auto-activation here, otherwise it will never work. */
+	}
+
+	update_disconnect_on_connection_peer_missing (self);
+}
+
+static void
+remove_all_peers (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMWifiP2PPeer *peer;
+
+	if (c_list_is_empty (&priv->peers_lst_head))
+		return;
+
+	while ((peer = c_list_first_entry (&priv->peers_lst_head, NMWifiP2PPeer, peers_lst)))
+		peer_add_remove (self, FALSE, peer, FALSE);
+
+	nm_device_recheck_available_connections (NM_DEVICE (self));
+}
+
+/*****************************************************************************/
+
+
+static NMActStageReturn
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
+{
+	gboolean indicate_addressing_running;
+	NMConnection *connection;
+	const char *method;
+
+	connection = nm_device_get_applied_connection (device);
+
+	method = nm_utils_get_ip_config_method (connection, addr_family);
+
+	if (addr_family == AF_INET)
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+	else {
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		                                                    NM_SETTING_IP6_CONFIG_METHOD_DHCP);
+	}
+
+	if (indicate_addressing_running)
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), TRUE);
+
+	return NM_DEVICE_CLASS (nm_device_wifi_p2p_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
+}
+
+static void
+deactivate (NMDevice *device)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	int ifindex = nm_device_get_ip_ifindex (device);
+
+	cleanup_p2p_connect_attempt (self, TRUE);
+
+	/* Clear any critical protocol notification in the Wi-Fi stack */
+	if (ifindex > 0)
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), ifindex, FALSE);
+}
+
+static guint32
+get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+{
+	*out_source = NM_DEVICE_MTU_SOURCE_NONE;
+	return 0;
+}
+
+static const char *
+get_auto_ip_config_method (NMDevice *device, int addr_family)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	/* Override the AUTO method to mean shared if we are group owner. */
+	if (   priv->group_iface
+	    && nm_supplicant_interface_get_p2p_group_owner (priv->group_iface)) {
+		if (addr_family == AF_INET)
+			return NM_SETTING_IP4_CONFIG_METHOD_SHARED;
+
+		if (addr_family == AF_INET6)
+			return NM_SETTING_IP6_CONFIG_METHOD_SHARED;
+	}
+
+	return NULL;
+}
+
+static gboolean
+unmanaged_on_quit (NMDevice *self)
+{
+	return TRUE;
+}
+
+static void
+supplicant_iface_state_cb (NMSupplicantInterface *iface,
+                           int new_state_i,
+                           int old_state_i,
+                           int disconnect_reason,
+                           gpointer user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDevice *device = NM_DEVICE (self);
+	NMSupplicantInterfaceState new_state = new_state_i;
+	NMSupplicantInterfaceState old_state = old_state_i;
+
+	if (new_state == old_state)
+		return;
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "supplicant management interface state: %s -> %s",
+	       nm_supplicant_interface_state_to_string (old_state),
+	       nm_supplicant_interface_state_to_string (new_state));
+
+	switch (new_state) {
+	case NM_SUPPLICANT_INTERFACE_STATE_READY:
+		_LOGD (LOGD_WIFI, "supplicant ready");
+		nm_device_queue_recheck_available (device,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+
+		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
+			_set_is_waiting_for_supplicant (self, FALSE);
+		break;
+	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+		supplicant_interfaces_release (self, TRUE);
+		nm_device_queue_recheck_available (device,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+supplicant_iface_peer_updated_cb (NMSupplicantInterface *iface,
+                                  const char *object_path,
+                                  GVariant *properties,
+                                  NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv;
+	NMWifiP2PPeer *found_peer;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (object_path != NULL);
+
+	priv  = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	found_peer = nm_wifi_p2p_peers_find_by_supplicant_path (&priv->peers_lst_head, object_path);
+	if (found_peer) {
+		if (!nm_wifi_p2p_peer_update_from_properties (found_peer, object_path, properties))
+			return;
+
+		update_disconnect_on_connection_peer_missing (self);
+		_peer_dump (self, LOGL_DEBUG, found_peer, "updated", 0);
+	} else {
+		gs_unref_object NMWifiP2PPeer *peer = NULL;
+
+		peer = nm_wifi_p2p_peer_new_from_properties (object_path, properties);
+		if (!peer) {
+			_LOGD (LOGD_WIFI, "invalid P2P peer properties received for %s", object_path);
+			return;
+		}
+
+		peer_add_remove (self, TRUE, peer, TRUE);
+	}
+
+	schedule_peer_list_dump (self);
+}
+
+static void
+supplicant_iface_peer_removed_cb (NMSupplicantInterface *iface,
+                                  const char *object_path,
+                                  NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv;
+	NMWifiP2PPeer *peer;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (object_path != NULL);
+
+	priv  = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	peer = nm_wifi_p2p_peers_find_by_supplicant_path (&priv->peers_lst_head, object_path);
+	if (!peer)
+		return;
+
+	peer_add_remove (self, FALSE, peer, TRUE);
+	schedule_peer_list_dump (self);
+}
+
+static void
+check_group_iface_ready (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);;
+
+	if (!priv->group_iface)
+		return;
+
+	if (nm_supplicant_interface_get_state (priv->group_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		return;
+
+	if (!nm_supplicant_interface_get_p2p_group_joined (priv->group_iface))
+		return;
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+	update_disconnect_on_connection_peer_missing (self);
+
+	nm_device_activate_schedule_stage3_ip_config_start (NM_DEVICE (self));
+}
+
+static void
+supplicant_group_iface_state_cb (NMSupplicantInterface *iface,
+                                 int new_state_i,
+                                 int old_state_i,
+                                 int disconnect_reason,
+                                 gpointer user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	NMSupplicantInterfaceState new_state = new_state_i;
+	NMSupplicantInterfaceState old_state = old_state_i;
+
+	if (new_state == old_state)
+		return;
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "P2P Group supplicant interface state: %s -> %s",
+	       nm_supplicant_interface_state_to_string (old_state),
+	       nm_supplicant_interface_state_to_string (new_state));
+
+	switch (new_state) {
+	case NM_SUPPLICANT_INTERFACE_STATE_READY:
+		_LOGD (LOGD_WIFI, "P2P Group supplicant ready");
+
+		if (!nm_device_set_ip_iface (device, nm_supplicant_interface_get_ifname (priv->group_iface))) {
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+			break;
+		}
+
+		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
+			_set_is_waiting_for_supplicant (self, FALSE);
+
+		check_group_iface_ready (self);
+		break;
+	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+		supplicant_group_interface_release (self);
+
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_DISCONNECTED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+supplicant_group_iface_group_finished_cb (NMSupplicantInterface *iface,
+                                          void *user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+
+	supplicant_group_interface_release (self);
+
+	nm_device_state_changed (NM_DEVICE (self),
+	                         NM_DEVICE_STATE_DISCONNECTED,
+	                         NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+}
+
+static void
+supplicant_iface_group_joined_updated_cb (NMSupplicantInterface *iface,
+                                          GParamSpec *pspec,
+                                          void *user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+
+	check_group_iface_ready (self);
+}
+
+static void
+supplicant_iface_group_started_cb (NMSupplicantInterface *iface,
+                                   NMSupplicantInterface *group_iface,
+                                   NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv;
+
+	g_return_if_fail (self != NULL);
+
+	if (!nm_device_is_activating (NM_DEVICE (self))) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI, "P2P: WPA supplicant notified a group start but we are not trying to connect! Ignoring the event.");
+		return;
+	}
+
+	priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	supplicant_group_interface_release (self);
+	priv->group_iface = g_object_ref (group_iface);
+
+	/* We need to wait for the interface to be ready and the group
+	 * information to be resolved. */
+	g_signal_connect (priv->group_iface,
+	                  "notify::" NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED,
+	                  G_CALLBACK (supplicant_iface_group_joined_updated_cb),
+	                  self);
+
+	g_signal_connect (priv->group_iface,
+	                  NM_SUPPLICANT_INTERFACE_STATE,
+	                  G_CALLBACK (supplicant_group_iface_state_cb),
+	                  self);
+
+	g_signal_connect (priv->group_iface, NM_SUPPLICANT_INTERFACE_GROUP_FINISHED,
+	                  G_CALLBACK (supplicant_group_iface_group_finished_cb),
+	                  self);
+
+	if (nm_supplicant_interface_get_state (priv->group_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		_set_is_waiting_for_supplicant (self, TRUE);
+
+	check_group_iface_ready (self);
+}
+
+static void
+supplicant_group_interface_release (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (!priv->group_iface)
+		return;
+
+	g_signal_handlers_disconnect_by_data (priv->group_iface, self);
+
+	nm_supplicant_interface_p2p_disconnect (priv->group_iface);
+
+	g_clear_object (&priv->group_iface);
+}
+
+static void
+supplicant_interfaces_release (NMDeviceWifiP2P *self, gboolean set_is_waiting)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->peer_dump_id);
+
+	remove_all_peers (self);
+
+	if (priv->mgmt_iface) {
+		_LOGD (LOGD_DEVICE | LOGD_WIFI, "P2P: Releasing WPA supplicant interface.");
+		nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, NULL);
+		g_signal_handlers_disconnect_by_data (priv->mgmt_iface, self);
+		g_clear_object (&priv->mgmt_iface);
+		nm_clear_g_source (&priv->sup_timeout_id);
+	}
+
+	supplicant_group_interface_release (self);
+
+	if (set_is_waiting)
+		_set_is_waiting_for_supplicant (self, TRUE);
+}
+
+static void
+device_state_changed (NMDevice *device,
+                      NMDeviceState new_state,
+                      NMDeviceState old_state,
+                      NMDeviceStateReason reason)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	update_disconnect_on_connection_peer_missing (self);
+
+	if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) {
+		/* Clean up the supplicant interface because in these states the
+		 * device cannot be used.
+		 * Do not clean up for the UNMANAGED to UNAVAILABLE transition which
+		 * will happen during initialization.
+		 */
+		if (priv->mgmt_iface && old_state > new_state)
+			supplicant_interfaces_release (self, TRUE);
+
+		/* TODO: More cleanup needed? */
+	}
+
+	switch (new_state) {
+	case NM_DEVICE_STATE_UNMANAGED:
+		break;
+	case NM_DEVICE_STATE_UNAVAILABLE:
+		if (   !priv->mgmt_iface
+		    || nm_supplicant_interface_get_state (priv->mgmt_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+			_set_is_waiting_for_supplicant (self, TRUE);
+
+		break;
+	case NM_DEVICE_STATE_NEED_AUTH:
+		/* Disconnect? */
+		break;
+	case NM_DEVICE_STATE_IP_CHECK:
+		/* Clear any critical protocol notification in the wifi stack */
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), FALSE);
+		break;
+	case NM_DEVICE_STATE_ACTIVATED:
+		//activation_success_handler (device);
+		break;
+	case NM_DEVICE_STATE_FAILED:
+		/* Clear any critical protocol notification in the wifi stack.
+		 * At this point the IP device may have been removed already. */
+		nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, NULL);
+		if (nm_device_get_ip_ifindex (device) > 0)
+			nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), FALSE);
+		break;
+	case NM_DEVICE_STATE_DISCONNECTED:
+		nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, NULL);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+impl_device_wifi_p2p_start_find (NMDBusObject *obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const NMDBusMethodInfoExtended *method_info,
+                                 GDBusConnection *connection,
+                                 const char *sender,
+                                 GDBusMethodInvocation *invocation,
+                                 GVariant *parameters)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (obj);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	gs_unref_variant GVariant *options = NULL;
+	const char *opts_key;
+	GVariant *opts_val;
+	GVariantIter iter;
+	gint32 timeout = 30;
+
+	g_variant_get (parameters, "(@a{sv})", &options);
+
+	g_variant_iter_init (&iter, options);
+	while (g_variant_iter_next (&iter, "{&sv}", &opts_key, &opts_val)) {
+		_nm_unused gs_unref_variant GVariant *opts_val_free = opts_val;
+
+		if (nm_streq (opts_key, "timeout")) {
+			if (!g_variant_is_of_type (opts_val, G_VARIANT_TYPE_INT32)) {
+				g_dbus_method_invocation_return_error_literal (invocation,
+				                                               NM_DEVICE_ERROR,
+				                                               NM_DEVICE_ERROR_INVALID_ARGUMENT,
+				                                               "\"timeout\" must be an integer \"i\"");
+				return;
+			}
+
+			timeout = g_variant_get_int32 (opts_val);
+			if (timeout <= 0 || timeout > 600) {
+				g_dbus_method_invocation_return_error_literal (invocation,
+				                                               NM_DEVICE_ERROR,
+				                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+				                                               "The timeout for a find operation needs to be in the range of 1-600s.");
+				return;
+			}
+
+			continue;
+		}
+
+		g_dbus_method_invocation_return_error (invocation,
+		                                       NM_DEVICE_ERROR,
+		                                       NM_DEVICE_ERROR_INVALID_ARGUMENT,
+		                                       "Unsupported options key \"%s\"",
+		                                       opts_key);
+		return;
+	}
+
+	if (!priv->mgmt_iface) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ACTIVE,
+		                                               "WPA Supplicant management interface is currently unavailable.");
+		return;
+	}
+
+	nm_supplicant_interface_p2p_start_find (priv->mgmt_iface, timeout);
+
+	g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static void
+impl_device_wifi_p2p_stop_find (NMDBusObject *obj,
+                                const NMDBusInterfaceInfoExtended *interface_info,
+                                const NMDBusMethodInfoExtended *method_info,
+                                GDBusConnection *connection,
+                                const char *sender,
+                                GDBusMethodInvocation *invocation,
+                                GVariant *parameters)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (obj);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (!priv->mgmt_iface) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ACTIVE,
+		                                               "WPA Supplicant management interface is currently unavailable.");
+		return;
+	}
+
+	nm_supplicant_interface_p2p_stop_find (priv->mgmt_iface);
+
+	g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+/*****************************************************************************/
+
+NMSupplicantInterface *
+nm_device_wifi_p2p_get_mgmt_iface (NMDeviceWifiP2P *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE_WIFI_P2P (self), NULL);
+
+	return NM_DEVICE_WIFI_P2P_GET_PRIVATE (self)->mgmt_iface;
+}
+
+void
+nm_device_wifi_p2p_set_mgmt_iface (NMDeviceWifiP2P *self,
+                                   NMSupplicantInterface *iface)
+{
+	NMDeviceWifiP2PPrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE_WIFI_P2P (self));
+	g_return_if_fail (!iface || NM_IS_SUPPLICANT_INTERFACE (iface));
+
+	priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (priv->mgmt_iface == iface)
+		goto done;
+
+	supplicant_interfaces_release (self, FALSE);
+
+	if (!iface)
+		goto done;
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI, "P2P: WPA supplicant management interface changed to %s.",
+	       nm_supplicant_interface_get_object_path (iface));
+
+	priv->mgmt_iface = g_object_ref (iface);
+
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_STATE,
+	                  G_CALLBACK (supplicant_iface_state_cb),
+	                  self);
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_PEER_UPDATED,
+	                  G_CALLBACK (supplicant_iface_peer_updated_cb),
+	                  self);
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_PEER_REMOVED,
+	                  G_CALLBACK (supplicant_iface_peer_removed_cb),
+	                  self);
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_GROUP_STARTED,
+	                  G_CALLBACK (supplicant_iface_group_started_cb),
+	                  self);
+done:
+	nm_device_queue_recheck_available (NM_DEVICE (self),
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+	_set_is_waiting_for_supplicant (self,
+	                                   !priv->mgmt_iface
+	                                || (  nm_supplicant_interface_get_state (priv->mgmt_iface)
+	                                    < NM_SUPPLICANT_INTERFACE_STATE_READY));
+}
+
+void
+nm_device_wifi_p2p_remove (NMDeviceWifiP2P* self)
+{
+	g_signal_emit_by_name (self, NM_DEVICE_REMOVED);
+}
+
+/*****************************************************************************/
+
+static const char *
+get_type_description (NMDevice *device)
+{
+	return "wifi-p2p";
+}
+
+/*****************************************************************************/
+
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"PeerAdded",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("peer", "o"),
+	),
+);
+
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"PeerRemoved",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("peer", "o"),
+	),
+);
+
+static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_WIFI_P2P,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"StartFind",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("options", "a{sv}"),
+					),
+				),
+				.handle = impl_device_wifi_p2p_start_find,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"StopFind",
+				),
+				.handle = impl_device_wifi_p2p_stop_find,
+			),
+		),
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_wifi_p2p_peer_added,
+			&nm_signal_info_wifi_p2p_peer_removed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("HwAddress",  "s",  NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Peers",      "ao", NM_DEVICE_WIFI_P2P_PEERS),
+		),
+	),
+	.legacy_property_changed = FALSE,
+};
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (object);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	const char **list;
+
+	switch (prop_id) {
+	case PROP_PEERS:
+		list = nm_wifi_p2p_peers_get_paths (&priv->peers_lst_head);
+		g_value_take_boxed (value, nm_utils_strv_make_deep_copied (list));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_wifi_p2p_init (NMDeviceWifiP2P * self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	c_list_init (&priv->peers_lst_head);
+
+	priv->sup_mgr = g_object_ref (nm_supplicant_manager_get ());
+}
+
+static void
+constructed (GObject *object)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (object);
+
+	G_OBJECT_CLASS (nm_device_wifi_p2p_parent_class)->constructed (object);
+
+	_set_is_waiting_for_supplicant (self, TRUE);
+}
+
+NMDeviceWifiP2P *
+nm_device_wifi_p2p_new (const char *iface)
+{
+	return g_object_new (NM_TYPE_DEVICE_WIFI_P2P,
+	                     NM_DEVICE_IFACE, iface,
+	                     NM_DEVICE_TYPE_DESC, "802.11 Wi-Fi P2P",
+	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI_P2P,
+	                     NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIFI,
+	                     NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN,
+	                     NULL);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (object);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (object);
+
+	g_clear_object (&priv->sup_mgr);
+
+	supplicant_interfaces_release (self, FALSE);
+
+	G_OBJECT_CLASS (nm_device_wifi_p2p_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMDeviceWifiP2P *peer = NM_DEVICE_WIFI_P2P (object);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (peer);
+
+	nm_assert (c_list_is_empty (&priv->peers_lst_head));
+
+	G_OBJECT_CLASS (nm_device_wifi_p2p_parent_class)->finalize (object);
+}
+
+static void
+nm_device_wifi_p2p_class_init (NMDeviceWifiP2PClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
+
+	object_class->constructed  = constructed;
+	object_class->get_property = get_property;
+	object_class->dispose      = dispose;
+	object_class->finalize     = finalize;
+
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_wifi_p2p);
+
+	device_class->connection_type_supported        = NM_SETTING_WIFI_P2P_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_WIFI_P2P_SETTING_NAME;
+	device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WIFI_P2P);
+	device_class->get_type_description             = get_type_description;
+
+	/* Do we need compatibility checking or is the default good enough? */
+	device_class->is_available                     = is_available;
+	device_class->check_connection_compatible      = check_connection_compatible;
+	device_class->complete_connection              = complete_connection;
+
+	device_class->act_stage1_prepare               = act_stage1_prepare;
+	device_class->act_stage2_config                = act_stage2_config;
+	device_class->get_configured_mtu               = get_configured_mtu;
+	device_class->get_auto_ip_config_method        = get_auto_ip_config_method;
+	device_class->act_stage3_ip_config_start      = act_stage3_ip_config_start;
+
+	device_class->deactivate                       = deactivate;
+	device_class->unmanaged_on_quit                = unmanaged_on_quit;
+
+	device_class->state_changed                    = device_state_changed;
+
+	obj_properties[PROP_PEERS] =
+	    g_param_spec_boxed (NM_DEVICE_WIFI_P2P_PEERS, "", "",
+	                        G_TYPE_STRV,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/src/devices/wifi/nm-device-wifi-p2p.h b/src/devices/wifi/nm-device-wifi-p2p.h
new file mode 100644
index 00000000..a13eef15
--- /dev/null
+++ b/src/devices/wifi/nm-device-wifi-p2p.h
@@ -0,0 +1,50 @@
+/* NetworkManager -- Wi-Fi P2P Device
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_DEVICE_WIFI_P2P_H__
+#define __NM_DEVICE_WIFI_P2P_H__
+
+#include "devices/nm-device.h"
+#include "supplicant/nm-supplicant-interface.h"
+
+#define NM_TYPE_DEVICE_WIFI_P2P            (nm_device_wifi_p2p_get_type ())
+#define NM_DEVICE_WIFI_P2P(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2P))
+#define NM_DEVICE_WIFI_P2P_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2PClass))
+#define NM_IS_DEVICE_WIFI_P2P(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_WIFI_P2P))
+#define NM_IS_DEVICE_WIFI_P2P_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_WIFI_P2P))
+#define NM_DEVICE_WIFI_P2P_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2PClass))
+
+#define NM_DEVICE_WIFI_P2P_PEERS       "peers"
+#define NM_DEVICE_WIFI_P2P_GROUPS      "groups"
+
+typedef struct _NMDeviceWifiP2P NMDeviceWifiP2P;
+typedef struct _NMDeviceWifiP2PClass NMDeviceWifiP2PClass;
+
+GType nm_device_wifi_p2p_get_type (void);
+
+NMDeviceWifiP2P *nm_device_wifi_p2p_new (const char *iface);
+
+NMSupplicantInterface * nm_device_wifi_p2p_get_mgmt_iface (NMDeviceWifiP2P *self);
+void                    nm_device_wifi_p2p_set_mgmt_iface (NMDeviceWifiP2P *self,
+                                                           NMSupplicantInterface  *iface);
+
+void nm_device_wifi_p2p_remove (NMDeviceWifiP2P *self);
+
+#endif /* __NM_DEVICE_WIFI_P2P_H__ */
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 2ce84618..64869672 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -24,10 +24,9 @@
 #include "nm-device-wifi.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 
+#include "nm-device-wifi-p2p.h"
 #include "nm-wifi-ap.h"
 #include "nm-common-macros.h"
 #include "devices/nm-device.h"
@@ -79,6 +78,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 
 enum {
 	SCANNING_PROHIBITED,
+	P2P_DEVICE_CREATED,
 
 	LAST_SIGNAL
 };
@@ -124,6 +124,8 @@ typedef struct {
 	guint             wps_timeout_id;
 
 	NMSettingWirelessWakeOnWLan wowlan_restore;
+
+	NMDeviceWifiP2P  *p2p_device;
 } NMDeviceWifiPrivate;
 
 struct _NMDeviceWifi
@@ -186,6 +188,10 @@ static void supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
                                                  GParamSpec *pspec,
                                                  NMDeviceWifi *self);
 
+static void supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
+                                                   GParamSpec *pspec,
+                                                   NMDeviceWifi *self);
+
 static void request_wireless_scan (NMDeviceWifi *self,
                                    gboolean periodic,
                                    gboolean force_if_scanning,
@@ -198,6 +204,8 @@ static void ap_add_remove (NMDeviceWifi *self,
 
 static void _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset);
 
+static void recheck_p2p_availability (NMDeviceWifi *self);
+
 /*****************************************************************************/
 
 static void
@@ -291,6 +299,10 @@ supplicant_interface_acquire (NMDeviceWifi *self)
 	                  "notify::" NM_SUPPLICANT_INTERFACE_CURRENT_BSS,
 	                  G_CALLBACK (supplicant_iface_notify_current_bss),
 	                  self);
+	g_signal_connect (priv->sup_iface,
+	                  "notify::" NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE,
+	                  G_CALLBACK (supplicant_iface_notify_p2p_available),
+	                  self);
 
 	_notify_scanning (self);
 
@@ -347,6 +359,11 @@ supplicant_interface_release (NMDeviceWifi *self)
 		g_clear_object (&priv->sup_iface);
 	}
 
+	if (priv->p2p_device) {
+		/* Signal to P2P device to also release its reference */
+		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, NULL);
+	}
+
 	_notify_scanning (self);
 }
 
@@ -732,7 +749,7 @@ check_connection_available (NMDevice *device,
 
 	/* Hidden SSIDs obviously don't always appear in the scan list either.
 	 *
-	 * For an explict user-activation-request, a connection is considered
+	 * For an explicit user-activation-request, a connection is considered
 	 * available because for hidden Wi-Fi, clients didn't consistently
 	 * set the 'hidden' property to indicate hidden SSID networks.  If
 	 * activating but the network isn't available let the device recheck
@@ -838,7 +855,7 @@ complete_connection (NMDevice *device,
 		ssid = nm_wifi_ap_get_ssid (ap);
 
 	if (ssid == NULL) {
-		/* The AP must be hidden.  Connecting to a WiFi AP requires the SSID
+		/* The AP must be hidden.  Connecting to a Wi-Fi AP requires the SSID
 		 * as part of the initial handshake, so check the connection details
 		 * for the SSID.  The AP object will still be used for encryption
 		 * settings and such.
@@ -909,18 +926,6 @@ complete_connection (NMDevice *device,
 				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_MAC_ADDRESS);
 				return FALSE;
 			}
-		} else {
-			guint8 tmp[ETH_ALEN];
-
-			/* Lock the connection to this device by default if it uses a
-			 * permanent MAC address (ie not a 'locally administered' one)
-			 */
-			nm_utils_hwaddr_aton (perm_hw_addr, tmp, ETH_ALEN);
-			if (!(tmp[0] & 0x02)) {
-				g_object_set (G_OBJECT (s_wifi),
-				              NM_SETTING_WIRELESS_MAC_ADDRESS, perm_hw_addr,
-				              NULL);
-			}
 		}
 	}
 
@@ -981,7 +986,7 @@ can_auto_connect (NMDevice *device,
 	g_return_val_if_fail (s_wifi, FALSE);
 
 	/* Always allow autoconnect for AP and non-autoconf Ad-Hoc */
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	method = nm_utils_get_ip_config_method (connection, AF_INET);
 	mode = nm_setting_wireless_get_mode (s_wifi);
 	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP))
 		return TRUE;
@@ -1197,7 +1202,7 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
 	                       NM_DEVICE_AUTH_REQUEST,
 	                       invocation,
 	                       NULL,
-	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                       NM_AUTH_PERMISSION_WIFI_SCAN,
 	                       TRUE,
 	                       dbus_request_scan_cb,
 	                       options ? g_variant_ref (options) : NULL);
@@ -1236,7 +1241,7 @@ scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 		return FALSE;
 	case NM_DEVICE_STATE_ACTIVATED:
 		/* Prohibit periodic scans when connected; we ask the supplicant to
-		 * background scan for us, unless the connection is locked to a specifc
+		 * background scan for us, unless the connection is locked to a specific
 		 * BSSID.
 		 */
 		if (periodic)
@@ -1685,20 +1690,37 @@ wifi_secrets_cb (NMActRequest *req,
 	g_return_if_fail (nm_act_request_get_settings_connection (req) == connection);
 
 	if (error) {
-		_LOGW (LOGD_WIFI, "%s", error->message);
-
-		if (g_error_matches (error, NM_AGENT_MANAGER_ERROR,
-		                     NM_AGENT_MANAGER_ERROR_USER_CANCELED)) {
-			/* Don't wait for WPS timeout on an explicit cancel. */
-			nm_clear_g_source (&priv->wps_timeout_id);
-		}
+		_LOGW (LOGD_WIFI, "no secrets: %s", error->message);
 
-		if (!priv->wps_timeout_id) {
-			/* Fail the device only if the WPS period is over too. */
-			nm_device_state_changed (device,
-			                         NM_DEVICE_STATE_FAILED,
-			                         NM_DEVICE_STATE_REASON_NO_SECRETS);
-		}
+		/* Even if WPS is still pending, let's abort the activation when the secret
+		 * request returns.
+		 *
+		 * This means, a user can only effectively use WPS when also running a secret
+		 * agent, and pressing the push button while being prompted for the password.
+		 * Note, that in the secret prompt the user can see that WPS is in progress
+		 * (via the NM_SECRET_AGENT_GET_SECRETS_FLAG_WPS_PBC_ACTIVE flag).
+		 *
+		 * Previously, WPS was not cancelled when the secret request returns.
+		 * Note that in common use-cases WPS is enabled in the connection profile
+		 * but it won't succeed (because it's disabled in the AP or because the
+		 * user is not prepared to press the push button).
+		 * That means for example, during boot we would try to autoconnect with WPS.
+		 * At that point, there is no secret-agent running, and WPS is pending for
+		 * full 30 seconds. If in the meantime a secret agent registers (because
+		 * of logging into the DE), the profile is still busy waiting for WPS to time
+		 * out. Only after that delay, autoconnect starts again (note that autoconnect gets
+		 * not blocked in this case, because a secret agent registered in the meantime).
+		 *
+		 * It seems wrong to continue doing WPS if the user is not aware
+		 * that WPS is ongoing. The user is required to perform an action (push button),
+		 * and must be told via the secret prompt.
+		 * If no secret-agent is running, if the user cancels the secret-request, or any
+		 * other error to obtain secrets, the user apparently does not want WPS either.
+		 */
+		nm_clear_g_source (&priv->wps_timeout_id);
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
 	} else
 		nm_device_activate_schedule_stage1_device_prepare (device);
 }
@@ -1851,9 +1873,10 @@ need_new_8021x_secrets (NMDeviceWifi *self,
 	NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
 	NMConnection *connection;
 
-	g_assert (setting_name != NULL);
+	g_return_val_if_fail (setting_name, FALSE);
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
 	g_return_val_if_fail (connection != NULL, FALSE);
 
 	/* 802.1x stuff only happens in the supplicant's ASSOCIATED state when it's
@@ -1905,10 +1928,11 @@ need_new_wpa_psk (NMDeviceWifi *self,
 	NMConnection *connection;
 	const char *key_mgmt = NULL;
 
-	g_assert (setting_name != NULL);
+	g_return_val_if_fail (setting_name, FALSE);
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
-	g_return_val_if_fail (connection != NULL, FALSE);
+
+	g_return_val_if_fail (connection, FALSE);
 
 	/* A bad PSK will cause the supplicant to disconnect during the 4-way handshake */
 	if (old_state != NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE)
@@ -2021,6 +2045,10 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 	    && new_state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
 		priv->ssid_found = TRUE;
 
+	if (   old_state < NM_SUPPLICANT_INTERFACE_STATE_READY
+	    && new_state >= NM_SUPPLICANT_INTERFACE_STATE_READY)
+		recheck_p2p_availability (self);
+
 	switch (new_state) {
 	case NM_SUPPLICANT_INTERFACE_STATE_READY:
 		_LOGD (LOGD_WIFI, "supplicant ready");
@@ -2040,15 +2068,12 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 		 * schedule the next activation stage.
 		 */
 		if (devstate == NM_DEVICE_STATE_CONFIG) {
-			NMConnection *connection;
 			NMSettingWireless *s_wifi;
 			GBytes *ssid;
 			gs_free char *ssid_str = NULL;
 
-			connection = nm_device_get_applied_connection (NM_DEVICE (self));
-			g_return_if_fail (connection);
+			s_wifi = nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_WIRELESS);
 
-			s_wifi = nm_connection_get_setting_wireless (connection);
 			g_return_if_fail (s_wifi);
 
 			ssid = nm_setting_wireless_get_ssid (s_wifi);
@@ -2200,6 +2225,67 @@ supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
 	}
 }
 
+/* We bind the existence of the P2P device to a wifi device that is being
+ * managed by NetworkManager and is capable of P2P operation.
+ * Note that some care must be taken here, because we don't want to re-create
+ * the device every time the supplicant interface is destroyed (e.g. due to
+ * a suspend/resume cycle).
+ * Therefore, this function will be called when a change in the P2P capability
+ * is detected and the supplicant interface has been initialised.
+ */
+static void
+recheck_p2p_availability (NMDeviceWifi *self)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	gboolean p2p_available;
+
+	g_object_get (priv->sup_iface,
+	              NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, &p2p_available,
+	              NULL);
+
+	if (p2p_available && !priv->p2p_device) {
+		gs_free char *iface_name = NULL;
+
+		/* Create a P2P device. "p2p-dev-" is the same prefix as chosen by
+		 * wpa_supplicant internally.
+		 */
+		iface_name = g_strconcat ("p2p-dev-", nm_device_get_iface (NM_DEVICE (self)), NULL);
+
+		priv->p2p_device = nm_device_wifi_p2p_new (iface_name);
+
+		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, priv->sup_iface);
+
+		g_signal_emit (self, signals[P2P_DEVICE_CREATED], 0, priv->p2p_device);
+		g_object_add_weak_pointer (G_OBJECT (priv->p2p_device), (gpointer*) &priv->p2p_device);
+		g_object_unref (priv->p2p_device);
+		return;
+	}
+
+	if (p2p_available && priv->p2p_device) {
+		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, priv->sup_iface);
+		return;
+	}
+
+	if (!p2p_available && priv->p2p_device) {
+		/* Destroy the P2P device. */
+		g_object_remove_weak_pointer (G_OBJECT (priv->p2p_device), (gpointer*) &priv->p2p_device);
+		nm_device_wifi_p2p_remove (g_steal_pointer (&priv->p2p_device));
+		return;
+	}
+}
+
+static void
+supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
+                                       GParamSpec *pspec,
+                                       NMDeviceWifi *self)
+{
+	/* Do not update when the interface is still initializing. */
+	if (nm_supplicant_interface_get_state (iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		return;
+
+	recheck_p2p_availability (self);
+}
+
 static gboolean
 handle_auth_or_fail (NMDeviceWifi *self,
                      NMActRequest *req,
@@ -2469,7 +2555,7 @@ wake_on_wlan_enable (NMDeviceWifi *self)
 	NMSettingWirelessWakeOnWLan wowl;
 	NMSettingWireless *s_wireless;
 
-	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_WIRELESS);
+	s_wireless = nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_WIRELESS);
 	if (s_wireless) {
 		wowl = nm_setting_wireless_get_wake_on_wlan (s_wireless);
 		if (wowl != NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT)
@@ -2646,7 +2732,8 @@ set_powersave (NMDevice *device)
 	NMSettingWireless *s_wireless;
 	NMSettingWirelessPowersave val;
 
-	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+	s_wireless = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+
 	g_return_if_fail (s_wireless);
 
 	val = nm_setting_wireless_get_powersave (s_wireless);
@@ -2787,50 +2874,29 @@ out:
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip4;
-	const char *method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
-
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	if (s_ip4)
-		method = nm_setting_ip_config_get_method (s_ip4);
-
-	/* Indicate that a critical protocol is about to start */
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0)
-		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), TRUE);
-
-	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
+	gboolean indicate_addressing_running;
 	NMConnection *connection;
-	NMSettingIPConfig *s_ip6;
-	const char *method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
+	const char *method;
 
 	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
-	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	if (s_ip6)
-		method = nm_setting_ip_config_get_method (s_ip6);
+	method = nm_utils_get_ip_config_method (connection, addr_family);
+	if (addr_family == AF_INET)
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+	else {
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		                                                    NM_SETTING_IP6_CONFIG_METHOD_DHCP);
+	}
 
-	/* Indicate that a critical protocol is about to start */
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0 ||
-	    strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0)
-		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), TRUE);
+	if (indicate_addressing_running)
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), TRUE);
 
-	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static guint32
@@ -2865,90 +2931,52 @@ is_static_wep (NMConnection *connection)
 }
 
 static NMActStageReturn
-handle_ip_config_timeout (NMDeviceWifi *self,
-                          NMConnection *connection,
-                          gboolean may_fail,
-                          gboolean *chain_up,
-                          NMDeviceStateReason *out_failure_reason)
+act_stage4_ip_config_timeout (NMDevice *device,
+                              int addr_family,
+                              NMDeviceStateReason *out_failure_reason)
 {
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip;
+	gboolean may_fail;
+
+	connection = nm_device_get_applied_connection (device);
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
+	may_fail = nm_setting_ip_config_get_may_fail (s_ip);
 
-	g_return_val_if_fail (connection != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+	if (priv->mode == NM_802_11_MODE_AP)
+		goto call_parent;
 
-	if (NM_DEVICE_WIFI_GET_PRIVATE (self)->mode == NM_802_11_MODE_AP) {
-		*chain_up = TRUE;
-		return NM_ACT_STAGE_RETURN_FAILURE;
+	if (   may_fail
+	    || !is_static_wep (connection)) {
+		/* Not static WEP or failure allowed; let superclass handle it */
+		goto call_parent;
 	}
 
 	/* If IP configuration times out and it's a static WEP connection, that
 	 * usually means the WEP key is wrong.  WEP's Open System auth mode has
 	 * no provision for figuring out if the WEP key is wrong, so you just have
-	 * to wait for DHCP to fail to figure it out.  For all other WiFi security
+	 * to wait for DHCP to fail to figure it out.  For all other Wi-Fi security
 	 * types (open, WPA, 802.1x, etc) if the secrets/certs were wrong the
 	 * connection would have failed before IP configuration.
-	 */
-	if (!may_fail && is_static_wep (connection)) {
-		/* Activation failed, we must have bad encryption key */
-		_LOGW (LOGD_DEVICE | LOGD_WIFI,
-		       "Activation: (wifi) could not get IP configuration for connection '%s'.",
-		       nm_connection_get_id (connection));
+	 *
+	 * Activation failed, we must have bad encryption key */
+	_LOGW (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) could not get IP configuration for connection '%s'.",
+	       nm_connection_get_id (connection));
 
-		if (handle_auth_or_fail (self, NULL, TRUE)) {
-			_LOGI (LOGD_DEVICE | LOGD_WIFI,
-			       "Activation: (wifi) asking for new secrets");
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-		} else {
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-			ret = NM_ACT_STAGE_RETURN_FAILURE;
-		}
-	} else {
-		/* Not static WEP or failure allowed; let superclass handle it */
-		*chain_up = TRUE;
+	if (!handle_auth_or_fail (self, NULL, TRUE)) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	return ret;
-}
-
-static NMActStageReturn
-act_stage4_ip4_config_timeout (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip4;
-	gboolean may_fail = FALSE, chain_up = FALSE;
-	NMActStageReturn ret;
-
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	may_fail = nm_setting_ip_config_get_may_fail (s_ip4);
-
-	ret = handle_ip_config_timeout (NM_DEVICE_WIFI (device), connection, may_fail, &chain_up, out_failure_reason);
-	if (chain_up)
-		ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip4_config_timeout (device, out_failure_reason);
-
-	return ret;
-}
-
-static NMActStageReturn
-act_stage4_ip6_config_timeout (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip6;
-	gboolean may_fail = FALSE, chain_up = FALSE;
-	NMActStageReturn ret;
-
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	may_fail = nm_setting_ip_config_get_may_fail (s_ip6);
-
-	ret = handle_ip_config_timeout (NM_DEVICE_WIFI (device), connection, may_fail, &chain_up, out_failure_reason);
-	if (chain_up)
-		ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip6_config_timeout (device, out_failure_reason);
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) asking for new secrets");
+	return NM_ACT_STAGE_RETURN_POSTPONE;
 
-	return ret;
+call_parent:
+	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip_config_timeout (device, addr_family, out_failure_reason);
 }
 
 static void
@@ -3279,7 +3307,7 @@ nm_device_wifi_new (const char *iface, NMDeviceWifiCapabilities capabilities)
 {
 	return g_object_new (NM_TYPE_DEVICE_WIFI,
 	                     NM_DEVICE_IFACE, iface,
-	                     NM_DEVICE_TYPE_DESC, "802.11 WiFi",
+	                     NM_DEVICE_TYPE_DESC, "802.11 Wi-Fi",
 	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI,
 	                     NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIFI,
 	                     NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN,
@@ -3305,6 +3333,12 @@ dispose (GObject *object)
 
 	remove_all_aps (self);
 
+	if (priv->p2p_device) {
+		/* Destroy the P2P device. */
+		g_object_remove_weak_pointer (G_OBJECT (priv->p2p_device), (gpointer*) &priv->p2p_device);
+		nm_device_wifi_p2p_remove (g_steal_pointer (&priv->p2p_device));
+	}
+
 	G_OBJECT_CLASS (nm_device_wifi_parent_class)->dispose (object);
 }
 
@@ -3350,10 +3384,8 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
 	device_class->get_configured_mtu = get_configured_mtu;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
-	device_class->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
-	device_class->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
+	device_class->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
 	device_class->deactivate = deactivate;
 	device_class->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
 	device_class->unmanaged_on_quit = unmanaged_on_quit;
@@ -3417,4 +3449,12 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_prohibited),
 	                  NULL, NULL, NULL,
 	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
+
+	signals[P2P_DEVICE_CREATED] =
+	    g_signal_new (NM_DEVICE_WIFI_P2P_DEVICE_CREATED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0, NULL, NULL,
+	                  g_cclosure_marshal_VOID__OBJECT,
+	                  G_TYPE_NONE, 1, NM_TYPE_DEVICE);
 }
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index 1c555f5f..116ad11e 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -40,6 +40,7 @@
 #define NM_DEVICE_WIFI_LAST_SCAN           "last-scan"
 
 #define NM_DEVICE_WIFI_SCANNING_PROHIBITED    "scanning-prohibited"
+#define NM_DEVICE_WIFI_P2P_DEVICE_CREATED     "p2p-device-created"
 
 typedef struct _NMDeviceWifi NMDeviceWifi;
 typedef struct _NMDeviceWifiClass NMDeviceWifiClass;
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index a3da9791..d668f0d8 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -22,7 +22,6 @@
 
 #include "nm-iwd-manager.h"
 
-#include <string.h>
 #include <net/if.h>
 
 #include "nm-logging.h"
@@ -48,6 +47,7 @@ typedef struct {
 
 typedef struct {
 	NMManager *manager;
+	NMSettings *settings;
 	GCancellable *cancellable;
 	gboolean running;
 	GDBusObjectManager *object_manager;
@@ -136,6 +136,7 @@ agent_dbus_method_cb (GDBusConnection *connection,
 	int ifindex;
 	NMDevice *device;
 	gs_free char *name_owner = NULL;
+	int errsv;
 
 	/* Be paranoid and check the sender address */
 	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager));
@@ -171,8 +172,9 @@ agent_dbus_method_cb (GDBusConnection *connection,
 
 	ifindex = if_nametoindex (ifname);
 	if (!ifindex) {
+		errsv = errno;
 		_LOGD ("agent-request: if_nametoindex failed for Name %s for Device at %s: %i",
-		       ifname, device_path, errno);
+		       ifname, device_path, errsv);
 		goto return_error;
 	}
 
@@ -338,6 +340,7 @@ set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
 	const char *ifname;
 	int ifindex;
 	NMDevice *device;
+	int errsv;
 
 	ifname = get_property_string_or_null (proxy, "Name");
 	if (!ifname) {
@@ -349,8 +352,9 @@ set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
 	ifindex = if_nametoindex (ifname);
 
 	if (!ifindex) {
+		errsv = errno;
 		_LOGE ("if_nametoindex failed for Name %s for Device at %s: %i",
-		       ifname, g_dbus_proxy_get_object_path (proxy), errno);
+		       ifname, g_dbus_proxy_get_object_path (proxy), errsv);
 		return;
 	}
 
@@ -363,31 +367,35 @@ set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
 	nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object);
 }
 
-/* Create an in-memory NMConnection for a WPA2-Enterprise network that
- * has been preprovisioned with an IWD config file so that NM autoconnect
- * mechanism and the clients know this networks needs no additional EAP
- * configuration from the user.  Only do this if no existing connection
- * SSID and security type match that network yet.
+/* Look up an existing NMSettingsConnection for a WPA2-Enterprise network
+ * that has been preprovisioned with an IWD config file, or create a new
+ * in-memory connection object so that NM autoconnect mechanism and the
+ * clients know this networks needs no additional EAP configuration from
+ * the user.
  */
 static NMSettingsConnection *
 mirror_8021x_connection (NMIwdManager *self,
-                         const char *name)
+                         const char *name,
+                         gboolean create_new)
 {
-	NMSettings *settings = NM_SETTINGS_GET;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
 	NMSettingsConnection *const*iter;
 	gs_unref_object NMConnection *connection = NULL;
-	NMSettingsConnection *settings_connection;
+	NMSettingsConnection *settings_connection = NULL;
 	char uuid[37];
 	NMSetting *setting;
 	GError *error = NULL;
 	gs_unref_bytes GBytes *new_ssid = NULL;
 
-	for (iter = nm_settings_get_connections (settings, NULL); *iter; iter++) {
+	for (iter = nm_settings_get_connections (priv->settings, NULL); *iter; iter++) {
 		NMSettingsConnection *sett_conn = *iter;
 		NMConnection *conn = nm_settings_connection_get_connection (sett_conn);
 		NMIwdNetworkSecurity security;
 		gs_free char *ssid_name = NULL;
 		NMSettingWireless *s_wifi;
+		NMSetting8021x *s_8021x;
+		gboolean external = FALSE;
+		guint i;
 
 		security = nm_wifi_connection_get_iwd_security (conn, NULL);
 		if (security != NM_IWD_NETWORK_SECURITY_8021X)
@@ -399,14 +407,30 @@ mirror_8021x_connection (NMIwdManager *self,
 
 		ssid_name = _nm_utils_ssid_to_utf8 (nm_setting_wireless_get_ssid (s_wifi));
 
-		/* We already have an NMSettingsConnection matching this
-		 * KnownNetwork, whether it's saved or an in-memory connection
-		 * potentially created by ourselves.  Nothing to do here.
-		 */
-		if (nm_streq (ssid_name, name))
-			return NULL;
+		if (!nm_streq (ssid_name, name))
+			continue;
+
+		s_8021x = nm_connection_get_setting_802_1x (conn);
+		for (i = 0; i < nm_setting_802_1x_get_num_eap_methods (s_8021x); i++) {
+			if (nm_streq (nm_setting_802_1x_get_eap_method (s_8021x, i), "external")) {
+				external = TRUE;
+				break;
+			}
+		}
+
+		/* Prefer returning connections for EAP method "external" */
+		if (!settings_connection || external)
+			settings_connection = sett_conn;
 	}
 
+	/* If we already have an NMSettingsConnection matching this
+	 * KnownNetwork, whether it's saved or an in-memory connection
+	 * potentially created by ourselves then we have nothing left to
+	 * do here.
+	 */
+	if (settings_connection || !create_new)
+		return settings_connection;
+
 	connection = nm_simple_connection_new ();
 
 	setting = NM_SETTING (g_object_new (NM_TYPE_SETTING_CONNECTION,
@@ -446,7 +470,7 @@ mirror_8021x_connection (NMIwdManager *self,
 	if (!nm_connection_normalize (connection, NULL, NULL, NULL))
 		return NULL;
 
-	settings_connection = nm_settings_add_connection (settings, connection,
+	settings_connection = nm_settings_add_connection (priv->settings, connection,
 	                                                  FALSE, &error);
 	if (!settings_connection) {
 		_LOGW ("failed to add a mirror NMConnection for IWD's Known Network '%s': %s",
@@ -526,16 +550,19 @@ interface_added (GDBusObjectManager *object_manager, GDBusObject *object,
 		id = known_network_id_new (name, security);
 
 		data = g_hash_table_lookup (priv->known_networks, id);
-		if (data)
+		if (data) {
+			_LOGW ("DBus error: KnownNetwork already exists ('%s', %s)",
+			       name, type_str);
 			g_free (id);
-		else {
+			nm_g_object_ref_set (&data->known_network, proxy);
+		} else {
 			data = g_slice_new0 (KnownNetworkData);
 			data->known_network = g_object_ref (proxy);
 			g_hash_table_insert (priv->known_networks, id, data);
 		}
 
 		if (security == NM_IWD_NETWORK_SECURITY_8021X) {
-			sett_conn = mirror_8021x_connection (self, name);
+			sett_conn = mirror_8021x_connection (self, name, TRUE);
 
 			if (   sett_conn
 			    && sett_conn != data->mirror_connection) {
@@ -593,6 +620,55 @@ interface_removed (GDBusObjectManager *object_manager, GDBusObject *object,
 	}
 }
 
+static void
+connection_removed (NMSettings *settings,
+                    NMSettingsConnection *sett_conn,
+                    gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	NMConnection *conn = nm_settings_connection_get_connection (sett_conn);
+	NMSettingWireless *s_wireless;
+	gboolean mapped;
+	KnownNetworkData *data;
+	KnownNetworkId id;
+
+	id.security = nm_wifi_connection_get_iwd_security (conn, &mapped);
+	if (!mapped)
+		return;
+
+	s_wireless = nm_connection_get_setting_wireless (conn);
+	id.name = _nm_utils_ssid_to_utf8 (nm_setting_wireless_get_ssid (s_wireless));
+	data = g_hash_table_lookup (priv->known_networks, &id);
+	g_free ((char *) id.name);
+	if (!data)
+		return;
+
+	if (id.security == NM_IWD_NETWORK_SECURITY_8021X) {
+		NMSettingsConnection *new_mirror_conn;
+
+		if (data->mirror_connection != sett_conn)
+			return;
+
+		g_clear_object (&data->mirror_connection);
+
+		/* Don't call Forget for an 8021x network until there's no
+		 * longer *any* matching NMSettingsConnection (debatable)
+		 */
+		new_mirror_conn = mirror_8021x_connection (self, id.name, FALSE);
+		if (new_mirror_conn) {
+			data->mirror_connection = g_object_ref (new_mirror_conn);
+			return;
+		}
+	}
+
+	if (!priv->running)
+		return;
+
+	g_dbus_proxy_call (data->known_network, "Forget",
+	                   NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
 static gboolean
 _om_has_name_owner (GDBusObjectManager *object_manager)
 {
@@ -744,7 +820,7 @@ got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
 	                                   &priv->agent_path,
 	                                   &error);
 	if (!priv->agent_id) {
-		_LOGE ("failed to export the IWD Agent: PSK/8021x WiFi networks may not work: %s",
+		_LOGE ("failed to export the IWD Agent: PSK/8021x Wi-Fi networks may not work: %s",
 		       error->message);
 		g_clear_error (&error);
 	}
@@ -824,6 +900,10 @@ nm_iwd_manager_init (NMIwdManager *self)
 	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED,
 	                  G_CALLBACK (device_added), self);
 
+	priv->settings = g_object_ref (nm_settings_get ());
+	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
+	                  G_CALLBACK (connection_removed), self);
+
 	priv->cancellable = g_cancellable_new ();
 
 	priv->known_networks = g_hash_table_new_full ((GHashFunc) known_network_id_hash,
@@ -844,6 +924,14 @@ dispose (GObject *object)
 
 	nm_clear_g_cancellable (&priv->cancellable);
 
+	if (priv->settings) {
+		g_signal_handlers_disconnect_by_data (priv->settings, self);
+		g_clear_object (&priv->settings);
+	}
+
+	/* This may trigger mirror connection removals so it happens
+	 * after the g_signal_handlers_disconnect_by_data above.
+	 */
 	nm_clear_pointer (&priv->known_networks, g_hash_table_destroy);
 
 	if (priv->manager) {
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index e5573383..87daff59 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -23,7 +23,6 @@
 
 #include "nm-wifi-ap.h"
 
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-setting-wireless.h"
@@ -425,6 +424,8 @@ security_from_vardict (GVariant *security)
 		    g_strv_contains (array, "wpa-fils-sha256") ||
 		    g_strv_contains (array, "wpa-fils-sha384"))
 			flags |= NM_802_11_AP_SEC_KEY_MGMT_802_1X;
+		if (g_strv_contains (array, "sae"))
+			flags |= NM_802_11_AP_SEC_KEY_MGMT_SAE;
 		g_free (array);
 	}
 
@@ -1390,7 +1391,8 @@ nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
 	| NM_802_11_AP_SEC_GROUP_TKIP \
 	| NM_802_11_AP_SEC_GROUP_CCMP \
 	| NM_802_11_AP_SEC_KEY_MGMT_PSK \
-	| NM_802_11_AP_SEC_KEY_MGMT_802_1X )
+	| NM_802_11_AP_SEC_KEY_MGMT_802_1X \
+	| NM_802_11_AP_SEC_KEY_MGMT_SAE )
 
 	GObjectClass *object_class = G_OBJECT_CLASS (ap_class);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (ap_class);
diff --git a/src/devices/wifi/nm-wifi-factory.c b/src/devices/wifi/nm-wifi-factory.c
index 6b8e5fb8..9a89bbe9 100644
--- a/src/devices/wifi/nm-wifi-factory.c
+++ b/src/devices/wifi/nm-wifi-factory.c
@@ -26,6 +26,7 @@
 #include "nm-setting-wireless.h"
 #include "nm-setting-olpc-mesh.h"
 #include "nm-device-wifi.h"
+#include "nm-device-wifi-p2p.h"
 #include "nm-device-olpc-mesh.h"
 #include "nm-device-iwd.h"
 #include "settings/nm-settings-connection.h"
@@ -68,6 +69,18 @@ nm_device_factory_create (GError **error)
 
 /*****************************************************************************/
 
+static void
+p2p_device_created (NMDeviceWifi    *device,
+                    NMDeviceWifiP2P *p2p_device,
+                    NMDeviceFactory *self)
+{
+	nm_log_info (LOGD_PLATFORM | LOGD_WIFI,
+	             "Wi-Fi P2P device controlled by interface %s created",
+	             nm_device_get_iface (NM_DEVICE (device)));
+
+	g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, p2p_device);
+}
+
 static NMDevice *
 create_device (NMDeviceFactory *factory,
                const char *iface,
@@ -75,8 +88,6 @@ create_device (NMDeviceFactory *factory,
                NMConnection *connection,
                gboolean *out_ignore)
 {
-	NMDeviceWifiCapabilities capabilities;
-	NM80211Mode mode;
 	gs_free char *backend = NULL;
 
 	g_return_val_if_fail (iface != NULL, NULL);
@@ -84,23 +95,6 @@ create_device (NMDeviceFactory *factory,
 	g_return_val_if_fail (g_strcmp0 (iface, plink->name) == 0, NULL);
 	g_return_val_if_fail (NM_IN_SET (plink->type, NM_LINK_TYPE_WIFI, NM_LINK_TYPE_OLPC_MESH), NULL);
 
-	if (!nm_platform_wifi_get_capabilities (NM_PLATFORM_GET,
-	                                        plink->ifindex,
-	                                        &capabilities)) {
-		nm_log_warn (LOGD_PLATFORM | LOGD_WIFI, "(%s) failed to initialize Wi-Fi driver for ifindex %d", iface, plink->ifindex);
-		return NULL;
-	}
-
-	/* Ignore monitor-mode and other unhandled interface types.
-	 * FIXME: keep TYPE_MONITOR devices in UNAVAILABLE state and manage
-	 * them if/when they change to a handled type.
-	 */
-	mode = nm_platform_wifi_get_mode (NM_PLATFORM_GET, plink->ifindex);
-	if (mode == NM_802_11_MODE_UNKNOWN) {
-		*out_ignore = TRUE;
-		return NULL;
-	}
-
 	if (plink->type != NM_LINK_TYPE_WIFI)
 		return nm_device_olpc_mesh_new (iface);
 
@@ -116,11 +110,42 @@ create_device (NMDeviceFactory *factory,
 	            iface,
 	            NM_PRINT_FMT_QUOTE_STRING (backend),
 	            WITH_IWD ? " (iwd support enabled)" : "");
-	if (!backend || !strcasecmp (backend, "wpa_supplicant"))
-		return nm_device_wifi_new (iface, capabilities);
+	if (!backend || !strcasecmp (backend, "wpa_supplicant")) {
+		NMDevice *device;
+		NMDeviceWifiCapabilities capabilities;
+		NM80211Mode mode;
+
+		if (!nm_platform_wifi_get_capabilities (NM_PLATFORM_GET,
+		                                        plink->ifindex,
+		                                        &capabilities)) {
+			nm_log_warn (LOGD_PLATFORM | LOGD_WIFI,
+			             "(%s) failed to initialize Wi-Fi driver for ifindex %d",
+			             iface, plink->ifindex);
+			return NULL;
+		}
+
+		/* Ignore monitor-mode and other unhandled interface types.
+		 * FIXME: keep TYPE_MONITOR devices in UNAVAILABLE state and manage
+		 * them if/when they change to a handled type.
+		 */
+		mode = nm_platform_wifi_get_mode (NM_PLATFORM_GET, plink->ifindex);
+		if (mode == NM_802_11_MODE_UNKNOWN) {
+			*out_ignore = TRUE;
+			return NULL;
+		}
+
+		device = nm_device_wifi_new (iface, capabilities);
+
+		g_signal_connect_object (device, NM_DEVICE_WIFI_P2P_DEVICE_CREATED,
+		                         G_CALLBACK (p2p_device_created),
+		                         factory,
+		                         0);
+
+		return device;
+	}
 #if WITH_IWD
 	else if (!strcasecmp (backend, "iwd"))
-		return nm_device_iwd_new (iface, capabilities);
+		return nm_device_iwd_new (iface);
 #endif
 
 	nm_log_warn (LOGD_PLATFORM | LOGD_WIFI, "(%s) config: unknown or unsupported wifi-backend %s", iface, backend);
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.c b/src/devices/wifi/nm-wifi-p2p-peer.c
new file mode 100644
index 00000000..4b524623
--- /dev/null
+++ b/src/devices/wifi/nm-wifi-p2p-peer.c
@@ -0,0 +1,796 @@
+/* NetworkManager -- Wi-Fi P2P Peer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-wifi-p2p-peer.h"
+
+#include <stdlib.h>
+
+#include "nm-setting-wireless.h"
+
+#include "nm-wifi-utils.h"
+#include "NetworkManagerUtils.h"
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "platform/nm-platform.h"
+#include "devices/nm-device.h"
+#include "nm-dbus-manager.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMWifiP2PPeer,
+	PROP_NAME,
+	PROP_MANUFACTURER,
+	PROP_MODEL,
+	PROP_MODEL_NUMBER,
+	PROP_SERIAL,
+	PROP_WFD_IES,
+	PROP_GROUPS,
+	PROP_HW_ADDRESS,
+	PROP_STRENGTH,
+	PROP_LAST_SEEN,
+	PROP_FLAGS,
+);
+
+struct _NMWifiP2PPeerPrivate {
+	char *supplicant_path;   /* D-Bus object path of this Peer from wpa_supplicant */
+
+	/* Scanned or cached values */
+	char *             name;
+	char *             manufacturer;
+	char *             model;
+	char *             model_number;
+	char *             serial;
+
+	char *             address;
+
+	GBytes *           wfd_ies;
+	char **            groups;
+
+	guint8             strength;
+
+	NM80211ApFlags     flags;      /* General flags */
+
+	/* Non-scanned attributes */
+	gint32             last_seen;    /* Timestamp when the Peer was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
+};
+
+typedef struct _NMWifiP2PPeerPrivate NMWifiP2PPeerPrivate;
+
+struct _NMWifiP2PPeerClass {
+	NMDBusObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMWifiP2PPeer, nm_wifi_p2p_peer, NM_TYPE_DBUS_OBJECT)
+
+#define NM_WIFI_P2P_PEER_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMWifiP2PPeer, NM_IS_WIFI_P2P_PEER)
+
+/*****************************************************************************/
+
+const char **
+nm_wifi_p2p_peers_get_paths (const CList *peers_lst_head)
+{
+	NMWifiP2PPeer *peer;
+	const char **list;
+	const char *path;
+	gsize i, n;
+
+	n = c_list_length (peers_lst_head);
+	list = g_new (const char *, n + 1);
+
+	i = 0;
+	if (n > 0) {
+		c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
+			nm_assert (i < n);
+			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (peer));
+			nm_assert (path);
+
+			list[i++] = path;
+		}
+		nm_assert (i <= n);
+	}
+	list[i] = NULL;
+	return list;
+}
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peers_find_first_compatible (const CList *peers_lst_head,
+                                         NMConnection *connection)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (connection, NULL);
+
+	c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
+		if (nm_wifi_p2p_peer_check_compatible (peer, connection))
+			return peer;
+	}
+	return NULL;
+}
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peers_find_by_supplicant_path (const CList *peers_lst_head, const char *path)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (path != NULL, NULL);
+
+	c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
+		if (nm_streq0 (path, nm_wifi_p2p_peer_get_supplicant_path (peer)))
+			return peer;
+	}
+	return NULL;
+}
+
+/*****************************************************************************/
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peer_lookup_for_device (NMDevice *device, const char *exported_path)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
+
+	peer = (NMWifiP2PPeer *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (device)),
+	                                                        exported_path);
+	if (   !peer
+	    || !NM_IS_WIFI_P2P_PEER (peer)
+	    || peer->wifi_device != device)
+		return NULL;
+
+	return peer;
+}
+
+/*****************************************************************************/
+
+const char *
+nm_wifi_p2p_peer_get_supplicant_path (NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->supplicant_path;
+}
+
+const char *
+nm_wifi_p2p_peer_get_name (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->name;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_name (NMWifiP2PPeer *peer, const char *name)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (name, priv->name) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->name, g_free);
+	if (name)
+		priv->name = g_strdup (name);
+
+	_notify (peer, PROP_NAME);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_manufacturer (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->manufacturer;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_manufacturer (NMWifiP2PPeer *peer, const char *manufacturer)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (manufacturer, priv->manufacturer) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->manufacturer, g_free);
+	if (manufacturer)
+		priv->manufacturer = g_strdup (manufacturer);
+
+	_notify (peer, PROP_MANUFACTURER);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_model (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->model;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_model (NMWifiP2PPeer *peer, const char *model)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (model, priv->model) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->model, g_free);
+	if (model)
+		priv->model = g_strdup (model);
+
+	_notify (peer, PROP_MODEL);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_model_number (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->model_number;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_model_number (NMWifiP2PPeer *peer, const char *model_number)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (model_number, priv->model_number) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->model_number, g_free);
+	if (model_number)
+		priv->model_number = g_strdup (model_number);
+
+	_notify (peer, PROP_MODEL_NUMBER);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_serial (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->serial;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_serial (NMWifiP2PPeer *peer, const char *serial)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (serial, priv->serial) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->serial, g_free);
+	if (serial)
+		priv->serial = g_strdup (serial);
+
+	_notify (peer, PROP_SERIAL);
+	return TRUE;
+}
+
+GBytes *
+nm_wifi_p2p_peer_get_wfd_ies (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->wfd_ies;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_wfd_ies (NMWifiP2PPeer *peer, GBytes *wfd_ies)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (nm_gbytes_equal0 (priv->wfd_ies, wfd_ies))
+		return FALSE;
+
+	g_bytes_unref (priv->wfd_ies);
+	priv->wfd_ies = wfd_ies ? g_bytes_ref (wfd_ies) : NULL;
+
+	_notify (peer, PROP_WFD_IES);
+	return TRUE;
+}
+
+const char *const*
+nm_wifi_p2p_peer_get_groups (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return (const char * const*) NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->groups;
+}
+
+static gboolean
+nm_wifi_p2p_peer_set_groups (NMWifiP2PPeer *peer, const char** groups)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+	g_return_val_if_fail (groups != NULL, FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (_nm_utils_strv_equal (priv->groups, (char **) groups))
+		return FALSE;
+
+	g_strfreev (priv->groups);
+	priv->groups = g_strdupv ((char**) groups);
+
+	_notify (peer, PROP_GROUPS);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_address (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->address;
+}
+
+static gboolean
+nm_wifi_p2p_peer_set_address_bin (NMWifiP2PPeer *peer, const guint8 addr[static ETH_ALEN])
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (   priv->address
+	    && nm_utils_hwaddr_matches (addr, ETH_ALEN, priv->address, -1))
+		return FALSE;
+
+	g_free (priv->address);
+	priv->address = nm_utils_hwaddr_ntoa (addr, ETH_ALEN);
+	_notify (peer, PROP_HW_ADDRESS);
+	return TRUE;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_address (NMWifiP2PPeer *peer, const char *addr)
+{
+	guint8 addr_buf[ETH_ALEN];
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (   !addr
+	    || !nm_utils_hwaddr_aton (addr, addr_buf, sizeof (addr_buf)))
+		g_return_val_if_reached (FALSE);
+
+	return nm_wifi_p2p_peer_set_address_bin (peer, addr_buf);
+}
+
+gint8
+nm_wifi_p2p_peer_get_strength (NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), 0);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->strength;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_strength (NMWifiP2PPeer *peer, const gint8 strength)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (priv->strength != strength) {
+		priv->strength = strength;
+		_notify (peer, PROP_STRENGTH);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+NM80211ApFlags
+nm_wifi_p2p_peer_get_flags (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NM_802_11_AP_FLAGS_NONE);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->flags;
+}
+
+static gboolean
+nm_wifi_p2p_peer_set_last_seen (NMWifiP2PPeer *peer, gint32 last_seen)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (priv->last_seen != last_seen) {
+		priv->last_seen = last_seen;
+		_notify (peer, PROP_LAST_SEEN);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+
+/*****************************************************************************/
+
+gboolean
+nm_wifi_p2p_peer_update_from_properties (NMWifiP2PPeer *peer,
+                                         const char *supplicant_path,
+                                         GVariant *properties)
+{
+	NMWifiP2PPeerPrivate *priv;
+	const guint8 *bytes;
+	GVariant *v;
+	gsize len;
+	const char *s;
+	const char **sv;
+	gint32 i32;
+	gboolean changed = FALSE;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+	g_return_val_if_fail (properties, FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	g_object_freeze_notify (G_OBJECT (peer));
+
+	if (g_variant_lookup (properties, "level", "i", &i32))
+		changed |= nm_wifi_p2p_peer_set_strength (peer, nm_wifi_utils_level_to_quality (i32));
+
+	if (g_variant_lookup (properties, "DeviceName", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_name (peer, s);
+
+	if (g_variant_lookup (properties, "Manufacturer", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_manufacturer (peer, s);
+
+	if (g_variant_lookup (properties, "Model", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_model (peer, s);
+
+	if (g_variant_lookup (properties, "ModelNumber", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_model_number (peer, s);
+
+	if (g_variant_lookup (properties, "Serial", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_serial (peer, s);
+
+	v = g_variant_lookup_value (properties, "DeviceAddress", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		if (   len == ETH_ALEN
+		    && memcmp (bytes, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
+		    && memcmp (bytes, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0)
+			changed |= nm_wifi_p2p_peer_set_address_bin (peer, bytes);
+		g_variant_unref (v);
+	}
+
+	/* The IEs property contains the WFD R1 subelements */
+	v = g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		gs_unref_bytes GBytes *b = NULL;
+
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		b = g_bytes_new (bytes, len);
+		changed |= nm_wifi_p2p_peer_set_wfd_ies (peer, b);
+		g_variant_unref (v);
+	}
+
+	v = g_variant_lookup_value (properties, "Groups", G_VARIANT_TYPE_OBJECT_PATH_ARRAY);
+	if (v) {
+		sv = g_variant_get_objv (v, NULL);
+		changed |= nm_wifi_p2p_peer_set_groups (peer, sv);
+		g_free (sv);
+	}
+
+	/*if (max_rate)
+		changed |= nm_wifi_p2p_peer_set_max_bitrate (peer, max_rate / 1000);*/
+
+	if (!priv->supplicant_path) {
+		priv->supplicant_path = g_strdup (supplicant_path);
+		changed = TRUE;
+	}
+
+	changed |= nm_wifi_p2p_peer_set_last_seen (peer, nm_utils_get_monotonic_timestamp_s ());
+
+	g_object_thaw_notify (G_OBJECT (peer));
+
+	return changed;
+}
+
+const char *
+nm_wifi_p2p_peer_to_string (const NMWifiP2PPeer *self,
+                            char *str_buf,
+                            gsize buf_len,
+                            gint32 now_s)
+{
+	const NMWifiP2PPeerPrivate *priv;
+	const char *supplicant_id = "-";
+	const char* export_path;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (self), NULL);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	if (priv->supplicant_path)
+		supplicant_id = strrchr (priv->supplicant_path, '/') ?: supplicant_id;
+
+	export_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
+	if (export_path)
+		export_path = strrchr (export_path, '/') ?: export_path;
+	else
+		export_path = "/";
+
+	g_snprintf (str_buf, buf_len,
+	            "%17s [n:%s, m:%s, mod:%s, mod_num:%s, ser:%s] %3us sup:%s [nm:%s]",
+	            priv->address ?: "(none)",
+	            priv->name,
+	            priv->manufacturer,
+	            priv->model,
+	            priv->model_number,
+	            priv->serial,
+	            priv->last_seen > 0 ? ((now_s > 0 ? now_s : nm_utils_get_monotonic_timestamp_s ()) - priv->last_seen) : -1,
+	            supplicant_id,
+	            export_path);
+
+	return str_buf;
+}
+
+gboolean
+nm_wifi_p2p_peer_check_compatible (NMWifiP2PPeer *self,
+                                   NMConnection *connection)
+{
+	NMWifiP2PPeerPrivate *priv;
+	NMSettingWifiP2P *s_wifi_p2p;
+	const char *hwaddr;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (self), FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+	if (s_wifi_p2p == NULL)
+		return FALSE;
+
+	hwaddr = nm_setting_wifi_p2p_get_peer (s_wifi_p2p);
+	if (   hwaddr
+	    && (   !priv->address
+	        || !nm_utils_hwaddr_matches (hwaddr, -1, priv->address, -1)))
+		return FALSE;
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMWifiP2PPeer *self = NM_WIFI_P2P_PEER (object);
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	switch (prop_id) {
+	case PROP_FLAGS:
+		g_value_set_uint (value, priv->flags);
+		break;
+	case PROP_NAME:
+		g_value_set_string (value, priv->name);
+		break;
+	case PROP_MANUFACTURER:
+		g_value_set_string (value, priv->manufacturer);
+		break;
+	case PROP_MODEL:
+		g_value_set_string (value, priv->model);
+		break;
+	case PROP_MODEL_NUMBER:
+		g_value_set_string (value, priv->model_number);
+		break;
+	case PROP_SERIAL:
+		g_value_set_string (value, priv->serial);
+		break;
+	case PROP_WFD_IES:
+		g_value_take_variant (value, nm_utils_gbytes_to_variant_ay (priv->wfd_ies));
+		break;
+	case PROP_GROUPS:
+		g_value_set_variant (value,
+		                      g_variant_new_strv (   (const char*const*) priv->groups
+		                                          ?: NM_PTRARRAY_EMPTY (const char *),
+		                                          -1));
+		break;
+	case PROP_HW_ADDRESS:
+		g_value_set_string (value, priv->address);
+		break;
+	case PROP_STRENGTH:
+		g_value_set_uchar (value, priv->strength);
+		break;
+	case PROP_LAST_SEEN:
+		g_value_set_int (value,
+		                 priv->last_seen > 0
+		                     ? (int) nm_utils_monotonic_timestamp_as_boottime (priv->last_seen, NM_UTILS_NS_PER_SECOND)
+		                     : -1);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_wifi_p2p_peer_init (NMWifiP2PPeer *self)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerPrivate);
+
+	self->_priv = priv;
+
+	c_list_init (&self->peers_lst);
+
+	priv->flags = NM_802_11_AP_FLAGS_NONE;
+	priv->last_seen = -1;
+}
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peer_new_from_properties (const char *supplicant_path, GVariant *properties)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (supplicant_path != NULL, NULL);
+	g_return_val_if_fail (properties != NULL, NULL);
+
+	peer = (NMWifiP2PPeer *) g_object_new (NM_TYPE_WIFI_P2P_PEER, NULL);
+	nm_wifi_p2p_peer_update_from_properties (peer, supplicant_path, properties);
+
+	/* ignore peers with invalid or missing address */
+	if (!nm_wifi_p2p_peer_get_address (peer)) {
+		g_object_unref (peer);
+		return NULL;
+	}
+
+	return peer;
+}
+
+static void
+finalize (GObject *object)
+{
+	NMWifiP2PPeer *self = NM_WIFI_P2P_PEER (object);
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	nm_assert (!self->wifi_device);
+	nm_assert (c_list_is_empty (&self->peers_lst));
+
+	g_free (priv->supplicant_path);
+	g_free (priv->name);
+	g_free (priv->manufacturer);
+	g_free (priv->model);
+	g_free (priv->model_number);
+	g_free (priv->serial);
+	g_free (priv->address);
+	g_bytes_unref (priv->wfd_ies);
+	g_strfreev (priv->groups);
+
+	G_OBJECT_CLASS (nm_wifi_p2p_peer_parent_class)->finalize (object);
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_p2p_peer = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_WIFI_P2P_PEER,
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Flags",        "u",  NM_WIFI_P2P_PEER_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Name",         "s",  NM_WIFI_P2P_PEER_NAME),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Manufacturer", "s",  NM_WIFI_P2P_PEER_MANUFACTURER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Model",        "s",  NM_WIFI_P2P_PEER_MODEL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("ModelNumber",  "s",  NM_WIFI_P2P_PEER_MODEL_NUMBER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Serial",       "s",  NM_WIFI_P2P_PEER_SERIAL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("WfdIEs",       "ay", NM_WIFI_P2P_PEER_WFD_IES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Groups",       "as", NM_WIFI_P2P_PEER_GROUPS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("HwAddress",    "s",  NM_WIFI_P2P_PEER_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Strength",     "y",  NM_WIFI_P2P_PEER_STRENGTH),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("LastSeen",     "i",  NM_WIFI_P2P_PEER_LAST_SEEN),
+		),
+	),
+	.legacy_property_changed = FALSE,
+};
+
+static void
+nm_wifi_p2p_peer_class_init (NMWifiP2PPeerClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+
+	g_type_class_add_private (object_class, sizeof (NMWifiP2PPeerPrivate));
+
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH_WIFI_P2P_PEER);
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_p2p_peer);
+
+	object_class->get_property = get_property;
+	object_class->finalize     = finalize;
+
+	obj_properties[PROP_FLAGS] =
+	    g_param_spec_uint (NM_WIFI_P2P_PEER_FLAGS, "", "",
+	                       NM_802_11_AP_FLAGS_NONE,
+	                       NM_802_11_AP_FLAGS_PRIVACY,
+	                       NM_802_11_AP_FLAGS_NONE,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_NAME] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_NAME, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MANUFACTURER] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_MANUFACTURER, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MODEL] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_MODEL, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MODEL_NUMBER] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_MODEL_NUMBER, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_SERIAL] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_SERIAL, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_WFD_IES] =
+	    g_param_spec_variant (NM_WIFI_P2P_PEER_WFD_IES, "", "",
+	                          G_VARIANT_TYPE ("ay"),
+	                          NULL,
+	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_GROUPS] =
+	    g_param_spec_variant (NM_WIFI_P2P_PEER_GROUPS, "", "",
+	                          G_VARIANT_TYPE ("as"),
+	                          NULL,
+	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_HW_ADDRESS] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_HW_ADDRESS, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_STRENGTH] =
+	    g_param_spec_uchar (NM_WIFI_P2P_PEER_STRENGTH, "", "",
+	                        0, G_MAXINT8, 0,
+	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_LAST_SEEN] =
+	    g_param_spec_int (NM_WIFI_P2P_PEER_LAST_SEEN, "", "",
+	                      -1, G_MAXINT, -1,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.h b/src/devices/wifi/nm-wifi-p2p-peer.h
new file mode 100644
index 00000000..d6ff7abc
--- /dev/null
+++ b/src/devices/wifi/nm-wifi-p2p-peer.h
@@ -0,0 +1,114 @@
+/* NetworkManager -- Wi-Fi P2P Peer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_WIFI_P2P_PEER_H__
+#define __NM_WIFI_P2P_PEER_H__
+
+#include "nm-dbus-object.h"
+#include "nm-dbus-interface.h"
+#include "nm-connection.h"
+
+#define NM_TYPE_WIFI_P2P_PEER            (nm_wifi_p2p_peer_get_type ())
+#define NM_WIFI_P2P_PEER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeer))
+#define NM_WIFI_P2P_PEER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerClass))
+#define NM_IS_WIFI_P2P_PEER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WIFI_P2P_PEER))
+#define NM_IS_WIFI_P2P_PEER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WIFI_P2P_PEER))
+#define NM_WIFI_P2P_PEER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerClass))
+
+#define NM_WIFI_P2P_PEER_FLAGS                "flags"
+#define NM_WIFI_P2P_PEER_NAME                 "name"
+#define NM_WIFI_P2P_PEER_MANUFACTURER         "manufacturer"
+#define NM_WIFI_P2P_PEER_MODEL                "model"
+#define NM_WIFI_P2P_PEER_MODEL_NUMBER         "model-number"
+#define NM_WIFI_P2P_PEER_SERIAL               "serial"
+#define NM_WIFI_P2P_PEER_WFD_IES              "wfd-ies"
+#define NM_WIFI_P2P_PEER_GROUPS               "groups"
+#define NM_WIFI_P2P_PEER_HW_ADDRESS           "hw-address"
+#define NM_WIFI_P2P_PEER_STRENGTH             "strength"
+#define NM_WIFI_P2P_PEER_LAST_SEEN            "last-seen"
+
+typedef struct {
+	NMDBusObject parent;
+	NMDevice *wifi_device;
+	CList peers_lst;
+	struct _NMWifiP2PPeerPrivate *_priv;
+} NMWifiP2PPeer;
+
+typedef struct _NMWifiP2PPeerClass NMWifiP2PPeerClass;
+
+GType nm_wifi_p2p_peer_get_type (void);
+
+NMWifiP2PPeer *   nm_wifi_p2p_peer_new_from_properties      (const char *supplicant_path,
+                                                             GVariant *properties);
+
+gboolean          nm_wifi_p2p_peer_update_from_properties   (NMWifiP2PPeer *peer,
+                                                             const char *supplicant_path,
+                                                             GVariant *properties);
+
+gboolean          nm_wifi_p2p_peer_check_compatible         (NMWifiP2PPeer *self,
+                                                             NMConnection *connection);
+
+const char *      nm_wifi_p2p_peer_get_supplicant_path      (NMWifiP2PPeer *peer);
+
+const char *      nm_wifi_p2p_peer_get_name                 (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_name                 (NMWifiP2PPeer *peer,
+                                                             const char *name);
+const char *      nm_wifi_p2p_peer_get_manufacturer         (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_manufacturer         (NMWifiP2PPeer *peer,
+                                                             const char *manufacturer);
+const char *      nm_wifi_p2p_peer_get_model                (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_model                (NMWifiP2PPeer *peer,
+                                                             const char *model);
+const char *      nm_wifi_p2p_peer_get_model_number         (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_model_number         (NMWifiP2PPeer *peer,
+                                                             const char *number);
+const char *      nm_wifi_p2p_peer_get_serial               (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_serial               (NMWifiP2PPeer *peer,
+                                                             const char *serial);
+
+GBytes *          nm_wifi_p2p_peer_get_wfd_ies             (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_wfd_ies             (NMWifiP2PPeer *peer,
+                                                            GBytes *bytes);
+
+const char *const*nm_wifi_p2p_peer_get_groups              (const NMWifiP2PPeer *peer);
+
+const char *      nm_wifi_p2p_peer_get_address              (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_address              (NMWifiP2PPeer *peer,
+                                                             const char *addr);
+gint8             nm_wifi_p2p_peer_get_strength             (NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_strength             (NMWifiP2PPeer *peer,
+                                                             gint8 strength);
+NM80211ApFlags    nm_wifi_p2p_peer_get_flags                (const NMWifiP2PPeer *self);
+
+const char       *nm_wifi_p2p_peer_to_string                (const NMWifiP2PPeer *self,
+                                                             char *str_buf,
+                                                             gsize buf_len,
+                                                             gint32 now_s);
+
+const char      **nm_wifi_p2p_peers_get_paths               (const CList *peers_lst_head);
+
+NMWifiP2PPeer    *nm_wifi_p2p_peers_find_first_compatible (const CList *peers_lst_head,
+                                                           NMConnection *connection);
+
+NMWifiP2PPeer    *nm_wifi_p2p_peers_find_by_supplicant_path (const CList *peers_lst_head, const char *path);
+
+NMWifiP2PPeer    *nm_wifi_p2p_peer_lookup_for_device (NMDevice *device, const char *exported_path);
+
+#endif /* __NM_WIFI_P2P_PEER_H__ */
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index 0f7836be..c6e8b3e0 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -22,7 +22,6 @@
 
 #include "nm-wifi-utils.h"
 
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-utils.h"
@@ -739,6 +738,12 @@ nm_wifi_utils_complete_connection (GBytes *ap_ssid,
 		 * setting.  Since there's so much configuration required for it, there's
 		 * no way it can be automatically completed.
 		 */
+	} else if (   (key_mgmt && !strcmp (key_mgmt, "sae"))
+	           || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE)) {
+		g_object_set (s_wsec,
+		              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae",
+		              NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open",
+		              NULL);
 	} else if (   (key_mgmt && !strcmp (key_mgmt, "wpa-psk"))
 	           || (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
 	           || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) {
diff --git a/src/devices/wifi/tests/meson.build b/src/devices/wifi/tests/meson.build
index bb8f7c27..895853c4 100644
--- a/src/devices/wifi/tests/meson.build
+++ b/src/devices/wifi/tests/meson.build
@@ -3,11 +3,12 @@ test_unit = 'test-general'
 exe = executable(
   'wifi-' + test_unit,
   [test_unit + '.c'] + common_sources,
-  dependencies: test_nm_dep
+  dependencies: test_nm_dep,
 )
 
 test(
   'devices/wifi/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
diff --git a/src/devices/wifi/tests/test-general.c b/src/devices/wifi/tests/test-general.c
index f752bbfc..e0899837 100644
--- a/src/devices/wifi/tests/test-general.c
+++ b/src/devices/wifi/tests/test-general.c
@@ -20,10 +20,7 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "devices/wifi/nm-wifi-utils.h"
-
 #include "nm-core-internal.h"
 
 #include "nm-test-utils-core.h"
diff --git a/src/devices/wwan/libnm-wwan.ver b/src/devices/wwan/libnm-wwan.ver
index 70b954c5..ea966afe 100644
--- a/src/devices/wwan/libnm-wwan.ver
+++ b/src/devices/wwan/libnm-wwan.ver
@@ -6,7 +6,6 @@ global:
 	nm_modem_complete_connection;
 	nm_modem_deactivate;
 	nm_modem_deactivate_async;
-	nm_modem_deactivate_async_finish;
 	nm_modem_device_state_changed;
 	nm_modem_get_capabilities;
 	nm_modem_get_configured_mtu;
diff --git a/src/devices/wwan/meson.build b/src/devices/wwan/meson.build
index 5fe6e433..482dc205 100644
--- a/src/devices/wwan/meson.build
+++ b/src/devices/wwan/meson.build
@@ -1,13 +1,13 @@
 sources = files(
   'nm-modem-broadband.c',
   'nm-modem.c',
-  'nm-modem-manager.c'
+  'nm-modem-manager.c',
 )
 
 deps = [
   libsystemd_dep,
   mm_glib_dep,
-  nm_dep
+  nm_dep,
 ]
 
 if enable_ofono
@@ -25,12 +25,12 @@ libnm_wwan = shared_module(
   ],
   link_depends: linker_script,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 libnm_wwan_dep = declare_dependency(
   include_directories: include_directories('.'),
-  link_with: libnm_wwan
+  link_with: libnm_wwan,
 )
 
 core_plugins += libnm_wwan
@@ -43,7 +43,7 @@ test(
 
 sources = files(
   'nm-device-modem.c',
-  'nm-wwan-factory.c'
+  'nm-wwan-factory.c',
 )
 
 libnm_device_plugin_wwan = shared_module(
@@ -55,7 +55,7 @@ libnm_device_plugin_wwan = shared_module(
   link_depends: linker_script_devices,
   install: true,
   install_dir: nm_plugindir,
-  install_rpath: nm_plugindir
+  install_rpath: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_wwan
@@ -63,7 +63,7 @@ core_plugins += libnm_device_plugin_wwan
 run_target(
   'check-local-devices-wwan',
   command: [check_exports, libnm_device_plugin_wwan.full_path(), linker_script_devices],
-  depends: libnm_device_plugin_wwan
+  depends: libnm_device_plugin_wwan,
 )
 
 # FIXME: check_so_symbols replacement
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index 8ff931aa..1e316280 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -22,9 +22,8 @@
 
 #include "nm-device-modem.h"
 
-#include <string.h>
-
 #include "nm-modem.h"
+#include "nm-ip4-config.h"
 #include "devices/nm-device-private.h"
 #include "nm-rfkill-manager.h"
 #include "settings/nm-settings-connection.h"
@@ -61,7 +60,7 @@ struct _NMDeviceModemClass {
 
 G_DEFINE_TYPE (NMDeviceModem, nm_device_modem, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_MODEM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceModem, NM_IS_DEVICE_MODEM)
+#define NM_DEVICE_MODEM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceModem, NM_IS_DEVICE_MODEM, NMDevice)
 
 /*****************************************************************************/
 
@@ -85,9 +84,9 @@ ppp_failed (NMModem *modem,
 	case NM_DEVICE_STATE_SECONDARIES:
 	case NM_DEVICE_STATE_ACTIVATED:
 		if (nm_device_activate_ip4_state_in_conf (device))
-			nm_device_activate_schedule_ip4_config_timeout (device);
+			nm_device_activate_schedule_ip_config_timeout (device, AF_INET);
 		else if (nm_device_activate_ip6_state_in_conf (device))
-			nm_device_activate_schedule_ip6_config_timeout (device);
+			nm_device_activate_schedule_ip_config_timeout (device, AF_INET6);
 		else if (nm_device_activate_ip4_state_done (device)) {
 			nm_device_ip_method_failed (device,
 			                            AF_INET,
@@ -213,8 +212,8 @@ modem_ip4_config_result (NMModem *modem,
 		                            AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 	} else {
-		nm_device_set_wwan_ip4_config (device, config);
-		nm_device_activate_schedule_ip4_config_result (device, NULL);
+		nm_device_set_dev2_ip_config (device, AF_INET, NM_IP_CONFIG_CAST (config));
+		nm_device_activate_schedule_ip_config_result (device, AF_INET, NULL);
 	}
 }
 
@@ -229,7 +228,7 @@ modem_ip6_config_result (NMModem *modem,
 	NMDevice *device = NM_DEVICE (self);
 	NMActStageReturn ret;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-	NMIP6Config *ignored = NULL;
+	gs_unref_object NMIP6Config *ignored = NULL;
 	gboolean got_config = !!config;
 
 	g_return_if_fail (nm_device_activate_ip6_state_in_conf (device) == TRUE);
@@ -244,14 +243,14 @@ modem_ip6_config_result (NMModem *modem,
 	}
 
 	/* Re-enable IPv6 on the interface */
-	nm_device_ipv6_sysctl_set (device, "disable_ipv6", "0");
+	nm_device_sysctl_ip_conf_set (device, AF_INET6, "disable_ipv6", "0");
 
 	if (config)
-		nm_device_set_wwan_ip6_config (device, config);
+		nm_device_set_dev2_ip_config (device, AF_INET6, NM_IP_CONFIG_CAST (config));
 
 	if (do_slaac == FALSE) {
 		if (got_config)
-			nm_device_activate_schedule_ip6_config_result (device);
+			nm_device_activate_schedule_ip_config_result (device, AF_INET6, NULL);
 		else {
 			_LOGW (LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: SLAAC not requested and no addresses");
 			nm_device_ip_method_failed (device,
@@ -262,15 +261,17 @@ modem_ip6_config_result (NMModem *modem,
 	}
 
 	/* Start SLAAC now that we have a link-local address from the modem */
-	ret = NM_DEVICE_CLASS (nm_device_modem_parent_class)->act_stage3_ip6_config_start (device, &ignored, &failure_reason);
-	g_assert (ignored == NULL);
+	ret = NM_DEVICE_CLASS (nm_device_modem_parent_class)->act_stage3_ip_config_start (device, AF_INET6, (gpointer *) &ignored, &failure_reason);
+
+	nm_assert (ignored == NULL);
+
 	switch (ret) {
 	case NM_ACT_STAGE_RETURN_FAILURE:
 		nm_device_ip_method_failed (device, AF_INET6, failure_reason);
 		break;
 	case NM_ACT_STAGE_RETURN_IP_FAIL:
 		/* all done */
-		nm_device_activate_schedule_ip6_config_result (device);
+		nm_device_activate_schedule_ip_config_result (device, AF_INET6, NULL);
 		break;
 	case NM_ACT_STAGE_RETURN_POSTPONE:
 		/* let SLAAC run */
@@ -279,7 +280,7 @@ modem_ip6_config_result (NMModem *modem,
 		/* Should never get here since we've assured that the IPv6 method
 		 * will either be "auto" or "ignored" when starting IPv6 configuration.
 		 */
-		g_assert_not_reached ();
+		nm_assert_not_reached ();
 	}
 }
 
@@ -303,7 +304,7 @@ ip_ifindex_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
 	 * internally, and leaving it enabled could allow the kernel's IPv6
 	 * RA handling code to run before NM is ready.
 	 */
-	nm_device_ipv6_sysctl_set (device, "disable_ipv6", "1");
+	nm_device_sysctl_ip_conf_set (device, AF_INET6, "disable_ipv6", "1");
 }
 
 static void
@@ -474,7 +475,7 @@ check_connection_available (NMDevice *device,
 	state = nm_modem_get_state (priv->modem);
 	if (state <= NM_MODEM_STATE_INITIALIZING) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "modem not initalized");
+		                            "modem not initialized");
 		return FALSE;
 	}
 
@@ -509,44 +510,35 @@ deactivate (NMDevice *device)
 
 /*****************************************************************************/
 
-static gboolean
-deactivate_async_finish (NMDevice *self,
-                         GAsyncResult *res,
-                         GError **error)
-{
-	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
-}
-
 static void
-modem_deactivate_async_ready (NMModem *modem,
-                              GAsyncResult *res,
-                              GSimpleAsyncResult *simple)
+modem_deactivate_async_cb (NMModem *modem,
+                           GError *error,
+                           gpointer user_data)
 {
-	GError *error = NULL;
+	gs_unref_object NMDevice *self = NULL;
+	NMDeviceDeactivateCallback callback;
+	gpointer callback_user_data;
 
-	if (!nm_modem_deactivate_async_finish (modem, res, &error))
-		g_simple_async_result_take_error (simple, error);
-	g_simple_async_result_complete (simple);
-	g_object_unref (simple);
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
+	callback (self, error, callback_user_data);
 }
 
 static void
 deactivate_async (NMDevice *self,
                   GCancellable *cancellable,
-                  GAsyncReadyCallback callback,
+                  NMDeviceDeactivateCallback callback,
                   gpointer user_data)
 {
-	GSimpleAsyncResult *simple;
+	nm_assert (G_IS_CANCELLABLE (cancellable));
+	nm_assert (callback);
 
-	simple = g_simple_async_result_new (G_OBJECT (self),
-	                                    callback,
-	                                    user_data,
-	                                    deactivate_async);
-	nm_modem_deactivate_async (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) self)->modem,
+	nm_modem_deactivate_async (NM_DEVICE_MODEM_GET_PRIVATE (self)->modem,
 	                           self,
 	                           cancellable,
-	                           (GAsyncReadyCallback) modem_deactivate_async_ready,
-	                           simple);
+	                           modem_deactivate_async_cb,
+	                           nm_utils_user_data_pack (g_object_ref (self),
+	                                                    callback,
+	                                                    user_data));
 }
 
 /*****************************************************************************/
@@ -579,14 +571,25 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
-	return nm_modem_stage3_ip4_config_start (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem,
-	                                         device,
-	                                         NM_DEVICE_CLASS (nm_device_modem_parent_class),
-	                                         out_failure_reason);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device);
+
+	nm_assert_addr_family (addr_family);
+
+	if (addr_family == AF_INET) {
+		return nm_modem_stage3_ip4_config_start (priv->modem,
+		                                         device,
+		                                         NM_DEVICE_CLASS (nm_device_modem_parent_class),
+		                                         out_failure_reason);
+	} else {
+		return nm_modem_stage3_ip6_config_start (priv->modem,
+		                                         device,
+		                                         out_failure_reason);
+	}
 }
 
 static void
@@ -595,16 +598,6 @@ ip4_config_pre_commit (NMDevice *device, NMIP4Config *config)
 	nm_modem_ip4_pre_commit (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem, device, config);
 }
 
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	return nm_modem_stage3_ip6_config_start (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem,
-	                                         device,
-	                                         out_failure_reason);
-}
-
 static gboolean
 get_ip_iface_identifier (NMDevice *device, NMUtilsIPv6IfaceId *out_iid)
 {
@@ -831,12 +824,10 @@ nm_device_modem_class_init (NMDeviceModemClass *klass)
 	device_class->check_connection_available = check_connection_available;
 	device_class->complete_connection = complete_connection;
 	device_class->deactivate_async = deactivate_async;
-	device_class->deactivate_async_finish = deactivate_async_finish;
 	device_class->deactivate = deactivate;
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->ip4_config_pre_commit = ip4_config_pre_commit;
 	device_class->get_enabled = get_enabled;
 	device_class->set_enabled = set_enabled;
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 04cb8599..1cb549b0 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -22,7 +22,6 @@
 
 #include "nm-modem-broadband.h"
 
-#include <string.h>
 #include <arpa/inet.h>
 #include <libmm-glib.h>
 
@@ -254,16 +253,21 @@ get_bearer_ip_method (MMBearerIpConfig *config)
 static MMSimpleConnectProperties *
 create_cdma_connect_properties (NMConnection *connection)
 {
-	NMSettingCdma *setting;
 	MMSimpleConnectProperties *properties;
-	const char *str;
 
-	setting = nm_connection_get_setting_cdma (connection);
 	properties = mm_simple_connect_properties_new ();
 
-	str = nm_setting_cdma_get_number (setting);
-	if (str)
-		mm_simple_connect_properties_set_number (properties, str);
+#if !MM_CHECK_VERSION (1, 9, 1)
+	{
+		NMSettingCdma *setting;
+		const char *str;
+
+		setting = nm_connection_get_setting_cdma (connection);
+		str = nm_setting_cdma_get_number (setting);
+		if (str)
+			mm_simple_connect_properties_set_number (properties, str);
+	}
+#endif
 
 	return properties;
 }
@@ -279,11 +283,6 @@ create_gsm_connect_properties (NMConnection *connection)
 	setting = nm_connection_get_setting_gsm (connection);
 	properties = mm_simple_connect_properties_new ();
 
-	/* TODO: not needed */
-	str = nm_setting_gsm_get_number (setting);
-	if (str)
-		mm_simple_connect_properties_set_number (properties, str);
-
 	/* Blank APN ("") means the default subscription APN */
 	str = nm_setting_gsm_get_apn (setting);
 	mm_simple_connect_properties_set_apn (properties, str ?: "");
@@ -693,10 +692,6 @@ complete_connection (NMModem *_self,
 			return FALSE;
 		}
 
-		/* TODO: This is not needed */
-		if (!nm_setting_gsm_get_number (s_gsm))
-			g_object_set (G_OBJECT (s_gsm), NM_SETTING_GSM_NUMBER, "*99#", NULL);
-
 		nm_utils_complete_generic (NM_PLATFORM_GET,
 		                           connection,
 		                           NM_SETTING_GSM_SETTING_NAME,
@@ -884,7 +879,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	/* Fully fail if invalid IP address retrieved */
 	address_string = mm_bearer_ip_config_get_address (self->_priv.ipv4_config);
 	if (   !address_string
-	    || !nm_utils_parse_inaddr_bin (AF_INET, address_string, &address_network)) {
+	    || !nm_utils_parse_inaddr_bin (AF_INET, address_string, NULL, &address_network)) {
 		error = g_error_new (NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                     "(%s) retrieving IP4 configuration failed: invalid address given %s%s%s",
@@ -896,7 +891,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	/* Missing gateway not a hard failure */
 	gw_string = mm_bearer_ip_config_get_gateway (self->_priv.ipv4_config);
 	if (   gw_string
-	    && !nm_utils_parse_inaddr_bin (AF_INET, gw_string, &gw)) {
+	    && !nm_utils_parse_inaddr_bin (AF_INET, gw_string, NULL, &gw)) {
 		error = g_error_new (NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                     "(%s) retrieving IP4 configuration failed: invalid gateway address \"%s\"",
@@ -937,7 +932,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	/* DNS servers */
 	dns = mm_bearer_ip_config_get_dns (self->_priv.ipv4_config);
 	for (i = 0; dns && dns[i]; i++) {
-		if (   nm_utils_parse_inaddr_bin (AF_INET, dns[i], &address_network)
+		if (   nm_utils_parse_inaddr_bin (AF_INET, dns[i], NULL, &address_network)
 		    && address_network > 0) {
 			nm_ip4_config_add_nameserver (config, address_network);
 			_LOGI ("  DNS %s", dns[i]);
@@ -1098,100 +1093,101 @@ stage3_ip6_config_request (NMModem *modem, NMDeviceStateReason *out_failure_reas
 
 typedef struct {
 	NMModemBroadband *self;
-	GSimpleAsyncResult *result;
+	_NMModemDisconnectCallback callback;
+	gpointer callback_user_data;
 	GCancellable *cancellable;
 	gboolean warn;
 } DisconnectContext;
 
 static void
-disconnect_context_complete (DisconnectContext *ctx)
+disconnect_context_complete (DisconnectContext *ctx, GError *error)
 {
-	g_simple_async_result_complete_in_idle (ctx->result);
-	if (ctx->cancellable)
-		g_object_unref (ctx->cancellable);
-	g_object_unref (ctx->result);
+	if (ctx->callback)
+		ctx->callback (NM_MODEM (ctx->self), error, ctx->callback_user_data);
+	nm_g_object_unref (ctx->cancellable);
 	g_object_unref (ctx->self);
 	g_slice_free (DisconnectContext, ctx);
 }
 
-static gboolean
-disconnect_finish (NMModem *self,
-                   GAsyncResult *res,
-                   GError **error)
+static void
+disconnect_context_complete_on_idle (gpointer user_data,
+                                     GCancellable *cancellable)
 {
-	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+	DisconnectContext *ctx = user_data;
+	gs_free_error GError *cancelled_error = NULL;
+
+	g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error);
+	disconnect_context_complete (ctx, cancelled_error);
 }
 
 static void
-simple_disconnect_ready (MMModemSimple *modem_iface,
+simple_disconnect_ready (GObject *source_object,
                          GAsyncResult *res,
-                         DisconnectContext *ctx)
+                         gpointer user_data)
 {
+	MMModemSimple *modem_iface = MM_MODEM_SIMPLE (source_object);
+	DisconnectContext *ctx = user_data;
 	GError *error = NULL;
 
 	if (!mm_modem_simple_disconnect_finish (modem_iface, res, &error)) {
-		if (ctx->warn && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
+		if (   ctx->warn
+		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
 			NMModemBroadband *self = ctx->self;
 
 			_LOGW ("failed to disconnect modem: %s",
 			       error->message);
 		}
-		g_simple_async_result_take_error (ctx->result, error);
 	}
 
-	disconnect_context_complete (ctx);
+	disconnect_context_complete (ctx, error);
 }
 
 static void
 disconnect (NMModem *modem,
             gboolean warn,
             GCancellable *cancellable,
-            GAsyncReadyCallback callback,
+            _NMModemDisconnectCallback callback,
             gpointer user_data)
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
 	DisconnectContext *ctx;
-	GError *error = NULL;
 
 	connect_context_clear (self);
-	ctx = g_slice_new (DisconnectContext);
-	ctx->cancellable = NULL;
+
+	ctx = g_slice_new0 (DisconnectContext);
 	ctx->self = g_object_ref (self);
-	ctx->result = g_simple_async_result_new (G_OBJECT (self),
-	                                         callback,
-	                                         user_data,
-	                                         disconnect);
+	ctx->cancellable = nm_g_object_ref (cancellable);
+	ctx->callback = callback;
+	ctx->callback_user_data = user_data;
+
 	/* Don't bother warning on FAILED since the modem is already gone */
 	ctx->warn = warn;
 
-	/* Already cancelled? */
-	if (g_cancellable_set_error_if_cancelled (cancellable, &error)) {
-		g_simple_async_result_take_error (ctx->result, error);
-		disconnect_context_complete (ctx);
-		return;
-	}
-
-	/* If no simple iface, we're done */
-	if (!ctx->self->_priv.simple_iface) {
-		disconnect_context_complete (ctx);
+	/* Already cancelled or no simple-iface? We are done. */
+	if (   !ctx->self->_priv.simple_iface
+	    || g_cancellable_is_cancelled (cancellable)) {
+		nm_utils_invoke_on_idle (disconnect_context_complete_on_idle,
+		                         ctx,
+		                         cancellable);
 		return;
 	}
 
 	_LOGD ("notifying ModemManager about the modem disconnection");
-	ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-	mm_modem_simple_disconnect (ctx->self->_priv.simple_iface,
+	mm_modem_simple_disconnect (self->_priv.simple_iface,
 	                            NULL, /* bearer path; if NULL given ALL get disconnected */
 	                            cancellable,
-	                            (GAsyncReadyCallback) simple_disconnect_ready,
+	                            simple_disconnect_ready,
 	                            ctx);
 }
 
 /*****************************************************************************/
 
 static void
-deactivate_cleanup (NMModem *_self, NMDevice *device)
+deactivate_cleanup (NMModem *modem,
+                    NMDevice *device,
+                    gboolean stop_ppp_manager)
 {
-	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
+	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
 
 	/* TODO: cancel SimpleConnect() if any */
 
@@ -1202,8 +1198,9 @@ deactivate_cleanup (NMModem *_self, NMDevice *device)
 
 	self->_priv.pin_tries = 0;
 
-	/* Chain up parent's */
-	NM_MODEM_CLASS (nm_modem_broadband_parent_class)->deactivate_cleanup (_self, device);
+	NM_MODEM_CLASS (nm_modem_broadband_parent_class)->deactivate_cleanup (modem,
+	                                                                      device,
+	                                                                      stop_ppp_manager);
 }
 
 /*****************************************************************************/
@@ -1468,7 +1465,6 @@ nm_modem_broadband_class_init (NMModemBroadbandClass *klass)
 	modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start;
 	modem_class->stage3_ip6_config_request = stage3_ip6_config_request;
 	modem_class->disconnect = disconnect;
-	modem_class->disconnect_finish = disconnect_finish;
 	modem_class->deactivate_cleanup = deactivate_cleanup;
 	modem_class->set_mm_enabled = set_mm_enabled;
 	modem_class->get_user_pass = get_user_pass;
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index dfc102f3..fac14d69 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -24,7 +24,6 @@
 
 #include "nm-modem-manager.h"
 
-#include <string.h>
 #include <libmm-glib.h>
 
 #if HAVE_LIBSYSTEMD
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
index ea668590..78d9a9f0 100644
--- a/src/devices/wwan/nm-modem-ofono.c
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -22,8 +22,6 @@
 
 #include "nm-modem-ofono.h"
 
-#include <string.h>
-
 #include "nm-core-internal.h"
 #include "devices/nm-device-private.h"
 #include "nm-modem.h"
@@ -146,30 +144,36 @@ update_modem_state (NMModemOfono *self)
 /* Disconnect */
 typedef struct {
 	NMModemOfono *self;
-	GSimpleAsyncResult *result;
+	_NMModemDisconnectCallback callback;
+	gpointer callback_user_data;
 	GCancellable *cancellable;
 	gboolean warn;
 } DisconnectContext;
 
 static void
-disconnect_context_complete (DisconnectContext *ctx)
+disconnect_context_complete (DisconnectContext *ctx, GError *error)
 {
-	if (ctx->cancellable)
-		g_object_unref (ctx->cancellable);
-	if (ctx->result) {
-		g_simple_async_result_complete_in_idle (ctx->result);
-		g_object_unref (ctx->result);
-	}
+	if (ctx->callback)
+		ctx->callback (NM_MODEM (ctx->self), error, ctx->callback_user_data);
+	nm_g_object_unref (ctx->cancellable);
 	g_object_unref (ctx->self);
 	g_slice_free (DisconnectContext, ctx);
 }
 
-static gboolean
-disconnect_finish (NMModem *self,
-                   GAsyncResult *result,
-                   GError **error)
+static void
+disconnect_context_complete_on_idle (gpointer user_data,
+                                     GCancellable *cancellable)
 {
-	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+	DisconnectContext *ctx = user_data;
+	gs_free_error GError *error = NULL;
+
+	if (!g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+		g_set_error_literal (&error,
+		                     NM_UTILS_ERROR,
+		                     NM_UTILS_ERROR_UNKNOWN,
+		                     ("modem is currently not connected"));
+	}
+	disconnect_context_complete (ctx, error);
 }
 
 static void
@@ -177,16 +181,14 @@ disconnect_done (GObject *source,
                  GAsyncResult *result,
                  gpointer user_data)
 {
-	DisconnectContext *ctx = (DisconnectContext*) user_data;
+	DisconnectContext *ctx = user_data;
 	NMModemOfono *self = ctx->self;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *v = NULL;
 
 	v = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), result, &error);
 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-		if (ctx->result)
-			g_simple_async_result_take_error (ctx->result, g_steal_pointer (&error));
-		disconnect_context_complete (ctx);
+		disconnect_context_complete (ctx, error);
 		return;
 	}
 
@@ -196,21 +198,20 @@ disconnect_done (GObject *source,
 	_LOGD ("modem disconnected");
 
 	update_modem_state (self);
-	disconnect_context_complete (ctx);
+	disconnect_context_complete (ctx, error);
 }
 
 static void
 disconnect (NMModem *modem,
             gboolean warn,
             GCancellable *cancellable,
-            GAsyncReadyCallback callback,
+            _NMModemDisconnectCallback callback,
             gpointer user_data)
 {
 	NMModemOfono *self = NM_MODEM_OFONO (modem);
 	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
 	DisconnectContext *ctx;
 	NMModemState state = nm_modem_get_state (NM_MODEM (self));
-	GError *error = NULL;
 
 	_LOGD ("warn: %s modem_state: %s",
 	       warn ? "TRUE" : "FALSE",
@@ -218,37 +219,19 @@ disconnect (NMModem *modem,
 
 	ctx = g_slice_new0 (DisconnectContext);
 	ctx->self = g_object_ref (self);
+	ctx->cancellable = nm_g_object_ref (cancellable);
 	ctx->warn = warn;
-	if (callback) {
-		ctx->result = g_simple_async_result_new (G_OBJECT (self),
-		                                         callback,
-		                                         user_data,
-		                                         disconnect);
-	}
-
-	if (state != NM_MODEM_STATE_CONNECTED) {
-		if (ctx->result) {
-			g_set_error_literal (&error,
-			                     NM_UTILS_ERROR,
-			                     NM_UTILS_ERROR_UNKNOWN,
-			                     ("modem is currently not connected"));
-			g_simple_async_result_take_error (ctx->result, error);
-		}
-		disconnect_context_complete (ctx);
+	ctx->callback = callback;
+	ctx->callback_user_data = user_data;
+
+	if (   state != NM_MODEM_STATE_CONNECTED
+	    || g_cancellable_is_cancelled (cancellable)) {
+		nm_utils_invoke_on_idle (disconnect_context_complete_on_idle,
+		                         ctx,
+		                         cancellable);
 		return;
 	}
 
-	if (g_cancellable_set_error_if_cancelled (cancellable, &error)) {
-		if (ctx->result)
-			g_simple_async_result_take_error (ctx->result, error);
-		else
-			g_clear_error (&error);
-		disconnect_context_complete (ctx);
-		return;
-	}
-
-	ctx->cancellable = nm_g_object_ref (cancellable);
-
 	nm_modem_set_state (NM_MODEM (self),
 	                    NM_MODEM_STATE_DISCONNECTING,
 	                    nm_modem_state_to_string (NM_MODEM_STATE_DISCONNECTING));
@@ -266,7 +249,9 @@ disconnect (NMModem *modem,
 }
 
 static void
-deactivate_cleanup (NMModem *modem, NMDevice *device)
+deactivate_cleanup (NMModem *modem,
+                    NMDevice *device,
+                    gboolean stop_ppp_manager)
 {
 	NMModemOfono *self = NM_MODEM_OFONO (modem);
 	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
@@ -275,7 +260,9 @@ deactivate_cleanup (NMModem *modem, NMDevice *device)
 
 	g_clear_object (&priv->ip4_config);
 
-	NM_MODEM_CLASS (nm_modem_ofono_parent_class)->deactivate_cleanup (modem, device);
+	NM_MODEM_CLASS (nm_modem_ofono_parent_class)->deactivate_cleanup (modem,
+	                                                                  device,
+	                                                                  stop_ppp_manager);
 }
 
 static gboolean
@@ -664,7 +651,7 @@ handle_connman_iface (NMModemOfono *self, gboolean found)
 		                          OFONO_DBUS_INTERFACE_CONNECTION_MANAGER,
 		                          priv->connman_proxy_cancellable,
 		                          _connman_proxy_new_cb,
-		                          NULL);
+		                          self);
 	}
 }
 
@@ -883,7 +870,7 @@ context_property_changed (GDBusProxy *proxy,
 		goto out;
 	}
 	if (   !s
-	    || !nm_utils_parse_inaddr_bin (AF_INET, s, &address_network)) {
+	    || !nm_utils_parse_inaddr_bin (AF_INET, s, NULL, &address_network)) {
 		_LOGW ("can't convert 'Address' %s to addr", s ?: "");
 		goto out;
 	}
@@ -897,7 +884,7 @@ context_property_changed (GDBusProxy *proxy,
 		goto out;
 	}
 	if (   !s
-	    || !nm_utils_parse_inaddr_bin (AF_INET, s, &address_network)) {
+	    || !nm_utils_parse_inaddr_bin (AF_INET, s, NULL, &address_network)) {
 		_LOGW ("invalid 'Netmask': %s", s ?: "");
 		goto out;
 	}
@@ -911,7 +898,7 @@ context_property_changed (GDBusProxy *proxy,
 		_LOGW ("Settings 'Gateway' missing");
 		goto out;
 	}
-	if (!nm_utils_parse_inaddr_bin (AF_INET, s, &gateway_network)) {
+	if (!nm_utils_parse_inaddr_bin (AF_INET, s, NULL, &gateway_network)) {
 		_LOGW ("invalid 'Gateway': %s", s);
 		goto out;
 	}
@@ -938,7 +925,7 @@ context_property_changed (GDBusProxy *proxy,
 	}
 	if (array) {
 		for (iter = array; *iter; iter++) {
-			if (   nm_utils_parse_inaddr_bin (AF_INET, *iter, &address_network)
+			if (   nm_utils_parse_inaddr_bin (AF_INET, *iter, NULL, &address_network)
 			    && address_network) {
 				_LOGI ("DNS: %s", *iter);
 				nm_ip4_config_add_nameserver (priv->ip4_config, address_network);
@@ -958,7 +945,7 @@ context_property_changed (GDBusProxy *proxy,
 	if (g_variant_lookup (v_dict, "MessageProxy", "&s", &s)) {
 		_LOGI ("MessageProxy: %s", s);
 		if (   s
-		    && nm_utils_parse_inaddr_bin (AF_INET, s, &address_network)) {
+		    && nm_utils_parse_inaddr_bin (AF_INET, s, NULL, &address_network)) {
 			nm_modem_get_route_parameters (NM_MODEM (self),
 			                               &ip4_route_table,
 			                               &ip4_route_metric,
@@ -1319,7 +1306,6 @@ nm_modem_ofono_class_init (NMModemOfonoClass *klass)
 
 	modem_class->get_capabilities = get_capabilities;
 	modem_class->disconnect = disconnect;
-	modem_class->disconnect_finish = disconnect_finish;
 	modem_class->deactivate_cleanup = deactivate_cleanup;
 	modem_class->check_connection_compatible_with_modem = check_connection_compatible_with_modem;
 
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index 59b081e9..2217f2a2 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -24,7 +24,6 @@
 #include "nm-modem.h"
 
 #include <fcntl.h>
-#include <string.h>
 #include <termios.h>
 #include <linux/rtnetlink.h>
 
@@ -706,13 +705,13 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 
 	nm_modem_set_route_parameters_from_device (self, device);
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	method = nm_utils_get_ip_config_method (connection, AF_INET);
 
 	/* Only Disabled and Auto methods make sense for WWAN */
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0)
+	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
 		return NM_ACT_STAGE_RETURN_SUCCESS;
 
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) != 0) {
+	if (!nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
 		_LOGE ("unhandled WWAN IPv4 method '%s'; will fail", method);
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED);
 		return NM_ACT_STAGE_RETURN_FAILURE;
@@ -729,7 +728,7 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 		break;
 	case NM_MODEM_IP_METHOD_AUTO:
 		_LOGD ("MODEM_IP_METHOD_AUTO");
-		ret = device_class->act_stage3_ip4_config_start (device, NULL, out_failure_reason);
+		ret = device_class->act_stage3_ip_config_start (device, AF_INET, NULL, out_failure_reason);
 		break;
 	default:
 		_LOGI ("IPv4 configuration disabled");
@@ -823,13 +822,13 @@ nm_modem_stage3_ip6_config_start (NMModem *self,
 
 	nm_modem_set_route_parameters_from_device (self, device);
 
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	method = nm_utils_get_ip_config_method (connection, AF_INET6);
 
 	/* Only Ignore and Auto methods make sense for WWAN */
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0)
+	if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
 		return NM_ACT_STAGE_RETURN_IP_DONE;
 
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) != 0) {
+	if (!nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
 		_LOGW ("unhandled WWAN IPv6 method '%s'; will fail",
 		       method);
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
@@ -948,7 +947,7 @@ nm_modem_get_secrets (NMModem *self,
 	                                               FALSE,
 	                                               setting_name,
 	                                               flags,
-	                                               hint,
+	                                               NM_MAKE_STRV (hint),
 	                                               modem_secrets_cb,
 	                                               self);
 	g_return_if_fail (priv->secrets_id);
@@ -986,8 +985,7 @@ nm_modem_act_stage1_prepare (NMModem *self,
 
 	setting_name = nm_connection_need_secrets (connection, &hints);
 	if (!setting_name) {
-		/* Ready to connect */
-		g_assert (!hints);
+		nm_assert (!hints);
 		return NM_MODEM_GET_CLASS (self)->act_stage1_prepare (self, connection, out_failure_reason);
 	}
 
@@ -995,11 +993,14 @@ nm_modem_act_stage1_prepare (NMModem *self,
 	if (priv->secrets_tries++)
 		flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
 
+	if (hints)
+		g_ptr_array_add (hints, NULL);
+
 	priv->secrets_id = nm_act_request_get_secrets (req,
 	                                               FALSE,
 	                                               setting_name,
 	                                               flags,
-	                                               hints ? g_ptr_array_index (hints, 0) : NULL,
+	                                               hints ? (const char *const*) hints->pdata : NULL,
 	                                               modem_secrets_cb,
 	                                               self);
 	g_return_val_if_fail (priv->secrets_id, NM_ACT_STAGE_RETURN_FAILURE);
@@ -1105,7 +1106,9 @@ nm_modem_complete_connection (NMModem *self,
 /*****************************************************************************/
 
 static void
-deactivate_cleanup (NMModem *self, NMDevice *device)
+deactivate_cleanup (NMModem *self,
+                    NMDevice *device,
+                    gboolean stop_ppp_manager)
 {
 	NMModemPrivate *priv;
 	int ifindex;
@@ -1126,7 +1129,8 @@ deactivate_cleanup (NMModem *self, NMDevice *device)
 
 	if (priv->ppp_manager) {
 		g_signal_handlers_disconnect_by_data (priv->ppp_manager, self);
-		nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL);
+		if (stop_ppp_manager)
+			nm_ppp_manager_stop (priv->ppp_manager, NULL, NULL, NULL);
 		g_clear_object (&priv->ppp_manager);
 	}
 
@@ -1157,189 +1161,109 @@ deactivate_cleanup (NMModem *self, NMDevice *device)
 
 /*****************************************************************************/
 
-typedef enum {
-	DEACTIVATE_CONTEXT_STEP_FIRST,
-	DEACTIVATE_CONTEXT_STEP_CLEANUP,
-	DEACTIVATE_CONTEXT_STEP_PPP_MANAGER_STOP,
-	DEACTIVATE_CONTEXT_STEP_MM_DISCONNECT,
-	DEACTIVATE_CONTEXT_STEP_LAST
-} DeactivateContextStep;
-
 typedef struct {
 	NMModem *self;
 	NMDevice *device;
 	GCancellable *cancellable;
-	GSimpleAsyncResult *result;
-	DeactivateContextStep step;
-	NMPPPManager *ppp_manager;
-	NMPPPManagerStopHandle *ppp_stop_handle;
-	gulong ppp_stop_cancellable_id;
+	NMModemDeactivateCallback callback;
+	gpointer callback_user_data;
 } DeactivateContext;
 
 static void
-deactivate_context_complete (DeactivateContext *ctx)
+deactivate_context_complete (DeactivateContext *ctx, GError *error)
 {
-	if (ctx->ppp_stop_handle)
-		nm_ppp_manager_stop_cancel (ctx->ppp_stop_handle);
+	NMModem *self = ctx->self;
 
-	nm_assert (!ctx->ppp_stop_handle);
-	nm_assert (ctx->ppp_stop_cancellable_id == 0);
+	_LOGD ("modem deactivation finished %s%s%s",
+	       NM_PRINT_FMT_QUOTED (error, "with failure: ", error->message, "", "successfully"));
 
-	if (ctx->ppp_manager)
-		g_object_unref (ctx->ppp_manager);
-	if (ctx->cancellable)
-		g_object_unref (ctx->cancellable);
-	g_simple_async_result_complete_in_idle (ctx->result);
-	g_object_unref (ctx->result);
+	if (ctx->callback)
+		ctx->callback (ctx->self, error, ctx->callback_user_data);
+	nm_g_object_unref (ctx->cancellable);
 	g_object_unref (ctx->device);
 	g_object_unref (ctx->self);
 	g_slice_free (DeactivateContext, ctx);
 }
 
-gboolean
-nm_modem_deactivate_async_finish (NMModem *self,
-                                  GAsyncResult *res,
-                                  GError **error)
-{
-	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
-}
-
-static void deactivate_step (DeactivateContext *ctx);
-
 static void
-disconnect_ready (NMModem *self,
-                  GAsyncResult *res,
-                  DeactivateContext *ctx)
+_deactivate_call_disconnect_cb (NMModem *self,
+                                GError *error,
+                                gpointer user_data)
 {
-	GError *error = NULL;
-
-	if (!NM_MODEM_GET_CLASS (self)->disconnect_finish (self, res, &error)) {
-		g_simple_async_result_take_error (ctx->result, error);
-		deactivate_context_complete (ctx);
-		return;
-	}
-
-	/* Go on */
-	ctx->step++;
-	deactivate_step (ctx);
+	deactivate_context_complete (user_data, error);
 }
 
 static void
-ppp_manager_stop_ready (NMPPPManager *ppp_manager,
-                        NMPPPManagerStopHandle *handle,
-                        gboolean was_cancelled,
-                        gpointer user_data)
+_deactivate_call_disconnect (DeactivateContext *ctx)
 {
-	DeactivateContext *ctx = user_data;
-
-	nm_assert (ctx->ppp_stop_handle == handle);
-	ctx->ppp_stop_handle = NULL;
-
-	if (ctx->ppp_stop_cancellable_id) {
-		g_cancellable_disconnect (ctx->cancellable,
-		                          nm_steal_int (&ctx->ppp_stop_cancellable_id));
-	}
-
-	if (was_cancelled)
-		return;
-
-	ctx->step++;
-	deactivate_step (ctx);
+	NM_MODEM_GET_CLASS (ctx->self)->disconnect (ctx->self,
+	                                            FALSE,
+	                                            ctx->cancellable,
+	                                            _deactivate_call_disconnect_cb,
+	                                            ctx);
 }
 
 static void
-ppp_manager_stop_cancelled (GCancellable *cancellable,
-                            gpointer user_data)
+_deactivate_ppp_manager_stop_cb (NMPPPManager *ppp_manager,
+                                 NMPPPManagerStopHandle *handle,
+                                 gboolean was_cancelled,
+                                 gpointer user_data)
 {
 	DeactivateContext *ctx = user_data;
 
-	nm_ppp_manager_stop_cancel (ctx->ppp_stop_handle);
-}
+	g_object_unref (ppp_manager);
 
-static void
-deactivate_step (DeactivateContext *ctx)
-{
-	NMModem *self = ctx->self;
-	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
-	GError *error = NULL;
-
-	/* Check cancellable in each step */
-	if (g_cancellable_set_error_if_cancelled (ctx->cancellable, &error)) {
-		g_simple_async_result_take_error (ctx->result, error);
-		deactivate_context_complete (ctx);
-		return;
-	}
-
-	switch (ctx->step) {
-	case DEACTIVATE_CONTEXT_STEP_FIRST:
-		ctx->step++;
-		/* fall through */
-	case DEACTIVATE_CONTEXT_STEP_CLEANUP:
-		/* Make sure we keep a ref to the PPP manager if there is one */
-		if (priv->ppp_manager)
-			ctx->ppp_manager = g_object_ref (priv->ppp_manager);
-		/* Run cleanup */
-		NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, ctx->device);
-		ctx->step++;
-		/* fall through */
-	case DEACTIVATE_CONTEXT_STEP_PPP_MANAGER_STOP:
-		/* If we have a PPP manager, stop it */
-		if (ctx->ppp_manager) {
-			nm_assert (!ctx->ppp_stop_handle);
-			if (ctx->cancellable) {
-				ctx->ppp_stop_cancellable_id = g_cancellable_connect (ctx->cancellable,
-				                                                      G_CALLBACK (ppp_manager_stop_cancelled),
-				                                                      ctx,
-				                                                      NULL);
-			}
-			ctx->ppp_stop_handle = nm_ppp_manager_stop (ctx->ppp_manager,
-			                                            ppp_manager_stop_ready,
-			                                            ctx);
-			return;
-		}
-		ctx->step++;
-		/* fall through */
-	case DEACTIVATE_CONTEXT_STEP_MM_DISCONNECT:
-		/* Disconnect asynchronously */
-		NM_MODEM_GET_CLASS (self)->disconnect (self,
-		                                       FALSE,
-		                                       ctx->cancellable,
-		                                       (GAsyncReadyCallback) disconnect_ready,
-		                                       ctx);
-		return;
+	if (was_cancelled) {
+		gs_free_error GError *error = NULL;
 
-	case DEACTIVATE_CONTEXT_STEP_LAST:
-		_LOGD ("modem deactivation finished");
-		deactivate_context_complete (ctx);
+		if (!g_cancellable_set_error_if_cancelled (ctx->cancellable, &error))
+			nm_assert_not_reached ();
+		deactivate_context_complete (ctx, error);
 		return;
 	}
 
-	g_assert_not_reached ();
+	nm_assert (!g_cancellable_is_cancelled (ctx->cancellable));
+	_deactivate_call_disconnect (ctx);
 }
 
 void
 nm_modem_deactivate_async (NMModem *self,
                            NMDevice *device,
                            GCancellable *cancellable,
-                           GAsyncReadyCallback callback,
+                           NMModemDeactivateCallback callback,
                            gpointer user_data)
 {
+	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
 	DeactivateContext *ctx;
+	NMPPPManager *ppp_manager;
+
+	g_return_if_fail (NM_IS_MODEM (self));
+	g_return_if_fail (NM_IS_DEVICE (device));
+	g_return_if_fail (G_IS_CANCELLABLE (cancellable));
 
-	ctx = g_slice_new0 (DeactivateContext);
+	ctx = g_slice_new (DeactivateContext);
 	ctx->self = g_object_ref (self);
 	ctx->device = g_object_ref (device);
-	ctx->result = g_simple_async_result_new (G_OBJECT (self),
-	                                         callback,
-	                                         user_data,
-	                                         nm_modem_deactivate_async);
-	/* FIXME(shutdown): we always require a cancellable, otherwise we cannot
-	 * do a coordinated shutdown. */
-	ctx->cancellable = nm_g_object_ref (cancellable);
+	ctx->cancellable = g_object_ref (cancellable);
+	ctx->callback = callback;
+	ctx->callback_user_data = user_data;
+
+	ppp_manager = nm_g_object_ref (priv->ppp_manager);
+
+	NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, ctx->device, FALSE);
+
+	if (ppp_manager) {
+		/* If we have a PPP manager, stop it.
+		 *
+		 * Pass on the reference in @ppp_manager. */
+		nm_ppp_manager_stop (ppp_manager,
+		                     ctx->cancellable,
+		                     _deactivate_ppp_manager_stop_cb,
+		                     ctx);
+		return;
+	}
 
-	/* Start */
-	ctx->step = DEACTIVATE_CONTEXT_STEP_FIRST;
-	deactivate_step (ctx);
+	_deactivate_call_disconnect (ctx);
 }
 
 /*****************************************************************************/
@@ -1348,7 +1272,7 @@ void
 nm_modem_deactivate (NMModem *self, NMDevice *device)
 {
 	/* First cleanup */
-	NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, device);
+	NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, device, TRUE);
 	/* Then disconnect without waiting */
 	NM_MODEM_GET_CLASS (self)->disconnect (self, FALSE, NULL, NULL, NULL);
 }
@@ -1387,7 +1311,7 @@ nm_modem_device_state_changed (NMModem *self,
 			if (new_state == NM_DEVICE_STATE_FAILED || new_state == NM_DEVICE_STATE_DISCONNECTED)
 				warn = FALSE;
 			/* First cleanup */
-			NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, NULL);
+			NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, NULL, TRUE);
 			NM_MODEM_GET_CLASS (self)->disconnect (self, warn, NULL, NULL, NULL);
 		}
 		break;
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index c73745ce..f7b6bfe9 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -109,6 +109,10 @@ struct _NMModem {
 
 typedef struct _NMModem NMModem;
 
+typedef void (*_NMModemDisconnectCallback) (NMModem *modem,
+                                            GError *error,
+                                            gpointer user_data);
+
 typedef struct {
 	GObjectClass parent;
 
@@ -149,13 +153,12 @@ typedef struct {
 	void     (*disconnect)                     (NMModem *self,
 	                                            gboolean warn,
 	                                            GCancellable *cancellable,
-	                                            GAsyncReadyCallback callback,
+	                                            _NMModemDisconnectCallback callback,
 	                                            gpointer user_data);
-	gboolean (*disconnect_finish)              (NMModem *self,
-	                                            GAsyncResult *res,
-	                                            GError **error);
 
-	void     (*deactivate_cleanup)             (NMModem *self, NMDevice *device);
+	void     (*deactivate_cleanup)             (NMModem *self,
+	                                            NMDevice *device,
+	                                            gboolean stop_ppp_manager);
 
 	gboolean (*owns_port)                      (NMModem *self, const char *iface);
 } NMModemClass;
@@ -236,14 +239,15 @@ void nm_modem_get_secrets (NMModem *modem,
 
 void nm_modem_deactivate (NMModem *modem, NMDevice *device);
 
+typedef void (*NMModemDeactivateCallback) (NMModem *self,
+                                           GError *error,
+                                           gpointer user_data);
+
 void     nm_modem_deactivate_async        (NMModem *self,
                                            NMDevice *device,
                                            GCancellable *cancellable,
-                                           GAsyncReadyCallback callback,
+                                           NMModemDeactivateCallback callback,
                                            gpointer user_data);
-gboolean nm_modem_deactivate_async_finish (NMModem *self,
-                                           GAsyncResult *res,
-                                           GError **error);
 
 void nm_modem_device_state_changed (NMModem *modem,
                                     NMDeviceState new_state,
diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/devices/wwan/nm-wwan-factory.c
index a561b58a..c9ee27ff 100644
--- a/src/devices/wwan/nm-wwan-factory.c
+++ b/src/devices/wwan/nm-wwan-factory.c
@@ -20,7 +20,6 @@
 
 #include "nm-default.h"
 
-#include <string.h>
 #include <gmodule.h>
 
 #include "devices/nm-device-factory.h"