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.build15
-rw-r--r--src/devices/adsl/nm-atm-manager.c18
-rw-r--r--src/devices/adsl/nm-device-adsl.c18
-rw-r--r--src/devices/adsl/nm-device-adsl.h18
-rw-r--r--src/devices/bluetooth/meson.build36
-rw-r--r--src/devices/bluetooth/nm-bluez-common.h25
-rw-r--r--src/devices/bluetooth/nm-bluez-device.c1320
-rw-r--r--src/devices/bluetooth/nm-bluez-device.h84
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c2881
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.h39
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.c456
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.h57
-rw-r--r--src/devices/bluetooth/nm-bluez4-manager.c355
-rw-r--r--src/devices/bluetooth/nm-bluez4-manager.h40
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.c928
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.h59
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c596
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.h40
-rw-r--r--src/devices/bluetooth/nm-bt-error.c18
-rw-r--r--src/devices/bluetooth/nm-bt-error.h18
-rw-r--r--src/devices/bluetooth/nm-device-bt.c898
-rw-r--r--src/devices/bluetooth/nm-device-bt.h52
-rw-r--r--src/devices/bluetooth/tests/nm-bt-test.c220
-rw-r--r--src/devices/nm-acd-manager.c16
-rw-r--r--src/devices/nm-acd-manager.h16
-rw-r--r--src/devices/nm-device-6lowpan.c36
-rw-r--r--src/devices/nm-device-6lowpan.h20
-rw-r--r--src/devices/nm-device-bond.c64
-rw-r--r--src/devices/nm-device-bond.h20
-rw-r--r--src/devices/nm-device-bridge.c152
-rw-r--r--src/devices/nm-device-bridge.h23
-rw-r--r--src/devices/nm-device-dummy.c31
-rw-r--r--src/devices/nm-device-dummy.h20
-rw-r--r--src/devices/nm-device-ethernet-utils.c18
-rw-r--r--src/devices/nm-device-ethernet-utils.h18
-rw-r--r--src/devices/nm-device-ethernet.c80
-rw-r--r--src/devices/nm-device-ethernet.h18
-rw-r--r--src/devices/nm-device-factory.c40
-rw-r--r--src/devices/nm-device-factory.h53
-rw-r--r--src/devices/nm-device-generic.c20
-rw-r--r--src/devices/nm-device-generic.h20
-rw-r--r--src/devices/nm-device-infiniband.c54
-rw-r--r--src/devices/nm-device-infiniband.h20
-rw-r--r--src/devices/nm-device-ip-tunnel.c37
-rw-r--r--src/devices/nm-device-ip-tunnel.h20
-rw-r--r--src/devices/nm-device-logging.h18
-rw-r--r--src/devices/nm-device-macsec.c26
-rw-r--r--src/devices/nm-device-macsec.h20
-rw-r--r--src/devices/nm-device-macvlan.c37
-rw-r--r--src/devices/nm-device-macvlan.h20
-rw-r--r--src/devices/nm-device-ppp.c14
-rw-r--r--src/devices/nm-device-ppp.h12
-rw-r--r--src/devices/nm-device-private.h24
-rw-r--r--src/devices/nm-device-tun.c41
-rw-r--r--src/devices/nm-device-tun.h20
-rw-r--r--src/devices/nm-device-veth.c20
-rw-r--r--src/devices/nm-device-veth.h20
-rw-r--r--src/devices/nm-device-vlan.c34
-rw-r--r--src/devices/nm-device-vlan.h20
-rw-r--r--src/devices/nm-device-vxlan.c37
-rw-r--r--src/devices/nm-device-vxlan.h20
-rw-r--r--src/devices/nm-device-wireguard.c64
-rw-r--r--src/devices/nm-device-wireguard.h18
-rw-r--r--src/devices/nm-device-wpan.c38
-rw-r--r--src/devices/nm-device-wpan.h20
-rw-r--r--src/devices/nm-device.c1056
-rw-r--r--src/devices/nm-device.h60
-rw-r--r--src/devices/nm-lldp-listener.c18
-rw-r--r--src/devices/nm-lldp-listener.h18
-rw-r--r--src/devices/ovs/meson.build10
-rw-r--r--src/devices/ovs/nm-device-ovs-bridge.c20
-rw-r--r--src/devices/ovs/nm-device-ovs-bridge.h20
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c34
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.h20
-rw-r--r--src/devices/ovs/nm-device-ovs-port.c20
-rw-r--r--src/devices/ovs/nm-device-ovs-port.h20
-rw-r--r--src/devices/ovs/nm-ovs-factory.c24
-rw-r--r--src/devices/ovs/nm-ovsdb.c18
-rw-r--r--src/devices/ovs/nm-ovsdb.h20
-rw-r--r--src/devices/team/meson.build10
-rw-r--r--src/devices/team/nm-device-team.c223
-rw-r--r--src/devices/team/nm-device-team.h18
-rw-r--r--src/devices/team/nm-team-factory.c20
-rw-r--r--src/devices/tests/meson.build4
-rw-r--r--src/devices/tests/test-acd.c18
-rw-r--r--src/devices/tests/test-lldp.c18
-rw-r--r--src/devices/wifi/meson.build30
-rw-r--r--src/devices/wifi/nm-device-iwd.c44
-rw-r--r--src/devices/wifi/nm-device-iwd.h18
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c38
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.h25
-rw-r--r--src/devices/wifi/nm-device-wifi-p2p.c96
-rw-r--r--src/devices/wifi/nm-device-wifi-p2p.h21
-rw-r--r--src/devices/wifi/nm-device-wifi.c218
-rw-r--r--src/devices/wifi/nm-device-wifi.h18
-rw-r--r--src/devices/wifi/nm-iwd-manager.c20
-rw-r--r--src/devices/wifi/nm-iwd-manager.h18
-rw-r--r--src/devices/wifi/nm-wifi-ap.c123
-rw-r--r--src/devices/wifi/nm-wifi-ap.h19
-rw-r--r--src/devices/wifi/nm-wifi-common.c18
-rw-r--r--src/devices/wifi/nm-wifi-common.h18
-rw-r--r--src/devices/wifi/nm-wifi-factory.c18
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.c19
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.h19
-rw-r--r--src/devices/wifi/nm-wifi-utils.c254
-rw-r--r--src/devices/wifi/nm-wifi-utils.h18
-rw-r--r--src/devices/wifi/tests/meson.build14
-rw-r--r--src/devices/wifi/tests/test-devices-wifi.c16
-rw-r--r--src/devices/wwan/libnm-wwan.ver6
-rw-r--r--src/devices/wwan/meson.build42
-rw-r--r--src/devices/wwan/nm-device-modem.c104
-rw-r--r--src/devices/wwan/nm-device-modem.h18
-rw-r--r--src/devices/wwan/nm-modem-broadband.c193
-rw-r--r--src/devices/wwan/nm-modem-broadband.h18
-rw-r--r--src/devices/wwan/nm-modem-manager.c32
-rw-r--r--src/devices/wwan/nm-modem-manager.h21
-rw-r--r--src/devices/wwan/nm-modem-ofono.c26
-rw-r--r--src/devices/wwan/nm-modem-ofono.h18
-rw-r--r--src/devices/wwan/nm-modem.c92
-rw-r--r--src/devices/wwan/nm-modem.h32
-rw-r--r--src/devices/wwan/nm-service-providers.c459
-rw-r--r--src/devices/wwan/nm-service-providers.h24
-rw-r--r--src/devices/wwan/nm-wwan-factory.c32
-rw-r--r--src/devices/wwan/tests/test-service-providers.c124
-rw-r--r--src/devices/wwan/tests/test-service-providers.xml73
125 files changed, 6811 insertions, 6764 deletions
diff --git a/src/devices/adsl/meson.build b/src/devices/adsl/meson.build
index f92e809c..a5d1c4b4 100644
--- a/src/devices/adsl/meson.build
+++ b/src/devices/adsl/meson.build
@@ -3,15 +3,11 @@ sources = files(
   'nm-device-adsl.c',
 )
 
-deps = [
-  libudev_dep,
-  nm_dep,
-]
-
 libnm_device_plugin_adsl = shared_module(
   'nm-device-plugin-adsl',
   sources: sources,
-  dependencies: deps,
+  dependencies: daemon_nm_default_dep,
+  c_args: daemon_c_flags,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
@@ -25,10 +21,3 @@ test(
   check_exports,
   args: [libnm_device_plugin_adsl.full_path(), linker_script_devices],
 )
-
-# FIXME: check_so_symbols replacement
-'''
-check-local-devices-adsl: src/devices/adsl/libnm-device-plugin-adsl.la
-  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/adsl/.libs/libnm-device-plugin-adsl.so "$(srcdir)/linker-script-devices.ver"
-  $(call check_so_symbols,$(builddir)/src/devices/adsl/.libs/libnm-device-plugin-adsl.so)
-'''
diff --git a/src/devices/adsl/nm-atm-manager.c b/src/devices/adsl/nm-atm-manager.c
index bc0bf5c6..487011ff 100644
--- a/src/devices/adsl/nm-atm-manager.c
+++ b/src/devices/adsl/nm-atm-manager.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2009 - 2013 Red Hat, Inc.
  */
 
diff --git a/src/devices/adsl/nm-device-adsl.c b/src/devices/adsl/nm-device-adsl.c
index 34efdb01..7c2b3e20 100644
--- a/src/devices/adsl/nm-device-adsl.c
+++ b/src/devices/adsl/nm-device-adsl.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Pantelis Koukousoulas <pktoss@gmail.com>
  */
 
diff --git a/src/devices/adsl/nm-device-adsl.h b/src/devices/adsl/nm-device-adsl.h
index 2d3a9fc7..84176fae 100644
--- a/src/devices/adsl/nm-device-adsl.h
+++ b/src/devices/adsl/nm-device-adsl.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Author: Pantelis Koukousoulas <pktoss@gmail.com>
  * Copyright (C) 2009 - 2011 Red Hat Inc.
  */
diff --git a/src/devices/bluetooth/meson.build b/src/devices/bluetooth/meson.build
index b2f67ceb..f0507c23 100644
--- a/src/devices/bluetooth/meson.build
+++ b/src/devices/bluetooth/meson.build
@@ -1,16 +1,12 @@
 sources = files(
-  'nm-bluez-device.c',
   'nm-bluez-manager.c',
-  'nm-bluez4-adapter.c',
-  'nm-bluez4-manager.c',
-  'nm-bluez5-manager.c',
   'nm-bt-error.c',
   'nm-device-bt.c',
 )
 
 deps = [
+  daemon_nm_default_dep,
   libnm_wwan_dep,
-  nm_dep,
 ]
 
 if enable_bluez5_dun
@@ -19,9 +15,21 @@ if enable_bluez5_dun
   deps += bluez5_dep
 endif
 
+libnm_device_plugin_bluetooth_static = static_library(
+  'nm-device-plugin-bluetooth-static',
+  sources: sources,
+  dependencies: deps,
+  c_args: daemon_c_flags,
+)
+
+libnm_device_plugin_bluetooth_static_dep = declare_dependency(
+  link_whole: libnm_device_plugin_bluetooth_static,
+)
+
+deps += libnm_device_plugin_bluetooth_static_dep
+
 libnm_device_plugin_bluetooth = shared_module(
   'nm-device-plugin-bluetooth',
-  sources: sources,
   dependencies: deps,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
@@ -38,9 +46,13 @@ test(
   args: [libnm_device_plugin_bluetooth.full_path(), linker_script_devices],
 )
 
-# FIXME: check_so_symbols replacement
-'''
-check-local-devices-bluetooth: src/devices/bluetooth/libnm-device-plugin-bluetooth.la
-  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so "$(srcdir)/linker-script-devices.ver"
-  $(call check_so_symbols,$(builddir)/src/devices/bluetooth/.libs/libnm-device-plugin-bluetooth.so)
-'''
+if enable_tests
+  test_unit = 'nm-bt-test'
+
+  executable(
+    test_unit,
+    'tests/' + test_unit + '.c',
+    dependencies: [ libnetwork_manager_test_dep, deps ],
+    c_args: test_c_flags,
+  )
+endif
diff --git a/src/devices/bluetooth/nm-bluez-common.h b/src/devices/bluetooth/nm-bluez-common.h
index 956375bb..ade4434b 100644
--- a/src/devices/bluetooth/nm-bluez-common.h
+++ b/src/devices/bluetooth/nm-bluez-common.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
@@ -26,19 +12,12 @@
 #define NM_BLUEZ_SERVICE           "org.bluez"
 
 #define NM_BLUEZ_MANAGER_PATH      "/"
-#define NM_OBJECT_MANAGER_INTERFACE "org.freedesktop.DBus.ObjectManager"
 
 #define NM_BLUEZ5_ADAPTER_INTERFACE "org.bluez.Adapter1"
 #define NM_BLUEZ5_DEVICE_INTERFACE  "org.bluez.Device1"
 #define NM_BLUEZ5_NETWORK_INTERFACE "org.bluez.Network1"
 #define NM_BLUEZ5_NETWORK_SERVER_INTERFACE "org.bluez.NetworkServer1"
 
-#define NM_BLUEZ4_MANAGER_INTERFACE "org.bluez.Manager"
-#define NM_BLUEZ4_ADAPTER_INTERFACE "org.bluez.Adapter"
-#define NM_BLUEZ4_DEVICE_INTERFACE  "org.bluez.Device"
-#define NM_BLUEZ4_SERIAL_INTERFACE  "org.bluez.Serial"
-#define NM_BLUEZ4_NETWORK_INTERFACE "org.bluez.Network"
-
 #define NM_BLUEZ_MANAGER_BDADDR_ADDED "bdaddr-added"
 #define NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED "network-server-added"
 
diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c
deleted file mode 100644
index fb0a72c4..00000000
--- a/src/devices/bluetooth/nm-bluez-device.c
+++ /dev/null
@@ -1,1320 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2009 - 2012 Red Hat, Inc.
- * Copyright (C) 2013 Intel Corporation.
- */
-
-#include "nm-default.h"
-
-#include "nm-bluez-device.h"
-
-#include "nm-core-internal.h"
-#include "nm-bt-error.h"
-#include "nm-bluez-common.h"
-#include "settings/nm-settings.h"
-#include "settings/nm-settings-connection.h"
-#include "NetworkManagerUtils.h"
-
-#if WITH_BLUEZ5_DUN
-#include "nm-bluez5-dun.h"
-#endif
-
-/*****************************************************************************/
-
-#define VARIANT_IS_OF_TYPE_BOOLEAN(v)      ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_BOOLEAN) ))
-#define VARIANT_IS_OF_TYPE_STRING(v)       ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_STRING) ))
-#define VARIANT_IS_OF_TYPE_OBJECT_PATH(v)  ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_OBJECT_PATH) ))
-#define VARIANT_IS_OF_TYPE_STRING_ARRAY(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_STRING_ARRAY) ))
-
-/*****************************************************************************/
-
-NM_GOBJECT_PROPERTIES_DEFINE (NMBluezDevice,
-	PROP_PATH,
-	PROP_ADDRESS,
-	PROP_NAME,
-	PROP_CAPABILITIES,
-	PROP_USABLE,
-	PROP_CONNECTED,
-);
-
-enum {
-	INITIALIZED,
-	REMOVED,
-	LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	char *path;
-	GDBusConnection *dbus_connection;
-
-	GDBusProxy *proxy;
-
-	GDBusProxy *adapter5;
-	gboolean adapter_powered;
-
-	int bluez_version;
-
-	gboolean initialized;
-	gboolean usable;
-	NMBluetoothCapabilities connection_bt_type;
-
-	guint check_emit_usable_id;
-
-	char *adapter_address;
-	char *address;
-	char *name;
-	guint32 capabilities;
-	gboolean connected;
-	gboolean paired;
-
-	char *b4_iface;
-#if WITH_BLUEZ5_DUN
-	NMBluez5DunContext *b5_dun_context;
-#endif
-
-	NMSettings *settings;
-	GSList *connections;
-
-	NMSettingsConnection *pan_connection;
-	gboolean pan_connection_no_autocreate;
-} NMBluezDevicePrivate;
-
-struct _NMBluezDevice {
-	GObject parent;
-	NMBluezDevicePrivate _priv;
-};
-
-struct _NMBluezDeviceClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMBluezDevice, nm_bluez_device, G_TYPE_OBJECT)
-
-#define NM_BLUEZ_DEVICE_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluezDevice, NM_IS_BLUEZ_DEVICE)
-
-/*****************************************************************************/
-
-static void cp_connection_added (NMSettings *settings,
-                                 NMSettingsConnection *sett_conn,
-                                 NMBluezDevice *self);
-static gboolean connection_compatible (NMBluezDevice *self, NMSettingsConnection *sett_conn);
-
-/*****************************************************************************/
-
-const char *
-nm_bluez_device_get_path (NMBluezDevice *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), NULL);
-
-	return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->path;
-}
-
-const char *
-nm_bluez_device_get_address (NMBluezDevice *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), NULL);
-
-	return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->address;
-}
-
-gboolean
-nm_bluez_device_get_initialized (NMBluezDevice *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), FALSE);
-
-	return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->initialized;
-}
-
-gboolean
-nm_bluez_device_get_usable (NMBluezDevice *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), FALSE);
-
-	return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->usable;
-}
-
-const char *
-nm_bluez_device_get_name (NMBluezDevice *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), NULL);
-
-	return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->name;
-}
-
-guint32
-nm_bluez_device_get_capabilities (NMBluezDevice *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), 0);
-
-	return NM_BLUEZ_DEVICE_GET_PRIVATE (self)->capabilities;
-}
-
-gboolean
-nm_bluez_device_get_connected (NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv;
-
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (self), FALSE);
-
-	priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	return priv->connected;
-}
-
-static void
-pan_connection_check_create (NMBluezDevice *self)
-{
-	gs_unref_object NMConnection *connection = NULL;
-	NMSettingsConnection *added;
-	NMSetting *setting;
-	gs_free char *id = NULL;
-	char uuid[37];
-	GError *error = NULL;
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	g_return_if_fail (priv->capabilities & NM_BT_CAPABILITY_NAP);
-	g_return_if_fail (priv->connections == NULL);
-	g_return_if_fail (priv->name);
-
-	if (priv->pan_connection || priv->pan_connection_no_autocreate) {
-		/* already have a connection or we don't want to create one, nothing to do. */
-		return;
-	}
-
-	/* Only try once to create a connection. If it does not succeed, we do not try again. Also,
-	 * if the connection gets deleted later, do not create another one for this device. */
-	priv->pan_connection_no_autocreate = TRUE;
-
-	/* create a new connection */
-
-	connection = nm_simple_connection_new ();
-
-	/* Setting: Connection */
-	nm_utils_uuid_generate_buf (uuid);
-	id = g_strdup_printf (_("%s Network"), priv->name);
-	setting = nm_setting_connection_new ();
-	g_object_set (setting,
-	              NM_SETTING_CONNECTION_ID, id,
-	              NM_SETTING_CONNECTION_UUID, uuid,
-	              NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
-	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_BLUETOOTH_SETTING_NAME,
-	              NULL);
-	nm_connection_add_setting (connection, setting);
-
-	/* Setting: Bluetooth */
-	setting = nm_setting_bluetooth_new ();
-	g_object_set (G_OBJECT (setting),
-	              NM_SETTING_BLUETOOTH_BDADDR, priv->address,
-	              NM_SETTING_BLUETOOTH_TYPE, NM_SETTING_BLUETOOTH_TYPE_PANU,
-	              NULL);
-	nm_connection_add_setting (connection, setting);
-
-	if (!nm_connection_normalize (connection, NULL, NULL, &error)) {
-		nm_log_err (LOGD_BT, "bluez[%s] couldn't generate a connection for NAP device: %s",
-		            priv->path, error->message);
-		g_error_free (error);
-		g_return_if_reached ();
-	}
-
-	/* Adding a new connection raises a signal which eventually calls check_emit_usable (again)
-	 * which then already finds the suitable connection in priv->connections. This is confusing,
-	 * so block the signal. check_emit_usable will succeed after this function call returns. */
-	g_signal_handlers_block_by_func (priv->settings, cp_connection_added, self);
-	nm_settings_add_connection (priv->settings,
-	                            connection,
-	                            NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
-	                            NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
-	                            NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
-	                            &added,
-	                            &error);
-	g_signal_handlers_unblock_by_func (priv->settings, cp_connection_added, self);
-
-	if (added) {
-		nm_assert (!g_slist_find (priv->connections, added));
-		nm_assert (connection_compatible (self, added));
-		priv->connections = g_slist_prepend (priv->connections, g_object_ref (added));
-		priv->pan_connection = added;
-		nm_log_dbg (LOGD_BT, "bluez[%s] added new Bluetooth connection for NAP device: '%s' (%s)", priv->path, id, uuid);
-	} else {
-		nm_log_warn (LOGD_BT, "bluez[%s] couldn't add new Bluetooth connection for NAP device: '%s' (%s): %s",
-		             priv->path, id, uuid, error->message);
-		g_clear_error (&error);
-	}
-}
-
-static gboolean
-check_emit_usable (NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	gboolean new_usable;
-
-	/* only expect the supported capabilities set. */
-	nm_assert ((priv->capabilities & ~(NM_BT_CAPABILITY_NAP | NM_BT_CAPABILITY_DUN)) == NM_BT_CAPABILITY_NONE );
-
-	new_usable = (   priv->initialized && priv->capabilities
-	              && priv->name && priv->paired
-	              && (   (priv->bluez_version == 4)
-	                  || (priv->bluez_version == 5 && priv->adapter5 && priv->adapter_powered))
-	              && priv->dbus_connection && priv->address && priv->adapter_address);
-
-	if (!new_usable)
-		goto END;
-
-	if (priv->connections)
-		goto END;
-
-	if (!(priv->capabilities & NM_BT_CAPABILITY_NAP)) {
-		/* non NAP devices are only usable, if they already have a connection. */
-		new_usable = FALSE;
-		goto END;
-	}
-
-	pan_connection_check_create (self);
-	new_usable = !!priv->pan_connection;
-
-END:
-	if (new_usable != priv->usable) {
-		priv->usable = new_usable;
-		_notify (self, PROP_USABLE);
-	}
-
-	return G_SOURCE_REMOVE;
-}
-
-static void
-check_emit_usable_schedule (NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	if (priv->check_emit_usable_id == 0)
-		priv->check_emit_usable_id = g_idle_add ((GSourceFunc) check_emit_usable, self);
-}
-
-/*****************************************************************************/
-
-static gboolean
-connection_compatible (NMBluezDevice *self, NMSettingsConnection *sett_conn)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection = nm_settings_connection_get_connection (sett_conn);
-	NMSettingBluetooth *s_bt;
-	const char *bt_type;
-	const char *bdaddr;
-
-	if (!nm_connection_is_type (connection, NM_SETTING_BLUETOOTH_SETTING_NAME))
-		return FALSE;
-
-	s_bt = nm_connection_get_setting_bluetooth (connection);
-	if (!s_bt)
-		return FALSE;
-
-	if (!priv->address)
-		return FALSE;
-
-	bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt);
-	if (!bdaddr)
-		return FALSE;
-	if (!nm_utils_hwaddr_matches (bdaddr, -1, priv->address, -1))
-		return FALSE;
-
-	bt_type = nm_setting_bluetooth_get_connection_type (s_bt);
-
-	if (nm_streq (bt_type, NM_SETTING_BLUETOOTH_TYPE_NAP))
-		return FALSE;
-
-	if (   g_str_equal (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN)
-	    && !(priv->capabilities & NM_BT_CAPABILITY_DUN))
-		return FALSE;
-
-	if (   g_str_equal (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU)
-	    && !(priv->capabilities & NM_BT_CAPABILITY_NAP))
-		return FALSE;
-
-	return TRUE;
-}
-
-static gboolean
-_internal_track_connection (NMBluezDevice *self,
-                            NMSettingsConnection *sett_conn,
-                            gboolean tracked)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	gboolean was_tracked;
-
-	was_tracked = !!g_slist_find (priv->connections, sett_conn);
-	if (was_tracked == !!tracked)
-		return FALSE;
-
-	if (tracked)
-		priv->connections = g_slist_prepend (priv->connections, g_object_ref (sett_conn));
-	else {
-		priv->connections = g_slist_remove (priv->connections, sett_conn);
-		if (priv->pan_connection == sett_conn)
-			priv->pan_connection = NULL;
-		g_object_unref (sett_conn);
-	}
-
-	return TRUE;
-}
-
-static void
-cp_connection_added (NMSettings *settings,
-                     NMSettingsConnection *sett_conn,
-                     NMBluezDevice *self)
-{
-	if (connection_compatible (self, sett_conn)) {
-		if (_internal_track_connection (self, sett_conn, TRUE))
-			check_emit_usable (self);
-	}
-}
-
-static void
-cp_connection_removed (NMSettings *settings,
-                       NMSettingsConnection *sett_conn,
-                       NMBluezDevice *self)
-{
-	if (_internal_track_connection (self, sett_conn, FALSE))
-		check_emit_usable (self);
-}
-
-static void
-cp_connection_updated (NMSettings *settings,
-                       NMSettingsConnection *sett_conn,
-                       guint update_reason_u,
-                       NMBluezDevice *self)
-{
-	if (_internal_track_connection (self, sett_conn,
-	                                connection_compatible (self, sett_conn)))
-		check_emit_usable_schedule (self);
-}
-
-static void
-load_connections (NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	NMSettingsConnection *const*connections;
-	guint i;
-	gboolean changed = FALSE;
-
-	connections = nm_settings_get_connections (priv->settings, NULL);
-	for (i = 0; connections[i]; i++) {
-		if (connection_compatible (self, connections[i]))
-			changed |= _internal_track_connection (self, connections[i], TRUE);
-	}
-	if (changed)
-		check_emit_usable (self);
-}
-
-/*****************************************************************************/
-
-static void
-bluez_disconnect_cb (GDBusConnection *dbus_connection,
-                     GAsyncResult *res,
-                     gpointer user_data)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE ((NMBluezDevice *) user_data);
-	GError *error = NULL;
-	GVariant *variant;
-
-	variant = g_dbus_connection_call_finish (dbus_connection, res, &error);
-	if (!variant) {
-		if (!strstr (error->message, "org.bluez.Error.NotConnected"))
-			nm_log_warn (LOGD_BT, "bluez[%s]: failed to disconnect: %s", priv->path, error->message);
-		g_error_free (error);
-	} else
-		g_variant_unref (variant);
-
-	g_object_unref (NM_BLUEZ_DEVICE (user_data));
-}
-
-void
-nm_bluez_device_disconnect (NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GVariant *args = NULL;
-	const char *dbus_iface = NULL;
-
-	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
-			 * ignore the disconnect if the interface isn't known.
-			 */
-			if (!priv->b4_iface)
-				goto out;
-			args = g_variant_new ("(s)", priv->b4_iface),
-			dbus_iface = NM_BLUEZ4_SERIAL_INTERFACE;
-		} else if (priv->bluez_version == 5) {
-#if WITH_BLUEZ5_DUN
-			nm_bluez5_dun_cleanup (priv->b5_dun_context);
-#endif
-			priv->connected = FALSE;
-			goto out;
-		}
-	} else if (priv->connection_bt_type == NM_BT_CAPABILITY_NAP) {
-		if (priv->bluez_version == 4)
-			dbus_iface = NM_BLUEZ4_NETWORK_INTERFACE;
-		else if (priv->bluez_version == 5)
-			dbus_iface = NM_BLUEZ5_NETWORK_INTERFACE;
-		else
-			g_assert_not_reached ();
-	} else
-		g_assert_not_reached ();
-
-	g_dbus_connection_call (priv->dbus_connection,
-	                        NM_BLUEZ_SERVICE,
-	                        priv->path,
-	                        dbus_iface,
-	                        "Disconnect",
-	                        args ?: g_variant_new("()"),
-	                        NULL,
-	                        G_DBUS_CALL_FLAGS_NONE,
-	                        10000,
-	                        NULL,
-	                        (GAsyncReadyCallback) bluez_disconnect_cb,
-	                        g_object_ref (self));
-
-out:
-	g_clear_pointer (&priv->b4_iface, g_free);
-	priv->connection_bt_type = NM_BT_CAPABILITY_NONE;
-}
-
-static void
-_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)
-{
-	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;
-
-	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
-
-	priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	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;
-	}
-
-	_connect_complete (self, device, callback, callback_user_data, error);
-}
-
-#if WITH_BLUEZ5_DUN
-static void
-_connect_cb_bluez5_dun (NMBluez5DunContext *context,
-                        const char *device,
-                        GError *error,
-                        gpointer 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;
-
-	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;
-
-	_connect_complete (self, device, callback, callback_user_data, error);
-}
-#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,
-                               GCancellable *cancellable,
-                               NMBluezDeviceConnectCallback callback,
-                               gpointer callback_user_data)
-{
-	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));
-
-	priv->connection_bt_type = connection_bt_type;
-
-	if (connection_bt_type == NM_BT_CAPABILITY_NAP) {
-		connect_type = BLUETOOTH_CONNECT_NAP;
-		if (priv->bluez_version == 4)
-			dbus_iface = NM_BLUEZ4_NETWORK_INTERFACE;
-		else if (priv->bluez_version == 5)
-			dbus_iface = NM_BLUEZ5_NETWORK_INTERFACE;
-	} else if (connection_bt_type == NM_BT_CAPABILITY_DUN) {
-		connect_type = BLUETOOTH_CONNECT_DUN;
-		if (priv->bluez_version == 4)
-			dbus_iface = NM_BLUEZ4_SERIAL_INTERFACE;
-		else if (priv->bluez_version == 5) {
-#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,
-			                       _connect_cb_bluez5_dun,
-			                       nm_utils_user_data_pack (g_object_ref (self),
-			                                                nm_g_object_ref (cancellable),
-			                                                callback,
-			                                                callback_user_data));
-#else
-			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_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,
-	                        dbus_iface,
-	                        "Connect",
-	                        g_variant_new ("(s)", connect_type),
-	                        NULL,
-	                        G_DBUS_CALL_FLAGS_NONE,
-	                        20000,
-	                        cancellable,
-	                        _connect_cb,
-	                        nm_utils_user_data_pack (g_object_ref (self),
-	                                                 callback,
-	                                                 callback_user_data));
-}
-
-/*****************************************************************************/
-
-static void
-set_adapter_address (NMBluezDevice *self, const char *address)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	g_return_if_fail (address);
-
-	if (priv->adapter_address)
-		g_free (priv->adapter_address);
-	priv->adapter_address = g_strdup (address);
-}
-
-static guint32
-convert_uuids_to_capabilities (const char **strings)
-{
-	const char **iter;
-	guint32 capabilities = 0;
-
-	for (iter = strings; iter && *iter; iter++) {
-		char **parts;
-
-		parts = g_strsplit (*iter, "-", -1);
-		if (parts && parts[0]) {
-			switch (g_ascii_strtoull (parts[0], NULL, 16)) {
-			case 0x1103:
-				capabilities |= NM_BT_CAPABILITY_DUN;
-				break;
-			case 0x1116:
-				capabilities |= NM_BT_CAPABILITY_NAP;
-				break;
-			default:
-				break;
-			}
-		}
-		g_strfreev (parts);
-	}
-
-	return capabilities;
-}
-
-static void
-_set_property_capabilities (NMBluezDevice *self, const char **uuids)
-{
-	guint32 uint_val;
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	uint_val = convert_uuids_to_capabilities (uuids);
-	if (priv->capabilities != uint_val) {
-		if (priv->capabilities) {
-			/* changing (relevant) capabilities is not supported and ignored -- except setting initially */
-			nm_log_warn (LOGD_BT, "bluez[%s] ignore change of capabilities for Bluetooth device from %u to %u",
-			             priv->path, priv->capabilities, uint_val);
-			return;
-		}
-		nm_log_dbg (LOGD_BT, "bluez[%s] set capabilities for Bluetooth device: %s%s%s", priv->path,
-		            uint_val & NM_BT_CAPABILITY_NAP ? "NAP" : "",
-		            ((uint_val & NM_BT_CAPABILITY_DUN) && (uint_val &NM_BT_CAPABILITY_NAP)) ? " | " : "",
-		            uint_val & NM_BT_CAPABILITY_DUN ? "DUN" : "");
-		priv->capabilities = uint_val;
-		_notify (self, PROP_CAPABILITIES);
-	}
-}
-
-/**
- * priv->address can only be set one to a certain (non NULL) value. Every later attempt
- * to reset it to another value will be ignored and a warning will be logged.
- **/
-static void
-_set_property_address (NMBluezDevice *self, const char *addr)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	if (g_strcmp0 (priv->address, addr) == 0)
-		return;
-
-	if (!addr) {
-		nm_log_warn (LOGD_BT, "bluez[%s] cannot reset address from '%s' to NULL", priv->path, priv->address);
-		return;
-	}
-
-	if (priv->address != NULL) {
-		nm_log_warn (LOGD_BT, "bluez[%s] cannot reset address from '%s' to '%s'", priv->path, priv->address, addr);
-		return;
-	}
-
-	if (!nm_utils_hwaddr_valid (addr, ETH_ALEN)) {
-		nm_log_warn (LOGD_BT, "bluez[%s] cannot set address to '%s' (invalid value)", priv->path, addr);
-		return;
-	}
-
-	priv->address = g_strdup (addr);
-	_notify (self, PROP_ADDRESS);
-}
-
-static void
-_take_variant_property_address (NMBluezDevice *self, GVariant *v)
-{
-	_set_property_address (self, VARIANT_IS_OF_TYPE_STRING (v) ? g_variant_get_string (v, NULL) : NULL);
-	if (v)
-		g_variant_unref (v);
-}
-
-static void
-_take_variant_property_name (NMBluezDevice *self, GVariant *v)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	const char *str;
-
-	if (VARIANT_IS_OF_TYPE_STRING (v)) {
-		str = g_variant_get_string (v, NULL);
-		if (g_strcmp0 (priv->name, str)) {
-			g_free (priv->name);
-			priv->name = g_strdup (str);
-			_notify (self, PROP_NAME);
-		}
-	}
-	if (v)
-		g_variant_unref (v);
-}
-
-static void
-_take_variant_property_uuids (NMBluezDevice *self, GVariant *v)
-{
-	if (VARIANT_IS_OF_TYPE_STRING_ARRAY (v)) {
-		const char **uuids = g_variant_get_strv (v, NULL);
-
-		_set_property_capabilities (self, uuids);
-		g_free (uuids);
-	}
-	if (v)
-		g_variant_unref (v);
-}
-
-static void
-_take_variant_property_connected (NMBluezDevice *self, GVariant *v)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	if (VARIANT_IS_OF_TYPE_BOOLEAN (v)) {
-		gboolean connected = g_variant_get_boolean (v);
-
-		if (priv->connected != connected) {
-			priv->connected = connected;
-			_notify (self, PROP_CONNECTED);
-		}
-	}
-	if (v)
-		g_variant_unref (v);
-}
-
-static void
-_take_variant_property_paired (NMBluezDevice *self, GVariant *v)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	if (VARIANT_IS_OF_TYPE_BOOLEAN (v))
-		priv->paired = g_variant_get_boolean (v);
-
-	if (v)
-		g_variant_unref (v);
-}
-
-static void
-adapter5_on_properties_changed (GDBusProxy *proxy,
-                                GVariant *changed_properties,
-                                GStrv invalidated_properties,
-                                gpointer user_data)
-{
-	NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data);
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GVariantIter i;
-	const char *property;
-	GVariant *v;
-
-	g_variant_iter_init (&i, changed_properties);
-	while (g_variant_iter_next (&i, "{&sv}", &property, &v)) {
-		if (!strcmp (property, "Powered") && VARIANT_IS_OF_TYPE_BOOLEAN (v)) {
-			gboolean powered = g_variant_get_boolean (v);
-			if (priv->adapter_powered != powered)
-				priv->adapter_powered = powered;
-		}
-		g_variant_unref (v);
-	}
-
-	check_emit_usable (self);
-}
-
-static void
-adapter5_on_acquired (GObject *object, GAsyncResult *res, NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GError *error = NULL;
-	GVariant *v;
-
-	priv->adapter5 = g_dbus_proxy_new_for_bus_finish (res, &error);
-	if (!priv->adapter5) {
-		nm_log_warn (LOGD_BT, "bluez[%s] failed to acquire adapter proxy: %s.", priv->path, error->message);
-		g_clear_error (&error);
-		g_signal_emit (self, signals[INITIALIZED], 0, FALSE);
-	} else {
-		g_signal_connect (priv->adapter5, "g-properties-changed",
-		                  G_CALLBACK (adapter5_on_properties_changed), self);
-
-		/* Check adapter's powered state */
-		v = g_dbus_proxy_get_cached_property (priv->adapter5, "Powered");
-		priv->adapter_powered = VARIANT_IS_OF_TYPE_BOOLEAN (v) ? g_variant_get_boolean (v) : FALSE;
-		if (v)
-			g_variant_unref (v);
-
-		v = g_dbus_proxy_get_cached_property (priv->adapter5, "Address");
-		if (VARIANT_IS_OF_TYPE_STRING (v))
-			set_adapter_address (self, g_variant_get_string (v, NULL));
-
-		priv->initialized = TRUE;
-		g_signal_emit (self, signals[INITIALIZED], 0, TRUE);
-
-		check_emit_usable (self);
-	}
-
-	g_object_unref (self);
-}
-
-static void
-_take_one_variant_property (NMBluezDevice *self, const char *property, GVariant *v)
-{
-	if (v) {
-		if (!g_strcmp0 (property, "Address"))
-			_take_variant_property_address (self, v);
-		else if (!g_strcmp0 (property, "Connected"))
-			_take_variant_property_connected (self, v);
-		else if (!g_strcmp0 (property, "Paired"))
-			_take_variant_property_paired (self, v);
-		else if (!g_strcmp0 (property, "Name"))
-			_take_variant_property_name (self, v);
-		else if (!g_strcmp0 (property, "UUIDs"))
-			_take_variant_property_uuids (self, v);
-		else
-			g_variant_unref (v);
-	}
-}
-
-static void
-_set_properties (NMBluezDevice *self, GVariant *properties)
-{
-	GVariantIter i;
-	const char *property;
-	GVariant *v;
-
-	g_object_freeze_notify (G_OBJECT (self));
-	g_variant_iter_init (&i, properties);
-	while (g_variant_iter_next (&i, "{&sv}", &property, &v))
-		_take_one_variant_property (self, property, v);
-	g_object_thaw_notify (G_OBJECT (self));
-}
-
-static void
-properties_changed (GDBusProxy *proxy,
-                    GVariant *changed_properties,
-                    GStrv invalidated_properties,
-                    gpointer user_data)
-{
-	NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data);
-
-	_set_properties (self, changed_properties);
-	check_emit_usable (self);
-}
-
-static void
-bluez4_property_changed (GDBusProxy *proxy,
-                         const char *property,
-                         GVariant   *v,
-                         gpointer    user_data)
-{
-	NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data);
-
-	_take_one_variant_property (self, property, v);
-	check_emit_usable (self);
-}
-
-static void
-get_properties_cb_4 (GObject *source_object, GAsyncResult *res, gpointer user_data)
-{
-	NMBluezDevice *self = NM_BLUEZ_DEVICE (user_data);
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GError *err = NULL;
-	GVariant *v_properties, *v_dict;
-
-	v_properties = _nm_dbus_proxy_call_finish (priv->proxy, res,
-	                                           G_VARIANT_TYPE ("(a{sv})"),
-	                                           &err);
-	if (!v_properties) {
-		g_dbus_error_strip_remote_error (err);
-		nm_log_warn (LOGD_BT, "bluez[%s] error getting device properties: %s",
-		             priv->path, err->message);
-		g_error_free (err);
-		g_signal_emit (self, signals[INITIALIZED], 0, FALSE);
-		goto END;
-	}
-
-	v_dict = g_variant_get_child_value (v_properties, 0);
-	_set_properties (self, v_dict);
-	g_variant_unref (v_dict);
-	g_variant_unref (v_properties);
-
-	/* Check if any connections match this device */
-	load_connections (self);
-
-	priv->initialized = TRUE;
-	g_signal_emit (self, signals[INITIALIZED], 0, TRUE);
-
-	check_emit_usable (self);
-
-END:
-	g_object_unref (self);
-}
-
-static void
-query_properties (NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GVariant *v;
-
-	switch (priv->bluez_version) {
-	case 4:
-		g_dbus_proxy_call (priv->proxy, "GetProperties", NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, 3000,
-		                   NULL, get_properties_cb_4, g_object_ref (self));
-		break;
-	case 5:
-		g_object_freeze_notify (G_OBJECT (self));
-		_take_variant_property_address   (self, g_dbus_proxy_get_cached_property (priv->proxy, "Address"));
-		_take_variant_property_connected (self, g_dbus_proxy_get_cached_property (priv->proxy, "Connected"));
-		_take_variant_property_paired    (self, g_dbus_proxy_get_cached_property (priv->proxy, "Paired"));
-		_take_variant_property_name      (self, g_dbus_proxy_get_cached_property (priv->proxy, "Name"));
-		_take_variant_property_uuids     (self, g_dbus_proxy_get_cached_property (priv->proxy, "UUIDs"));
-		g_object_thaw_notify (G_OBJECT (self));
-
-		v = g_dbus_proxy_get_cached_property (priv->proxy, "Adapter");
-		if (VARIANT_IS_OF_TYPE_OBJECT_PATH (v)) {
-			g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-			                          G_DBUS_PROXY_FLAGS_NONE,
-			                          NULL,
-			                          NM_BLUEZ_SERVICE,
-			                          g_variant_get_string (v, NULL),
-			                          NM_BLUEZ5_ADAPTER_INTERFACE,
-			                          NULL,
-			                          (GAsyncReadyCallback) adapter5_on_acquired,
-			                          g_object_ref (self));
-			g_variant_unref (v);
-		} else {
-			/* If the Adapter property is unset at this point, we won't try to acquire the adapter later on
-			 * and the device stays unusable. This should not happen, but if it does, log a debug message. */
-			nm_log_dbg (LOGD_BT, "bluez[%s] device has no adapter property and cannot be used.", priv->path);
-		}
-
-		/* Check if any connections match this device */
-		load_connections (self);
-
-		break;
-	}
-}
-
-static void
-on_proxy_acquired (GObject *object, GAsyncResult *res, NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GError *error = NULL;
-
-	priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
-
-	if (!priv->proxy) {
-		nm_log_warn (LOGD_BT, "bluez[%s] failed to acquire device proxy: %s.", priv->path, error->message);
-		g_clear_error (&error);
-		g_signal_emit (self, signals[INITIALIZED], 0, FALSE);
-	} else {
-		g_signal_connect (priv->proxy, "g-properties-changed",
-		                  G_CALLBACK (properties_changed), self);
-		if (priv->bluez_version == 4) {
-			/* Watch for custom Bluez4 PropertyChanged signals */
-			_nm_dbus_signal_connect (priv->proxy, "PropertyChanged", G_VARIANT_TYPE ("(sv)"),
-			                         G_CALLBACK (bluez4_property_changed), self);
-		}
-
-		query_properties (self);
-	}
-	g_object_unref (self);
-}
-
-static void
-on_bus_acquired (GObject *object, GAsyncResult *res, NMBluezDevice *self)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	GError *error = NULL;
-
-	priv->dbus_connection = g_bus_get_finish (res, &error);
-
-	if (!priv->dbus_connection) {
-		nm_log_warn (LOGD_BT, "bluez[%s] failed to acquire bus connection: %s.", priv->path, error->message);
-		g_clear_error (&error);
-		g_signal_emit (self, signals[INITIALIZED], 0, FALSE);
-	} else
-		check_emit_usable (self);
-
-	g_object_unref (self);
-}
-
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id,
-              GValue *value, GParamSpec *pspec)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE ((NMBluezDevice *) object);
-
-	switch (prop_id) {
-	case PROP_PATH:
-		g_value_set_string (value, priv->path);
-		break;
-	case PROP_ADDRESS:
-		g_value_set_string (value, priv->address);
-		break;
-	case PROP_NAME:
-		g_value_set_string (value, priv->name);
-		break;
-	case PROP_CAPABILITIES:
-		g_value_set_uint (value, priv->capabilities);
-		break;
-	case PROP_USABLE:
-		g_value_set_boolean (value, priv->usable);
-		break;
-	case PROP_CONNECTED:
-		g_value_set_boolean (value, priv->connected);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE ((NMBluezDevice *) object);
-
-	switch (prop_id) {
-	case PROP_PATH:
-		/* construct-only */
-		priv->path = g_value_dup_string (value);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-/*****************************************************************************/
-
-static void
-nm_bluez_device_init (NMBluezDevice *self)
-{
-}
-
-NMBluezDevice *
-nm_bluez_device_new (const char *path,
-                     const char *adapter_address,
-                     NMSettings *settings,
-                     int bluez_version)
-{
-	NMBluezDevice *self;
-	NMBluezDevicePrivate *priv;
-	const char *interface_name = NULL;
-
-	g_return_val_if_fail (path != NULL, NULL);
-	g_return_val_if_fail (NM_IS_SETTINGS (settings), NULL);
-	g_return_val_if_fail (bluez_version == 4 || bluez_version == 5, NULL);
-
-	self = (NMBluezDevice *) g_object_new (NM_TYPE_BLUEZ_DEVICE,
-	                                       NM_BLUEZ_DEVICE_PATH, path,
-	                                       NULL);
-	if (!self)
-		return NULL;
-
-	nm_log_dbg (LOGD_BT, "bluez[%s] create NMBluezDevice", path);
-
-	priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-
-	priv->bluez_version = bluez_version;
-	priv->settings = g_object_ref (settings);
-	g_return_val_if_fail (bluez_version == 5 || (bluez_version == 4 && adapter_address), NULL);
-	if (adapter_address)
-		set_adapter_address (self, adapter_address);
-
-	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_ADDED,   G_CALLBACK (cp_connection_added),   self);
-	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, G_CALLBACK (cp_connection_removed), self);
-	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, G_CALLBACK (cp_connection_updated), self);
-
-	g_bus_get (G_BUS_TYPE_SYSTEM,
-	           NULL,
-	           (GAsyncReadyCallback) on_bus_acquired,
-	           g_object_ref (self));
-
-	switch (priv->bluez_version) {
-	case 4:
-		interface_name = NM_BLUEZ4_DEVICE_INTERFACE;
-		break;
-	case 5:
-		interface_name = NM_BLUEZ5_DEVICE_INTERFACE;
-		break;
-	}
-
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_NONE,
-	                          NULL,
-	                          NM_BLUEZ_SERVICE,
-	                          priv->path,
-	                          interface_name,
-	                          NULL,
-	                          (GAsyncReadyCallback) on_proxy_acquired,
-	                          g_object_ref (self));
-	return self;
-}
-
-static void
-dispose (GObject *object)
-{
-	NMBluezDevice *self = NM_BLUEZ_DEVICE (object);
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	NMSettingsConnection *to_delete = NULL;
-
-	nm_clear_g_source (&priv->check_emit_usable_id);
-
-	if (priv->pan_connection) {
-		/* Check whether we want to remove the created connection. If so, we take a reference
-		 * and delete it at the end of dispose(). */
-		if (NM_FLAGS_HAS (nm_settings_connection_get_flags (priv->pan_connection),
-		                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
-			to_delete = g_object_ref (priv->pan_connection);
-
-		priv->pan_connection = NULL;
-	}
-
-#if WITH_BLUEZ5_DUN
-	if (priv->b5_dun_context) {
-		nm_bluez5_dun_free (priv->b5_dun_context);
-		priv->b5_dun_context = NULL;
-	}
-#endif
-
-	if (priv->settings) {
-		g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_added, self);
-		g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_removed, self);
-		g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_updated, self);
-	}
-
-	g_slist_free_full (priv->connections, g_object_unref);
-	priv->connections = NULL;
-
-	if (priv->adapter5) {
-		g_signal_handlers_disconnect_by_func (priv->adapter5, adapter5_on_properties_changed, self);
-		g_clear_object (&priv->adapter5);
-	}
-
-	g_clear_object (&priv->dbus_connection);
-
-	G_OBJECT_CLASS (nm_bluez_device_parent_class)->dispose (object);
-
-	if (to_delete) {
-		nm_log_dbg (LOGD_BT, "bluez[%s] removing Bluetooth connection for NAP device: '%s' (%s)", priv->path,
-		            nm_settings_connection_get_id (to_delete), nm_settings_connection_get_uuid (to_delete));
-		nm_settings_connection_delete (to_delete, FALSE);
-		g_object_unref (to_delete);
-	}
-
-	g_clear_object (&priv->settings);
-}
-
-static void
-finalize (GObject *object)
-{
-	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE ((NMBluezDevice *) object);
-
-	nm_log_dbg (LOGD_BT, "bluez[%s]: finalize NMBluezDevice", priv->path);
-
-	g_free (priv->path);
-	g_free (priv->adapter_address);
-	g_free (priv->address);
-	g_free (priv->name);
-	g_free (priv->b4_iface);
-
-	if (priv->proxy)
-		g_signal_handlers_disconnect_by_data (priv->proxy, object);
-	g_clear_object (&priv->proxy);
-
-	G_OBJECT_CLASS (nm_bluez_device_parent_class)->finalize (object);
-}
-
-static void
-nm_bluez_device_class_init (NMBluezDeviceClass *config_class)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
-
-	object_class->get_property = get_property;
-	object_class->set_property = set_property;
-	object_class->dispose = dispose;
-	object_class->finalize = finalize;
-
-	obj_properties[PROP_PATH] =
-	     g_param_spec_string (NM_BLUEZ_DEVICE_PATH, "", "",
-	                          NULL,
-	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_ADDRESS] =
-	     g_param_spec_string (NM_BLUEZ_DEVICE_ADDRESS, "", "",
-	                          NULL,
-	                          G_PARAM_READABLE |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_NAME] =
-	     g_param_spec_string (NM_BLUEZ_DEVICE_NAME, "", "",
-	                          NULL,
-	                          G_PARAM_READABLE |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_CAPABILITIES] =
-	     g_param_spec_uint (NM_BLUEZ_DEVICE_CAPABILITIES, "", "",
-	                        0, G_MAXUINT, 0,
-	                        G_PARAM_READABLE |
-	                        G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_USABLE] =
-	     g_param_spec_boolean (NM_BLUEZ_DEVICE_USABLE, "", "",
-	                           FALSE,
-	                           G_PARAM_READABLE |
-	                           G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_CONNECTED] =
-	     g_param_spec_boolean (NM_BLUEZ_DEVICE_CONNECTED, "", "",
-	                           FALSE,
-	                           G_PARAM_READABLE |
-	                           G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	signals[INITIALIZED] = g_signal_new (NM_BLUEZ_DEVICE_INITIALIZED,
-	                                     G_OBJECT_CLASS_TYPE (object_class),
-	                                     G_SIGNAL_RUN_LAST,
-	                                     0,
-	                                     NULL, NULL, NULL,
-	                                     G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
-
-	signals[REMOVED] =     g_signal_new (NM_BLUEZ_DEVICE_REMOVED,
-	                                     G_OBJECT_CLASS_TYPE (object_class),
-	                                     G_SIGNAL_RUN_LAST,
-	                                     0,
-	                                     NULL, NULL, NULL,
-	                                     G_TYPE_NONE, 0);
-}
-
diff --git a/src/devices/bluetooth/nm-bluez-device.h b/src/devices/bluetooth/nm-bluez-device.h
deleted file mode 100644
index e8dd2ced..00000000
--- a/src/devices/bluetooth/nm-bluez-device.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2009 - 2014 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_BLUEZ_DEVICE_H__
-#define __NETWORKMANAGER_BLUEZ_DEVICE_H__
-
-#include "nm-connection.h"
-
-#define NM_TYPE_BLUEZ_DEVICE            (nm_bluez_device_get_type ())
-#define NM_BLUEZ_DEVICE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ_DEVICE, NMBluezDevice))
-#define NM_BLUEZ_DEVICE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ_DEVICE, NMBluezDeviceClass))
-#define NM_IS_BLUEZ_DEVICE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ_DEVICE))
-#define NM_IS_BLUEZ_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ_DEVICE))
-#define NM_BLUEZ_DEVICE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ_DEVICE, NMBluezDeviceClass))
-
-/* Properties */
-#define NM_BLUEZ_DEVICE_PATH         "path"
-#define NM_BLUEZ_DEVICE_ADDRESS      "address"
-#define NM_BLUEZ_DEVICE_NAME         "name"
-#define NM_BLUEZ_DEVICE_CAPABILITIES "capabilities"
-#define NM_BLUEZ_DEVICE_USABLE       "usable"
-#define NM_BLUEZ_DEVICE_CONNECTED    "connected"
-
-/* Signals */
-#define NM_BLUEZ_DEVICE_INITIALIZED  "initialized"
-#define NM_BLUEZ_DEVICE_REMOVED      "removed"
-
-typedef struct _NMBluezDevice NMBluezDevice;
-typedef struct _NMBluezDeviceClass NMBluezDeviceClass;
-
-GType nm_bluez_device_get_type (void);
-
-NMBluezDevice *nm_bluez_device_new (const char *path,
-                                    const char *adapter_address,
-                                    NMSettings *settings,
-                                    int bluez_version);
-
-const char *nm_bluez_device_get_path (NMBluezDevice *self);
-
-gboolean nm_bluez_device_get_initialized (NMBluezDevice *self);
-
-gboolean nm_bluez_device_get_usable (NMBluezDevice *self);
-
-const char *nm_bluez_device_get_address (NMBluezDevice *self);
-
-const char *nm_bluez_device_get_name (NMBluezDevice *self);
-
-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,
-                               GCancellable *cancellable,
-                               NMBluezDeviceConnectCallback callback,
-                               gpointer callback_user_data);
-
-void
-nm_bluez_device_disconnect (NMBluezDevice *self);
-
-#endif /* __NETWORKMANAGER_BLUEZ_DEVICE_H__ */
-
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index 7577ab8b..ef087d83 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -1,73 +1,185 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2013 - 2014 Red Hat, Inc.
  */
 
 #include "nm-default.h"
 
+#include "nm-bluez-manager.h"
+
 #include <signal.h>
 #include <stdlib.h>
 #include <gmodule.h>
 
+#include "nm-glib-aux/nm-dbus-aux.h"
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-dbus-manager.h"
 #include "devices/nm-device-factory.h"
 #include "devices/nm-device-bridge.h"
 #include "nm-setting-bluetooth.h"
 #include "settings/nm-settings.h"
-#include "nm-bluez4-manager.h"
-#include "nm-bluez5-manager.h"
-#include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
 #include "nm-device-bt.h"
+#include "nm-manager.h"
+#include "nm-bluez5-dun.h"
 #include "nm-core-internal.h"
 #include "platform/nm-platform.h"
 #include "nm-std-aux/nm-dbus-compat.h"
 
 /*****************************************************************************/
 
-#define NM_TYPE_BLUEZ_MANAGER            (nm_bluez_manager_get_type ())
-#define NM_BLUEZ_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ_MANAGER, NMBluezManager))
-#define NM_BLUEZ_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass))
-#define NM_IS_BLUEZ_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ_MANAGER))
-#define NM_IS_BLUEZ_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_BLUEZ_MANAGER))
-#define NM_BLUEZ_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass))
+#if WITH_BLUEZ5_DUN
+#define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_DUN
+#else
+#define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_NONE
+#endif
+#define _NM_BT_CAPABILITY_SUPPORTED (NM_BT_CAPABILITY_NAP | _NM_BT_CAPABILITY_SUPPORTED_DUN)
+
+typedef struct {
+	const char *bdaddr;
+	CList lst_head;
+	NMBluetoothCapabilities bt_type:8;
+	char bdaddr_data[];
+} ConnDataHead;
+
+typedef struct {
+	NMSettingsConnection *sett_conn;
+	ConnDataHead *cdata_hd;
+	CList lst;
+} ConnDataElem;
 
 typedef struct {
-	int bluez_version;
+	GCancellable *ext_cancellable;
+	GCancellable *int_cancellable;
+	NMBtVTableRegisterCallback callback;
+	gpointer callback_user_data;
+	gulong ext_cancelled_id;
+} NetworkServerRegisterReqData;
+
+typedef struct {
+	GCancellable *ext_cancellable;
+	GCancellable *int_cancellable;
+	NMBluezManagerConnectCb callback;
+	gpointer callback_user_data;
+	char *device_name;
+	gulong ext_cancelled_id;
+	guint timeout_id;
+	guint timeout_wait_connect_id;
+} DeviceConnectReqData;
+
+typedef struct {
+	const char *object_path;
+
+	NMBluezManager *self;
 
+	/* Fields name with "d_" prefix are purely cached values from BlueZ's
+	 * ObjectManager D-Bus interface. There is no logic whatsoever about
+	 * them.
+	 */
+
+	CList process_change_lst;
+
+	struct {
+		char *address;
+	} d_adapter;
+
+	struct {
+		char *address;
+		char *name;
+		char *adapter;
+	} d_device;
+
+	struct {
+		char *interface;
+	} d_network;
+
+	struct {
+		CList lst;
+		char *adapter_address;
+		NMDevice *device_br;
+		NetworkServerRegisterReqData *r_req_data;
+	} x_network_server;
+
+	struct {
+		NMSettingsConnection *panu_connection;
+		NMDeviceBt *device_bt;
+		DeviceConnectReqData *c_req_data;
+		NMBluez5DunContext *connect_dun_context;
+		gulong device_bt_signal_id;
+	} x_device;
+
+	/* indicate whether the D-Bus object has the particular D-Bus interface. */
+	bool d_has_adapter_iface:1;
+	bool d_has_device_iface:1;
+	bool d_has_network_iface:1;
+	bool d_has_network_server_iface:1;
+
+	/* cached D-Bus properties for Device1 ("d_device*"). */
+	NMBluetoothCapabilities d_device_capabilities:6;
+	bool d_device_connected:1;
+	bool d_device_paired:1;
+
+	/* cached D-Bus properties for Network1 ("d_network*"). */
+	bool d_network_connected:1;
+
+	/* cached D-Bus properties for Adapter1 ("d_adapter*"). */
+	bool d_adapter_powered:1;
+
+	/* properties related to device ("x_device*"). */
+	NMBluetoothCapabilities x_device_connect_bt_type:6;
+	bool x_device_is_usable:1;
+	bool x_device_is_connected:1;
+
+	bool x_device_panu_connection_allow_create:1;
+
+	/* flag to remember last time when we checked wether the object
+	 * was  a suitable adapter that is usable to a device. */
+	bool was_usable_adapter_for_device_before:1;
+
+	char _object_path_intern[];
+} BzDBusObj;
+
+typedef struct {
+	NMManager *manager;
 	NMSettings *settings;
-	NMBluez4Manager *manager4;
-	NMBluez5Manager *manager5;
 
-	guint watch_name_id;
+	GDBusConnection *dbus_connection;
+
+	NMBtVTableNetworkServer vtable_network_server;
+
+	GCancellable *name_owner_get_cancellable;
+	GCancellable *get_managed_objects_cancellable;
+
+	GHashTable *bzobjs;
+
+	char *name_owner;
+
+	GHashTable *conn_data_heads;
+	GHashTable *conn_data_elems;
+
+	CList network_server_lst_head;
+
+	CList process_change_lst_head;
+
+	guint name_owner_changed_id;
 
-	GDBusProxy *introspect_proxy;
-	GCancellable *async_cancellable;
+	guint managed_objects_changed_id;
+
+	guint properties_changed_id;
+
+	guint process_change_idle_id;
+
+	bool settings_registered:1;
 } NMBluezManagerPrivate;
 
-typedef struct {
+struct _NMBluezManager {
 	NMDeviceFactory parent;
 	NMBluezManagerPrivate _priv;
-} NMBluezManager;
+};
 
-typedef struct {
+struct _NMBluezManagerClass {
 	NMDeviceFactoryClass parent;
-} NMBluezManagerClass;
-
-static GType nm_bluez_manager_get_type (void);
+};
 
 G_DEFINE_TYPE (NMBluezManager, nm_bluez_manager, NM_TYPE_DEVICE_FACTORY);
 
@@ -93,313 +205,2583 @@ nm_device_factory_create (GError **error)
 
 /*****************************************************************************/
 
-static void check_bluez_and_try_setup (NMBluezManager *self);
+static NMBluetoothCapabilities
+convert_uuids_to_capabilities (const char *const*strv)
+{
+	NMBluetoothCapabilities capabilities = NM_BT_CAPABILITY_NONE;
+
+	if (strv) {
+		for (; strv[0]; strv++) {
+			gs_free char *s_part1 = NULL;
+			const char *str = strv[0];
+			const char *s;
+
+			s = strchr (str, '-');
+			if (!s)
+				continue;
+
+			s_part1 = g_strndup (str, s - str);
+			switch (g_ascii_strtoull (s_part1, NULL, 16)) {
+			case 0x1103:
+				capabilities |= NM_BT_CAPABILITY_DUN;
+				break;
+			case 0x1116:
+				capabilities |= NM_BT_CAPABILITY_NAP;
+				break;
+			default:
+				break;
+			}
+		}
+	}
+
+	return capabilities;
+}
+
+/*****************************************************************************/
+
+static void _cleanup_for_name_owner (NMBluezManager *self);
+static void _connect_disconnect (NMBluezManager *self,
+                                 BzDBusObj *bzobj,
+                                 const char *reason);
+static gboolean _bzobjs_network_server_is_usable (const BzDBusObj *bzobj,
+                                                  gboolean require_powered);
+static gboolean _bzobjs_is_dead (const BzDBusObj *bzobj);
+static gboolean _bzobjs_device_is_usable (const BzDBusObj *bzobj,
+                                          BzDBusObj **out_adapter_bzobj,
+                                          gboolean *out_create_panu_connection);
+static gboolean _bzobjs_adapter_is_usable_for_device (const BzDBusObj *bzobj);
+static ConnDataHead *_conn_track_find_head (NMBluezManager *self,
+                                            NMBluetoothCapabilities bt_type,
+                                            const char *bdaddr);
+static void _process_change_idle_schedule (NMBluezManager *self,
+                                           BzDBusObj *bzobj);
+static void _network_server_unregister_bridge (NMBluezManager *self,
+                                               BzDBusObj *bzobj,
+                                               const char *reason);
+static gboolean _connect_timeout_wait_connected_cb (gpointer user_data);
 
 /*****************************************************************************/
 
-struct AsyncData {
+static void
+_dbus_call_complete_cb_nop (GObject *source_object,
+                            GAsyncResult *res,
+                            gpointer user_data)
+{
+	/* we don't do anything at all. The only reason to register this
+	 * callback is so that GDBusConnection keeps the cancellable alive
+	 * long enough until the call completes.
+	 *
+	 * Note that this cancellable in turn is registered via
+	 * nm_shutdown_wait_obj_register_*(), to block shutdown until
+	 * we are done. */
+}
+
+/*****************************************************************************/
+
+static void
+_network_server_register_req_data_complete (NetworkServerRegisterReqData *r_req_data,
+                                            GError *error)
+{
+	nm_clear_g_signal_handler (r_req_data->ext_cancellable, &r_req_data->ext_cancelled_id);
+
+	nm_clear_g_cancellable (&r_req_data->int_cancellable);
+
+	if (r_req_data->callback) {
+		gs_free_error GError *error_cancelled = NULL;
+
+		if (g_cancellable_set_error_if_cancelled (r_req_data->ext_cancellable, &error_cancelled))
+			error = error_cancelled;
+
+		r_req_data->callback (error, r_req_data->callback_user_data);
+	}
+
+	g_object_unref (r_req_data->ext_cancellable);
+	nm_g_slice_free (r_req_data);
+}
+
+static void
+_device_connect_req_data_complete (DeviceConnectReqData *c_req_data,
+                                   NMBluezManager *self,
+                                   const char *device_name,
+                                   GError *error)
+{
+	nm_assert ((!!device_name) != (!!error));
+
+	nm_clear_g_signal_handler (c_req_data->ext_cancellable, &c_req_data->ext_cancelled_id);
+
+	nm_clear_g_cancellable (&c_req_data->int_cancellable);
+	nm_clear_g_source (&c_req_data->timeout_id);
+	nm_clear_g_source (&c_req_data->timeout_wait_connect_id);
+
+	if (c_req_data->callback) {
+		gs_free_error GError *error_cancelled = NULL;
+
+		if (g_cancellable_set_error_if_cancelled (c_req_data->ext_cancellable, &error_cancelled)) {
+			error = error_cancelled;
+			device_name = NULL;
+		}
+
+		c_req_data->callback (self, TRUE, device_name, error, c_req_data->callback_user_data);
+	}
+
+	g_object_unref (c_req_data->ext_cancellable);
+	nm_clear_g_free (&c_req_data->device_name);
+	nm_g_slice_free (c_req_data);
+}
+
+/*****************************************************************************/
+
+static BzDBusObj *
+_bz_dbus_obj_new (NMBluezManager *self,
+                  const char *object_path)
+{
+	BzDBusObj *bzobj;
+	gsize l;
+
+	nm_assert (NM_IS_BLUEZ_MANAGER (self));
+
+	l = strlen (object_path) + 1;
+
+	bzobj = g_malloc (sizeof (BzDBusObj) + l);
+	*bzobj = (BzDBusObj) {
+		.object_path                           = bzobj->_object_path_intern,
+		.self                                  = self,
+		.x_network_server.lst                  = C_LIST_INIT (bzobj->x_network_server.lst),
+		.process_change_lst                    = C_LIST_INIT (bzobj->process_change_lst),
+		.x_device_panu_connection_allow_create = TRUE,
+	};
+	memcpy (bzobj->_object_path_intern, object_path, l);
+
+	return bzobj;
+}
+
+static void
+_bz_dbus_obj_free (BzDBusObj *bzobj)
+{
+	nm_assert (bzobj);
+	nm_assert (NM_IS_BLUEZ_MANAGER (bzobj->self));
+	nm_assert (!bzobj->x_network_server.device_br);
+	nm_assert (!bzobj->x_network_server.r_req_data);
+	nm_assert (!bzobj->x_device.c_req_data);
+
+	c_list_unlink_stale (&bzobj->process_change_lst);
+	c_list_unlink_stale (&bzobj->x_network_server.lst);
+	g_free (bzobj->x_network_server.adapter_address);
+	g_free (bzobj->d_adapter.address);
+	g_free (bzobj->d_network.interface);
+	g_free (bzobj->d_device.address);
+	g_free (bzobj->d_device.name);
+	g_free (bzobj->d_device.adapter);
+	g_free (bzobj);
+}
+
+/*****************************************************************************/
+
+static const char *
+_bzobj_to_string (const BzDBusObj *bzobj, char *buf, gsize len)
+{
+	char *buf0 = buf;
+	const char *prefix = "";
+	gboolean device_is_usable;
+	gboolean create_panu_connection = FALSE;
+	gboolean network_server_is_usable;
+	char sbuf_cap[100];
+
+	if (len > 0)
+		buf[0] = '\0';
+
+	if (bzobj->d_has_adapter_iface) {
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+		nm_utils_strbuf_append_str (&buf, &len, "Adapter1 {");
+		if (bzobj->d_adapter.address) {
+			nm_utils_strbuf_append (&buf, &len, " d.address: \"%s\"", bzobj->d_adapter.address);
+			if (bzobj->d_adapter_powered)
+				nm_utils_strbuf_append_str (&buf, &len, ",");
+		}
+		if (bzobj->d_adapter_powered)
+			nm_utils_strbuf_append (&buf, &len, " d.powered: 1");
+		nm_utils_strbuf_append_str (&buf, &len, " }");
+	}
+
+	if (bzobj->d_has_device_iface) {
+		const char *prefix1 = "";
+
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+		nm_utils_strbuf_append_str (&buf, &len, "Device1 {");
+		if (bzobj->d_device.address) {
+			nm_utils_strbuf_append (&buf, &len, "%s d.address: \"%s\"", prefix1, bzobj->d_device.address);
+			prefix1 = ",";
+		}
+		if (bzobj->d_device.name) {
+			nm_utils_strbuf_append (&buf, &len, "%s d.name: \"%s\"", prefix1, bzobj->d_device.name);
+			prefix1 = ",";
+		}
+		if (bzobj->d_device.adapter) {
+			nm_utils_strbuf_append (&buf, &len, "%s d.adapter: \"%s\"", prefix1, bzobj->d_device.adapter);
+			prefix1 = ",";
+		}
+		if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) {
+			nm_utils_strbuf_append (&buf, &len, "%s d.capabilities: \"%s\"",
+			                        prefix1,
+			                        nm_bluetooth_capability_to_string (bzobj->d_device_capabilities, sbuf_cap, sizeof (sbuf_cap)));
+			prefix1 = ",";
+		}
+		if (bzobj->d_device_connected) {
+			nm_utils_strbuf_append (&buf, &len, "%s d.connected: 1", prefix1);
+			prefix1 = ",";
+		}
+		if (bzobj->d_device_paired) {
+			nm_utils_strbuf_append (&buf, &len, "%s d.paired: 1", prefix1);
+			prefix1 = ",";
+		}
+		nm_utils_strbuf_append_str (&buf, &len, " }");
+	}
+
+	network_server_is_usable = _bzobjs_network_server_is_usable (bzobj, TRUE);
+
+	if (   bzobj->d_has_network_server_iface
+	    || network_server_is_usable != (!c_list_is_empty (&bzobj->x_network_server.lst))
+	    || !c_list_is_empty (&bzobj->x_network_server.lst)
+	    || !nm_streq0 (bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, bzobj->x_network_server.adapter_address)
+	    || bzobj->x_network_server.device_br
+	    || bzobj->x_network_server.r_req_data) {
+
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+
+		nm_utils_strbuf_append (&buf, &len, "NetworkServer1 { ");
+
+		if (!bzobj->d_has_network_server_iface)
+			nm_utils_strbuf_append (&buf, &len, " has-d-iface: 0, ");
+
+		if (network_server_is_usable != (!c_list_is_empty (&bzobj->x_network_server.lst)))
+			nm_utils_strbuf_append (&buf, &len, "usable: %d, used: %d", !!network_server_is_usable, !network_server_is_usable);
+		else if (network_server_is_usable)
+			nm_utils_strbuf_append (&buf, &len, "used: 1");
+		else
+			nm_utils_strbuf_append (&buf, &len, "usable: 0");
+
+		if (!nm_streq0 (bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL, bzobj->x_network_server.adapter_address)) {
+			if (bzobj->x_network_server.adapter_address)
+				nm_utils_strbuf_append (&buf, &len, ", adapter-address: \"%s\"", bzobj->x_network_server.adapter_address);
+			else
+				nm_utils_strbuf_append (&buf, &len, ", adapter-address: <NULL>");
+		}
+
+		if (bzobj->x_network_server.device_br)
+			nm_utils_strbuf_append (&buf, &len, ", bridge-device: 1");
+
+		if (bzobj->x_network_server.r_req_data)
+			nm_utils_strbuf_append (&buf, &len, ", register-in-progress: 1");
+
+		nm_utils_strbuf_append_str (&buf, &len, " }");
+	}
+
+	device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, &create_panu_connection);
+
+	if (   bzobj->d_has_network_iface
+	    || bzobj->d_network.interface
+	    || bzobj->d_network_connected
+	    || create_panu_connection
+	    || bzobj->x_device.panu_connection
+	    || device_is_usable != bzobj->x_device_is_usable
+	    || bzobj->x_device.device_bt
+	    || bzobj->x_device_connect_bt_type != NM_BT_CAPABILITY_NONE
+	    || bzobj->x_device.connect_dun_context
+	    || bzobj->x_device.c_req_data
+	    || bzobj->x_device_is_connected != bzobj->d_network_connected) {
+
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+		nm_utils_strbuf_append_str (&buf, &len, "Network1 {");
+		if (bzobj->d_network.interface)
+			nm_utils_strbuf_append (&buf, &len, " d.interface: \"%s\", ", bzobj->d_network.interface);
+		if (bzobj->d_network_connected)
+			nm_utils_strbuf_append (&buf, &len, " d.connected: %d, ", !!bzobj->d_network_connected);
+		if (!bzobj->d_has_network_iface)
+			nm_utils_strbuf_append (&buf, &len, " has-d-iface: 0, ");
+		if (device_is_usable != bzobj->x_device_is_usable)
+			nm_utils_strbuf_append (&buf, &len, " usable: %d, used: %d", !!device_is_usable, !device_is_usable);
+		else if (device_is_usable)
+			nm_utils_strbuf_append (&buf, &len, " used: 1");
+		else
+			nm_utils_strbuf_append (&buf, &len, " usable: 0");
+
+		if (create_panu_connection)
+			nm_utils_strbuf_append (&buf, &len, ", create-panu-connection: 1");
+
+		if (bzobj->x_device.panu_connection)
+			nm_utils_strbuf_append (&buf, &len, ", has-panu-connection: 1");
+
+		if (bzobj->x_device.device_bt)
+			nm_utils_strbuf_append (&buf, &len, ", has-device: 1");
+
+		if (   bzobj->x_device_connect_bt_type != NM_BT_CAPABILITY_NONE
+		    || bzobj->x_device.connect_dun_context) {
+			nm_utils_strbuf_append (&buf, &len, ", connect: %s%s",
+			                        nm_bluetooth_capability_to_string (bzobj->x_device_connect_bt_type, sbuf_cap, sizeof (sbuf_cap)),
+			                        bzobj->x_device.connect_dun_context ? ",with-dun-context" : "");
+		}
+
+		if (bzobj->x_device.c_req_data)
+			nm_utils_strbuf_append (&buf, &len, ", connecting: 1");
+
+		if (bzobj->x_device_is_connected != bzobj->d_network_connected)
+			nm_utils_strbuf_append (&buf, &len, ", connected: %d", !!bzobj->x_device_is_connected);
+
+		nm_utils_strbuf_append_str (&buf, &len, " }");
+	}
+
+	if (_bzobjs_is_dead (bzobj)) {
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+		nm_utils_strbuf_append_str (&buf, &len, "dead: 1");
+	}
+
+	if (!c_list_is_empty (&bzobj->process_change_lst)) {
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+		nm_utils_strbuf_append (&buf, &len, "change-pending-on-idle: 1");
+	}
+
+	if (_bzobjs_adapter_is_usable_for_device (bzobj) != bzobj->was_usable_adapter_for_device_before) {
+		nm_utils_strbuf_append_str (&buf, &len, prefix);
+		prefix = ", ";
+		nm_utils_strbuf_append (&buf, &len, "change-usable-adapter-for-device: 1");
+	}
+
+	return buf0;
+}
+
+#define _LOG_bzobj(bzobj, context) \
+	G_STMT_START { \
+		const BzDBusObj *const _bzobj = (bzobj); \
+		char _buf[500]; \
+		\
+		_LOGT ("change %-21s %s : { %s }", \
+		       (context), \
+		       _bzobj->object_path, \
+		       _bzobj_to_string (_bzobj, _buf, sizeof (_buf))); \
+	} G_STMT_END
+
+static gboolean
+_bzobjs_is_dead (const BzDBusObj *bzobj)
+{
+	return    !bzobj->d_has_adapter_iface
+	       && !bzobj->d_has_device_iface
+	       && !bzobj->d_has_network_iface
+	       && !bzobj->d_has_network_server_iface
+	       && c_list_is_empty (&bzobj->process_change_lst);
+}
+
+static BzDBusObj *
+_bzobjs_get (NMBluezManager *self, const char *object_path)
+{
+	return g_hash_table_lookup (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->bzobjs, &object_path);
+}
+
+static BzDBusObj *
+_bzobjs_add (NMBluezManager *self,
+             const char *object_path)
+{
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	BzDBusObj *bzobj;
+
+	bzobj = _bz_dbus_obj_new (self, object_path);
+	if (!g_hash_table_add (priv->bzobjs, bzobj))
+		nm_assert_not_reached ();
+	return bzobj;
+}
+
+static void
+_bzobjs_del (BzDBusObj *bzobj)
+{
+	nm_assert (bzobj);
+	nm_assert (bzobj == _bzobjs_get (bzobj->self, bzobj->object_path));
+
+	if (!g_hash_table_remove (NM_BLUEZ_MANAGER_GET_PRIVATE (bzobj->self)->bzobjs, bzobj))
+		nm_assert_not_reached ();
+}
+
+static void
+_bzobjs_del_if_dead (BzDBusObj *bzobj)
+{
+	if (_bzobjs_is_dead (bzobj))
+		_bzobjs_del (bzobj);
+}
+
+static BzDBusObj *
+_bzobjs_init (NMBluezManager *self, BzDBusObj **inout, const char *object_path)
+{
+	nm_assert (NM_IS_BLUEZ_MANAGER (self));
+	nm_assert (object_path);
+	nm_assert (inout);
+
+	if (!*inout) {
+		*inout = _bzobjs_get (self, object_path);
+		if (!*inout)
+			*inout = _bzobjs_add (self, object_path);
+	}
+
+	nm_assert (nm_streq ((*inout)->object_path, object_path));
+	nm_assert (*inout == _bzobjs_get (self, object_path));
+	return *inout;
+}
+
+static gboolean
+_bzobjs_adapter_is_usable_for_device (const BzDBusObj *bzobj)
+{
+	return    bzobj->d_has_adapter_iface
+	       && bzobj->d_adapter.address
+	       && bzobj->d_adapter_powered;
+}
+
+static gboolean
+_bzobjs_device_is_usable (const BzDBusObj *bzobj,
+                          BzDBusObj **out_adapter_bzobj,
+                          gboolean *out_create_panu_connection)
+{
 	NMBluezManager *self;
-	GCancellable *async_cancellable;
-};
+	NMBluezManagerPrivate *priv;
+	gboolean usable_dun = FALSE;
+	gboolean usable_nap = FALSE;
+	BzDBusObj *bzobj_adapter;
+	gboolean create_panu_connection = FALSE;
+
+	if (   !bzobj->d_has_device_iface
+	    || !NM_FLAGS_ANY ((NMBluetoothCapabilities) bzobj->d_device_capabilities, _NM_BT_CAPABILITY_SUPPORTED)
+	    || !bzobj->d_device.name
+	    || !bzobj->d_device.address
+	    || !bzobj->d_device_paired
+	    || !bzobj->d_device.adapter)
+		goto out_unusable;
+
+	self = bzobj->self;
+
+	priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+
+	if (!priv->settings_registered)
+		goto out_unusable;
+
+	bzobj_adapter = _bzobjs_get (self, bzobj->d_device.adapter);
+	if (   !bzobj_adapter
+	    || !_bzobjs_adapter_is_usable_for_device (bzobj_adapter))
+		goto out_unusable;
+
+#if WITH_BLUEZ5_DUN
+	if (NM_FLAGS_HAS (bzobj->d_device_capabilities, NM_BT_CAPABILITY_DUN)) {
+		if (_conn_track_find_head (self, NM_BT_CAPABILITY_DUN, bzobj->d_device.address))
+			usable_dun = TRUE;
+	}
+#endif
+
+	if (NM_FLAGS_HAS (bzobj->d_device_capabilities, NM_BT_CAPABILITY_NAP)) {
+		if (!bzobj->d_has_network_iface)
+			usable_nap = FALSE;
+		else if (_conn_track_find_head (self, NM_BT_CAPABILITY_NAP, bzobj->d_device.address))
+			usable_nap = TRUE;
+		else if (bzobj->x_device_panu_connection_allow_create) {
+			/* We didn't yet try to create a connection. Presume we are going to create
+			 * it when the time comes... */
+			usable_nap = TRUE;
+			create_panu_connection = TRUE;
+		}
+	}
+
+	if (   !usable_dun
+	    && !usable_nap) {
+		if (   bzobj->x_device.device_bt
+		    && nm_device_get_state (NM_DEVICE (bzobj->x_device.device_bt)) > NM_DEVICE_STATE_DISCONNECTED) {
+			/* The device is still activated... the absence of a profile does not
+			 * render it unusable (yet). But since there is no more profile, the
+			 * device is probably about to disconnect. */
+		} else
+			goto out_unusable;
+	}
+
+	NM_SET_OUT (out_create_panu_connection, create_panu_connection);
+	NM_SET_OUT (out_adapter_bzobj, bzobj_adapter);
+	return TRUE;
 
-static struct AsyncData *
-async_data_pack (NMBluezManager *self)
+out_unusable:
+	NM_SET_OUT (out_create_panu_connection, FALSE);
+	NM_SET_OUT (out_adapter_bzobj, NULL);
+	return FALSE;
+}
+
+static gboolean
+_bzobjs_device_is_connected (const BzDBusObj *bzobj)
 {
-	struct AsyncData *data = g_new (struct AsyncData, 1);
+	nm_assert (_bzobjs_device_is_usable (bzobj, NULL, NULL));
+
+	if (   !bzobj->d_has_device_iface
+	    || !bzobj->d_device_connected)
+		return FALSE;
+
+	if (   bzobj->d_has_network_iface
+	    && bzobj->d_network_connected)
+		return TRUE;
+	if (bzobj->x_device.connect_dun_context) {
+		/* As long as we have a dun-context, we consider it connected.
+		 *
+		 * We require NMDeviceBt to try to connect to the modem, and if that fails,
+		 * it will disconnect. */
+		return TRUE;
+	}
+	return FALSE;
+}
 
-	data->self = self;
-	data->async_cancellable = g_object_ref (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->async_cancellable);
-	return data;
+static gboolean
+_bzobjs_network_server_is_usable (const BzDBusObj *bzobj,
+                                  gboolean require_powered)
+{
+	return    bzobj->d_has_network_server_iface
+	       && bzobj->d_has_adapter_iface
+	       && bzobj->d_adapter.address
+	       && (   !require_powered
+	           || bzobj->d_adapter_powered);
 }
 
-static NMBluezManager *
-async_data_unpack (struct AsyncData *async_data)
+/*****************************************************************************/
+
+static ConnDataHead *
+_conn_data_head_new (NMBluetoothCapabilities bt_type,
+                     const char *bdaddr)
 {
-	NMBluezManager *self = g_cancellable_is_cancelled (async_data->async_cancellable)
-	                       ? NULL : async_data->self;
+	ConnDataHead *cdata_hd;
+	gsize l;
 
-	g_object_unref (async_data->async_cancellable);
-	g_free (async_data);
-	return self;
+	nm_assert (NM_IN_SET (bt_type, NM_BT_CAPABILITY_DUN,
+	                               NM_BT_CAPABILITY_NAP));
+	nm_assert (bdaddr);
+
+	l = strlen (bdaddr) + 1;
+	cdata_hd = g_malloc (sizeof (ConnDataHead) + l);
+	*cdata_hd = (ConnDataHead) {
+		.bdaddr   = cdata_hd->bdaddr_data,
+		.lst_head = C_LIST_INIT (cdata_hd->lst_head),
+		.bt_type  = bt_type,
+	};
+	memcpy (cdata_hd->bdaddr_data, bdaddr, l);
+
+	nm_assert (cdata_hd->bt_type == bt_type);
+
+	return cdata_hd;
+}
+
+static guint
+_conn_data_head_hash (gconstpointer ptr)
+{
+	const ConnDataHead *cdata_hd = ptr;
+	NMHashState h;
+
+	nm_hash_init (&h, 520317467u);
+	nm_hash_update_val (&h, (NMBluetoothCapabilities) cdata_hd->bt_type);
+	nm_hash_update_str (&h, cdata_hd->bdaddr);
+	return nm_hash_complete (&h);
+}
+
+static gboolean
+_conn_data_head_equal (gconstpointer a, gconstpointer b)
+{
+	const ConnDataHead *cdata_hd_a = a;
+	const ConnDataHead *cdata_hd_b = b;
+
+	return     cdata_hd_a->bt_type == cdata_hd_b->bt_type
+	        && nm_streq (cdata_hd_a->bdaddr, cdata_hd_b->bdaddr);
+}
+
+static ConnDataHead *
+_conn_track_find_head (NMBluezManager *self,
+                       NMBluetoothCapabilities bt_type,
+                       const char *bdaddr)
+{
+	ConnDataHead cdata_hd = {
+		.bt_type = bt_type,
+		.bdaddr  = bdaddr,
+	};
+
+	return g_hash_table_lookup (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->conn_data_heads, &cdata_hd);
+}
+
+static ConnDataElem *
+_conn_track_find_elem (NMBluezManager *self,
+                       NMSettingsConnection *sett_conn)
+{
+	G_STATIC_ASSERT (G_STRUCT_OFFSET (ConnDataElem, sett_conn) == 0);
+
+	return g_hash_table_lookup (NM_BLUEZ_MANAGER_GET_PRIVATE (self)->conn_data_elems, &sett_conn);
+}
+
+static gboolean
+_conn_track_is_relevant_connection (NMConnection *connection,
+                                    NMBluetoothCapabilities *out_bt_type,
+                                    const char **out_bdaddr)
+{
+	NMSettingBluetooth *s_bt;
+	NMBluetoothCapabilities bt_type;
+	const char *bdaddr;
+	const char *b_type;
+
+	s_bt = nm_connection_get_setting_bluetooth (connection);
+	if (!s_bt)
+		return FALSE;
+
+	if (!nm_connection_is_type (connection, NM_SETTING_BLUETOOTH_SETTING_NAME))
+		return FALSE;
+
+	bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt);
+	if (!bdaddr)
+		return FALSE;
+
+	b_type = nm_setting_bluetooth_get_connection_type (s_bt);
+
+	if (nm_streq (b_type, NM_SETTING_BLUETOOTH_TYPE_DUN))
+		bt_type = NM_BT_CAPABILITY_DUN;
+	else if (nm_streq (b_type, NM_SETTING_BLUETOOTH_TYPE_PANU))
+		bt_type = NM_BT_CAPABILITY_NAP;
+	else
+		return FALSE;
+
+	NM_SET_OUT (out_bt_type, bt_type);
+	NM_SET_OUT (out_bdaddr, bdaddr);
+	return TRUE;
+}
+
+static gboolean
+_conn_track_is_relevant_sett_conn (NMSettingsConnection *sett_conn,
+                                   NMBluetoothCapabilities *out_bt_type,
+                                   const char **out_bdaddr)
+{
+	NMConnection *connection;
+
+	connection = nm_settings_connection_get_connection (sett_conn);
+	if (!connection)
+		return FALSE;
+
+	return _conn_track_is_relevant_connection (connection, out_bt_type, out_bdaddr);
+}
+
+static gboolean
+_conn_track_is_relevant_for_sett_conn (NMSettingsConnection *sett_conn,
+                                       NMBluetoothCapabilities bt_type,
+                                       const char *bdaddr)
+{
+	NMBluetoothCapabilities x_bt_type;
+	const char *x_bdaddr;
+
+	return    bdaddr
+	       && _conn_track_is_relevant_sett_conn (sett_conn, &x_bt_type, &x_bdaddr)
+	       && x_bt_type == bt_type
+	       && nm_streq (x_bdaddr, bdaddr);
+}
+
+static void
+_conn_track_schedule_notify (NMBluezManager *self,
+                             NMBluetoothCapabilities bt_type,
+                             const char *bdaddr)
+{
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	GHashTableIter iter;
+	BzDBusObj *bzobj;
+
+	g_hash_table_iter_init (&iter, priv->bzobjs);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &bzobj, NULL)) {
+		gboolean device_is_usable;
+
+		device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, NULL);
+		if (bzobj->x_device_is_usable != device_is_usable)
+			_process_change_idle_schedule (self, bzobj);
+	}
 }
 
-/**
- * Cancel any current attempt to detect the version and cleanup
- * the related fields.
- **/
 static void
-cleanup_checking (NMBluezManager *self, gboolean do_unwatch_name)
+_conn_track_update (NMBluezManager *self,
+                    NMSettingsConnection *sett_conn,
+                    gboolean track,
+                    gboolean *out_changed,
+                    gboolean *out_changed_usable,
+                    ConnDataElem **out_conn_data_elem)
 {
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	ConnDataHead *cdata_hd;
+	ConnDataElem *cdata_el;
+	ConnDataElem *cdata_el_remove = NULL;
+	NMBluetoothCapabilities bt_type;
+	const char *bdaddr;
+	gboolean changed = FALSE;
+	gboolean changed_usable = FALSE;
+	char sbuf_cap[100];
+
+	nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn));
+
+	cdata_el = _conn_track_find_elem (self, sett_conn);
+
+	if (track)
+		track = _conn_track_is_relevant_sett_conn (sett_conn, &bt_type, &bdaddr);
 
-	nm_clear_g_cancellable (&priv->async_cancellable);
+	if (!track) {
+		cdata_el_remove = g_steal_pointer (&cdata_el);
+		goto out_remove;
+	}
+
+	if (cdata_el) {
+		cdata_hd = cdata_el->cdata_hd;
+		if (   cdata_hd->bt_type != bt_type
+	        || !nm_streq (cdata_hd->bdaddr, bdaddr))
+			cdata_el_remove = g_steal_pointer (&cdata_el);
+	}
+
+	if (!cdata_el) {
+		_LOGT ("connecton: track for %s, %s: %s (%s)",
+		       nm_bluetooth_capability_to_string (bt_type, sbuf_cap, sizeof (sbuf_cap)),
+		       bdaddr,
+		       nm_settings_connection_get_uuid (sett_conn),
+		       nm_settings_connection_get_id (sett_conn));
+		changed = TRUE;
+		cdata_hd = _conn_track_find_head (self, bt_type, bdaddr);
+		if (!cdata_hd) {
+			changed_usable = TRUE;
+			cdata_hd = _conn_data_head_new (bt_type, bdaddr);
+			if (!g_hash_table_add (priv->conn_data_heads, cdata_hd))
+				nm_assert_not_reached ();
+			_conn_track_schedule_notify (self, bt_type, bdaddr);
+		}
+		cdata_el = g_slice_new (ConnDataElem);
+		cdata_el->sett_conn = sett_conn;
+		cdata_el->cdata_hd = cdata_hd;
+		c_list_link_tail (&cdata_hd->lst_head, &cdata_el->lst);
+		if (!g_hash_table_add (priv->conn_data_elems, cdata_el))
+			nm_assert_not_reached ();
+	}
 
-	g_clear_object (&priv->introspect_proxy);
+out_remove:
+	if (cdata_el_remove) {
+		GHashTableIter iter;
+		BzDBusObj *bzobj;
+
+		_LOGT ("connecton: untrack for %s, %s: %s (%s)",
+		       nm_bluetooth_capability_to_string (cdata_el_remove->cdata_hd->bt_type, sbuf_cap, sizeof (sbuf_cap)),
+		       cdata_el_remove->cdata_hd->bdaddr,
+		       nm_settings_connection_get_uuid (sett_conn),
+		       nm_settings_connection_get_id (sett_conn));
+
+		g_hash_table_iter_init (&iter, priv->bzobjs);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &bzobj, NULL)) {
+			if (bzobj->x_device.panu_connection == sett_conn)
+				bzobj->x_device.panu_connection = NULL;
+		}
 
-	if (do_unwatch_name && priv->watch_name_id) {
-		g_bus_unwatch_name (priv->watch_name_id);
-		priv->watch_name_id = 0;
+		changed = TRUE;
+		cdata_hd = cdata_el_remove->cdata_hd;
+		c_list_unlink_stale (&cdata_el_remove->lst);
+		if (!g_hash_table_remove (priv->conn_data_elems, cdata_el_remove))
+			nm_assert_not_reached ();
+		if (c_list_is_empty (&cdata_hd->lst_head)) {
+			changed_usable = TRUE;
+			_conn_track_schedule_notify (self, cdata_hd->bt_type, cdata_hd->bdaddr);
+			if (!g_hash_table_remove (priv->conn_data_heads, cdata_hd))
+				nm_assert_not_reached ();
+		}
 	}
+
+	NM_SET_OUT (out_changed, changed);
+	NM_SET_OUT (out_changed_usable, changed_usable);
+	NM_SET_OUT (out_conn_data_elem, cdata_el);
+}
+
+/*****************************************************************************/
+
+static void
+cp_connection_added (NMSettings *settings,
+                     NMSettingsConnection *sett_conn,
+                     NMBluezManager *self)
+{
+	_conn_track_update (self, sett_conn, TRUE, NULL, NULL, NULL);
 }
 
 static void
-manager_bdaddr_added_cb (GObject *manager,
-                         NMBluezDevice *bt_device,
-                         const char *bdaddr,
-                         const char *name,
-                         const char *object_path,
-                         guint32 capabilities,
-                         gpointer user_data)
+cp_connection_updated (NMSettings *settings,
+                       NMSettingsConnection *sett_conn,
+                       guint update_reason_u,
+                       NMBluezManager *self)
 {
-	NMBluezManager *self = NM_BLUEZ_MANAGER (user_data);
-	NMDevice *device;
-	gboolean has_dun = (capabilities & NM_BT_CAPABILITY_DUN);
-	gboolean has_nap = (capabilities & NM_BT_CAPABILITY_NAP);
+	_conn_track_update (self, sett_conn, TRUE, NULL, NULL, NULL);
+}
 
-	g_return_if_fail (bdaddr != NULL);
-	g_return_if_fail (name != NULL);
-	g_return_if_fail (object_path != NULL);
-	g_return_if_fail (capabilities != NM_BT_CAPABILITY_NONE);
-	g_return_if_fail (NM_IS_BLUEZ_DEVICE (bt_device));
+static void
+cp_connection_removed (NMSettings *settings,
+                       NMSettingsConnection *sett_conn,
+                       NMBluezManager *self)
+{
+	_conn_track_update (self, sett_conn, FALSE, NULL, NULL, NULL);
+}
+
+/*****************************************************************************/
 
-	device = nm_device_bt_new (bt_device, object_path, bdaddr, name, capabilities);
-	if (!device)
+static NMBluezManager *
+_network_server_get_bluez_manager (const NMBtVTableNetworkServer *vtable_network_server)
+{
+	NMBluezManager *self;
+
+	self = (NMBluezManager *) (((char *) vtable_network_server) - G_STRUCT_OFFSET (NMBluezManager, _priv.vtable_network_server));
+
+	g_return_val_if_fail (NM_IS_BLUEZ_MANAGER (self), NULL);
+
+	return self;
+}
+
+static BzDBusObj *
+_network_server_find_has_device (NMBluezManagerPrivate *priv,
+                                 NMDevice *device)
+{
+	BzDBusObj *bzobj;
+
+	c_list_for_each_entry (bzobj, &priv->network_server_lst_head, x_network_server.lst) {
+		if (bzobj->x_network_server.device_br == device)
+			return bzobj;
+	}
+	return NULL;
+}
+
+static BzDBusObj *
+_network_server_find_available (NMBluezManagerPrivate *priv,
+                                const char *addr,
+                                NMDevice *device_accept_busy)
+{
+	BzDBusObj *bzobj;
+
+	c_list_for_each_entry (bzobj, &priv->network_server_lst_head, x_network_server.lst) {
+		if (bzobj->x_network_server.device_br) {
+			if (bzobj->x_network_server.device_br != device_accept_busy)
+				continue;
+		}
+		if (   addr
+		    && !nm_streq (addr, bzobj->d_adapter.address))
+			continue;
+		nm_assert (!bzobj->x_network_server.r_req_data);
+		return bzobj;
+	}
+	return NULL;
+}
+
+static gboolean
+_network_server_vt_is_available (const NMBtVTableNetworkServer *vtable,
+                                 const char *addr,
+                                 NMDevice *device_accept_busy)
+{
+	NMBluezManager *self = _network_server_get_bluez_manager (vtable);
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+
+	return !!_network_server_find_available (priv, addr, device_accept_busy);
+}
+
+static void
+_network_server_register_cb (GObject *source_object,
+                             GAsyncResult *res,
+                             gpointer user_data)
+{
+	gs_unref_variant GVariant *ret = NULL;
+	gs_free_error GError *error = NULL;
+	BzDBusObj *bzobj;
+
+	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
+	if (   !ret
+	    && nm_utils_error_is_cancelled (error, FALSE))
 		return;
 
-	_LOGI ("BT device %s (%s) added (%s%s%s)",
-	       name,
-	       bdaddr,
-	       has_dun ? "DUN" : "",
-	       has_dun && has_nap ? " " : "",
-	       has_nap ? "NAP" : "");
-	g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device);
-	g_object_unref (device);
+	bzobj = user_data;
+
+	if (!ret) {
+		_LOGT ("NAP: [%s]: registering failed: %s", bzobj->object_path, error->message);
+	} else
+		_LOGT ("NAP: [%s]: registration successful", bzobj->object_path);
+
+	g_clear_object (&bzobj->x_network_server.r_req_data->int_cancellable);
+	_network_server_register_req_data_complete (g_steal_pointer (&bzobj->x_network_server.r_req_data), error);
+}
+
+static void
+_network_server_register_cancelled_cb (GCancellable *cancellable,
+                                       BzDBusObj *bzobj)
+{
+	_network_server_unregister_bridge (bzobj->self, bzobj, "registration cancelled");
+}
+
+static gboolean
+_network_server_vt_register_bridge (const NMBtVTableNetworkServer *vtable,
+                                    const char *addr,
+                                    NMDevice *device,
+                                    GCancellable *cancellable,
+                                    NMBtVTableRegisterCallback callback,
+                                    gpointer callback_user_data,
+                                    GError **error)
+{
+	NMBluezManager *self = _network_server_get_bluez_manager (vtable);
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	NetworkServerRegisterReqData *r_req_data;
+	BzDBusObj *bzobj;
+	const char *ifname;
+
+	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
+	g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), FALSE);
+
+	nm_assert (!g_cancellable_is_cancelled (cancellable));
+	nm_assert (!_network_server_find_has_device (priv, device));
+
+	ifname = nm_device_get_iface (device);
+	g_return_val_if_fail (ifname, FALSE);
+
+	g_return_val_if_fail (ifname, FALSE);
+
+	bzobj = _network_server_find_available (priv, addr, NULL);
+	if (!bzobj) {
+		/* The device checked that a network server is available, before
+		 * starting the activation, but for some reason it no longer is.
+		 * Indicate that the activation should not proceed. */
+		if (addr) {
+			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+			                    "adapter %s is not available for %s",
+			                    addr, ifname);
+		} else {
+			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+			                    "no adapter available for %s",
+			                    ifname);
+		}
+		return FALSE;
+	}
+
+	_LOGD ("NAP: [%s]: registering \"%s\" on adapter %s",
+	       bzobj->object_path,
+	       ifname,
+	       bzobj->d_adapter.address);
+
+	r_req_data = g_slice_new (NetworkServerRegisterReqData);
+	*r_req_data = (NetworkServerRegisterReqData) {
+		.int_cancellable     = g_cancellable_new (),
+		.ext_cancellable     = g_object_ref (cancellable),
+		.callback            = callback,
+		.callback_user_data  = callback_user_data,
+		.ext_cancelled_id    = g_signal_connect (cancellable,
+		                                         "cancelled",
+		                                         G_CALLBACK (_network_server_register_cancelled_cb),
+		                                         bzobj),
+	};
+
+	bzobj->x_network_server.device_br = g_object_ref (device);
+	bzobj->x_network_server.r_req_data = r_req_data;
+
+	g_dbus_connection_call (priv->dbus_connection,
+	                        priv->name_owner,
+	                        bzobj->object_path,
+	                        NM_BLUEZ5_NETWORK_SERVER_INTERFACE,
+	                        "Register",
+	                        g_variant_new ("(ss)",
+	                                       BLUETOOTH_CONNECT_NAP,
+	                                       ifname),
+	                        NULL,
+	                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                        -1,
+	                        bzobj->x_network_server.r_req_data->int_cancellable,
+	                        _network_server_register_cb,
+	                        bzobj);
+	return TRUE;
+}
+
+static void
+_network_server_unregister_bridge_complete_on_idle_cb (gpointer user_data,
+                                                       GCancellable *cancellable)
+{
+	gs_free_error GError *error = NULL;
+	gs_free char *reason  = NULL;
+	NetworkServerRegisterReqData *r_req_data;
+
+	nm_utils_user_data_unpack (user_data, &r_req_data, &reason);
+
+	nm_utils_error_set (&error, NM_UTILS_ERROR_UNKNOWN,
+	                    "registration was aborted due to %s",
+	                    reason);
+	_network_server_register_req_data_complete (r_req_data, error);
 }
 
 static void
-manager_network_server_added_cb (GObject *manager,
-                                 gpointer user_data)
+_network_server_unregister_bridge (NMBluezManager *self,
+                                   BzDBusObj *bzobj,
+                                   const char *reason)
+{
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	_nm_unused gs_unref_object NMDevice *device = NULL;
+	NetworkServerRegisterReqData *r_req_data;
+
+	nm_assert (NM_IS_DEVICE (bzobj->x_network_server.device_br));
+
+	_LOGD ("NAP: [%s]: unregistering \"%s\" (%s)",
+	       bzobj->object_path,
+	       nm_device_get_iface (bzobj->x_network_server.device_br),
+	       reason);
+
+	device = g_steal_pointer (&bzobj->x_network_server.device_br);
+
+	r_req_data = g_steal_pointer (&bzobj->x_network_server.r_req_data);
+
+	if (priv->name_owner) {
+		gs_unref_object GCancellable *cancellable = NULL;
+
+		cancellable = g_cancellable_new ();
+
+		nm_shutdown_wait_obj_register_cancellable_full (cancellable,
+		                                                g_strdup_printf ("bt-unregister-nap[%s]", bzobj->object_path),
+		                                                TRUE);
+
+		g_dbus_connection_call (priv->dbus_connection,
+		                        priv->name_owner,
+		                        bzobj->object_path,
+		                        NM_BLUEZ5_NETWORK_SERVER_INTERFACE,
+		                        "Unregister",
+		                        g_variant_new ("(s)", BLUETOOTH_CONNECT_NAP),
+		                        NULL,
+		                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+		                        -1,
+		                        cancellable,
+		                        _dbus_call_complete_cb_nop,
+		                        NULL);
+	}
+
+	if (r_req_data) {
+		nm_clear_g_cancellable (&r_req_data->int_cancellable);
+		nm_utils_invoke_on_idle (_network_server_unregister_bridge_complete_on_idle_cb,
+		                         nm_utils_user_data_pack (r_req_data, g_strdup (reason)),
+		                         r_req_data->ext_cancellable);
+	}
+
+	_nm_device_bridge_notify_unregister_bt_nap (device, reason);
+}
+
+static gboolean
+_network_server_vt_unregister_bridge (const NMBtVTableNetworkServer *vtable,
+                                      NMDevice *device)
 {
-	nm_device_factory_emit_component_added (NM_DEVICE_FACTORY (user_data), NULL);
+	NMBluezManager *self = _network_server_get_bluez_manager (vtable);
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	BzDBusObj *bzobj;
+
+	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
+
+	bzobj = _network_server_find_has_device (priv, device);
+	if (bzobj)
+		_network_server_unregister_bridge (self, bzobj, "disconnecting");
+
+	return TRUE;
 }
 
 static void
-setup_version_number (NMBluezManager *self, int bluez_version)
+_network_server_process_change (BzDBusObj *bzobj,
+                                gboolean *out_emit_device_availability_changed)
 {
+	NMBluezManager *self = bzobj->self;
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	gboolean network_server_is_usable;
+	gboolean emit_device_availability_changed = FALSE;
+
+	network_server_is_usable = _bzobjs_network_server_is_usable (bzobj, TRUE);
 
-	g_return_if_fail (!priv->bluez_version);
+	if (!network_server_is_usable) {
 
-	_LOGI ("use BlueZ version %d", bluez_version);
+		if (!c_list_is_empty (&bzobj->x_network_server.lst)) {
+			emit_device_availability_changed = TRUE;
+			c_list_unlink (&bzobj->x_network_server.lst);
+		}
 
-	priv->bluez_version = bluez_version;
+		nm_clear_g_free (&bzobj->x_network_server.adapter_address);
+
+		if (bzobj->x_network_server.device_br) {
+			_network_server_unregister_bridge (self,
+			                                   bzobj,
+			                                     _bzobjs_network_server_is_usable (bzobj, FALSE)
+			                                   ? "adapter disabled"
+			                                   : "adapter disappeared");
+		}
+
+	} else {
+
+		if (!nm_streq0 (bzobj->x_network_server.adapter_address, bzobj->d_adapter.address)) {
+			emit_device_availability_changed = TRUE;
+			g_free (bzobj->x_network_server.adapter_address);
+			bzobj->x_network_server.adapter_address = g_strdup (bzobj->d_adapter.address);
+		}
 
-	/* Just detected the version. Cleanup the ongoing checking/detection. */
-	cleanup_checking (self, TRUE);
+		if (c_list_is_empty (&bzobj->x_network_server.lst)) {
+			emit_device_availability_changed = TRUE;
+			c_list_link_tail (&priv->network_server_lst_head, &bzobj->x_network_server.lst);
+		}
+
+	}
+
+	if (emit_device_availability_changed)
+		NM_SET_OUT (out_emit_device_availability_changed, TRUE);
 }
 
+/*****************************************************************************/
+
 static void
-setup_bluez4 (NMBluezManager *self)
+_conn_create_panu_connection (NMBluezManager *self,
+                              BzDBusObj *bzobj)
 {
-	NMBluez4Manager *manager;
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	gs_unref_object NMConnection *connection = NULL;
+	NMSettingsConnection *added;
+	NMSetting *setting;
+	gs_free char *id = NULL;
+	char uuid[37];
+	gs_free_error GError *error = NULL;
+
+	nm_utils_uuid_generate_buf (uuid);
+	id = g_strdup_printf (_("%s Network"), bzobj->d_device.name);
+
+	connection = nm_simple_connection_new ();
+
+	setting = nm_setting_connection_new ();
+	g_object_set (setting,
+	              NM_SETTING_CONNECTION_ID, id,
+	              NM_SETTING_CONNECTION_UUID, uuid,
+	              NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
+	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_BLUETOOTH_SETTING_NAME,
+	              NULL);
+	nm_connection_add_setting (connection, setting);
+
+	setting = nm_setting_bluetooth_new ();
+	g_object_set (setting,
+	              NM_SETTING_BLUETOOTH_BDADDR, bzobj->d_device.address,
+	              NM_SETTING_BLUETOOTH_TYPE, NM_SETTING_BLUETOOTH_TYPE_PANU,
+	              NULL);
+	nm_connection_add_setting (connection, setting);
+
+	if (!nm_connection_normalize (connection, NULL, NULL, &error)) {
+		_LOGE ("connection: couldn't generate a connection for NAP device: %s",
+		       error->message);
+		g_return_if_reached ();
+	}
 
-	g_return_if_fail (!priv->manager4 && !priv->manager5 && !priv->bluez_version);
+	nm_assert (_conn_track_is_relevant_connection (connection, NULL, NULL));
+
+	_LOGT ("connection: create in-memory PANU connection %s (%s) for device \"%s\" (%s)",
+	       uuid,
+	       id,
+	       bzobj->d_device.name,
+	       bzobj->d_device.address);
+
+	nm_settings_add_connection (priv->settings,
+	                            connection,
+	                            NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+	                            NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+	                            NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
+	                            &added,
+	                            &error);
+	if (!added) {
+		_LOGW ("connection: couldn't add new Bluetooth connection for NAP device: '%s' (%s): %s",
+		       id, uuid, error->message);
+		return;
+	}
 
-	setup_version_number (self, 4);
-	priv->manager4 = manager = nm_bluez4_manager_new (priv->settings);
+	if (   !_conn_track_is_relevant_for_sett_conn (added, NM_BT_CAPABILITY_NAP, bzobj->d_device.address)
+	    || !_conn_track_find_elem (self, added)
+	    || bzobj->x_device.panu_connection) {
+		_LOGE ("connection: something went wrong creating PANU connection %s (%s) for device '%s'",
+		       uuid, id, bzobj->d_device.address);
+		g_return_if_reached ();
+	}
 
-	g_signal_connect (manager,
-	                  NM_BLUEZ_MANAGER_BDADDR_ADDED,
-	                  G_CALLBACK (manager_bdaddr_added_cb),
-	                  self);
+	bzobj->x_device.panu_connection = added;
+}
+
+/*****************************************************************************/
+
+static void
+_device_state_changed_cb (NMDevice *device,
+                          guint new_state_u,
+                          guint old_state_u,
+                          guint reason_u,
+                          gpointer user_data)
+{
+	BzDBusObj *bzobj = user_data;
 
-	nm_bluez4_manager_query_devices (manager);
+	if (!_bzobjs_device_is_usable (bzobj, NULL, NULL)) {
+		/* the device got unusable? Need to revisit it... */
+		_process_change_idle_schedule (bzobj->self, bzobj);
+	}
 }
 
 static void
-setup_bluez5 (NMBluezManager *self)
+_device_process_change (BzDBusObj *bzobj)
+{
+	NMBluezManager *self = bzobj->self;
+	gs_unref_object NMDeviceBt *device_added = NULL;
+	gs_unref_object NMDeviceBt *device_deleted = NULL;
+	gboolean device_is_usable;
+	gboolean create_panu_connection = FALSE;
+
+	device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, &create_panu_connection);
+
+	if (create_panu_connection) {
+		bzobj->x_device_panu_connection_allow_create = FALSE;
+		_conn_create_panu_connection (self, bzobj);
+		device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, NULL);
+	} else {
+		if (   device_is_usable
+		    && bzobj->x_device_panu_connection_allow_create
+		    && NM_FLAGS_HAS (bzobj->d_device_capabilities, NM_BT_CAPABILITY_NAP)
+		    && _conn_track_find_head (self, NM_BT_CAPABILITY_NAP, bzobj->d_device.address) ) {
+			/* We have a useable device and also a panu-connection. We block future attemps
+			 * to generate a connection. */
+			bzobj->x_device_panu_connection_allow_create = FALSE;
+		}
+		if (bzobj->x_device.panu_connection) {
+			if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (bzobj->x_device.panu_connection),
+			                   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
+				/* the connection that we generated earlier still exists, but it's not longer the same
+				 * as it was when we created it. Forget about it, so that we don't delete the profile later... */
+				bzobj->x_device.panu_connection = NULL;
+			} else {
+				if (   !device_is_usable
+				    || !_conn_track_is_relevant_for_sett_conn (bzobj->x_device.panu_connection,
+				                                               NM_BT_CAPABILITY_NAP,
+				                                               bzobj->d_device.address)) {
+					_LOGT ("connection: delete in-memory PANU connection %s (%s) as device %s",
+					       nm_settings_connection_get_uuid (bzobj->x_device.panu_connection),
+					       nm_settings_connection_get_id (bzobj->x_device.panu_connection),
+					       !device_is_usable ? "is now unusable" : "no longer matches");
+					bzobj->x_device_panu_connection_allow_create = TRUE;
+					nm_settings_connection_delete (g_steal_pointer (&bzobj->x_device.panu_connection), FALSE);
+				}
+			}
+		}
+	}
+
+	bzobj->x_device_is_connected =    device_is_usable
+	                               && _bzobjs_device_is_connected (bzobj);
+
+	bzobj->x_device_is_usable = device_is_usable;
+
+	if (bzobj->x_device.device_bt) {
+		const char *device_to_delete_msg;
+
+		if (!device_is_usable)
+			device_to_delete_msg = "device became unusable";
+		else if (!_nm_device_bt_for_same_device (bzobj->x_device.device_bt,
+		                                         bzobj->object_path,
+		                                         bzobj->d_device.address,
+		                                         NULL,
+		                                         bzobj->d_device_capabilities))
+			device_to_delete_msg = "device is no longer compatible";
+		else
+			device_to_delete_msg = NULL;
+
+		if (device_to_delete_msg) {
+			nm_clear_g_signal_handler (bzobj->x_device.device_bt, &bzobj->x_device.device_bt_signal_id);
+
+			device_deleted = g_steal_pointer (&bzobj->x_device.device_bt);
+
+			_LOGD ("[%s]: drop device because %s",
+			       bzobj->object_path,
+			       device_to_delete_msg);
+
+			_connect_disconnect (self, bzobj, device_to_delete_msg);
+		}
+	}
+
+	if (device_is_usable) {
+		if (!bzobj->x_device.device_bt) {
+			bzobj->x_device.device_bt = nm_device_bt_new (self,
+			                                              bzobj->object_path,
+			                                              bzobj->d_device.address,
+			                                              bzobj->d_device.name,
+			                                              bzobj->d_device_capabilities);
+			device_added = g_object_ref (bzobj->x_device.device_bt);
+			bzobj->x_device.device_bt_signal_id = g_signal_connect (device_added,
+			                                                        NM_DEVICE_STATE_CHANGED,
+			                                                        G_CALLBACK (_device_state_changed_cb),
+			                                                        bzobj);
+		} else
+			_nm_device_bt_notify_set_name (bzobj->x_device.device_bt, bzobj->d_device.name);
+
+		_nm_device_bt_notify_set_connected (bzobj->x_device.device_bt, bzobj->x_device_is_connected);
+	}
+
+	if (   bzobj->x_device.c_req_data
+	    && !bzobj->x_device.c_req_data->int_cancellable
+	    && bzobj->x_device_is_connected) {
+		gs_free char *device_name = g_steal_pointer (&bzobj->x_device.c_req_data->device_name);
+
+		_device_connect_req_data_complete (g_steal_pointer (&bzobj->x_device.c_req_data),
+		                                   self,
+		                                   device_name,
+		                                   NULL);
+	}
+
+	if (device_added)
+		g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device_added);
+
+	if (device_deleted)
+		_nm_device_bt_notify_removed (device_deleted);
+}
+
+/*****************************************************************************/
+
+static void
+_process_change_idle_all (NMBluezManager *self,
+                          gboolean *out_emit_device_availability_changed)
 {
-	NMBluez5Manager *manager;
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	BzDBusObj *bzobj;
+
+	while ((bzobj = c_list_first_entry (&priv->process_change_lst_head, BzDBusObj, process_change_lst))) {
+
+		c_list_unlink (&bzobj->process_change_lst);
+
+		_LOG_bzobj (bzobj, "before-processing");
+
+		_device_process_change (bzobj);
+
+		_network_server_process_change (bzobj, out_emit_device_availability_changed);
+
+		_LOG_bzobj (bzobj, "after-processing");
+
+		_bzobjs_del_if_dead (bzobj);
+	}
+
+	nm_clear_g_source (&priv->process_change_idle_id);
+}
 
-	g_return_if_fail (!priv->manager4 && !priv->manager5 && !priv->bluez_version);
+static gboolean
+_process_change_idle_cb (gpointer user_data)
+{
+	NMBluezManager *self = user_data;
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	gboolean emit_device_availability_changed = FALSE;
 
-	setup_version_number (self, 5);
-	priv->manager5 = manager = nm_bluez5_manager_new (priv->settings);
+	_process_change_idle_all (self, &emit_device_availability_changed);
 
-	g_signal_connect (manager,
-	                  NM_BLUEZ_MANAGER_BDADDR_ADDED,
-	                  G_CALLBACK (manager_bdaddr_added_cb),
-	                  self);
-	g_signal_connect (manager,
-	                  NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED,
-	                  G_CALLBACK (manager_network_server_added_cb),
-	                  self);
+	if (emit_device_availability_changed)
+		nm_manager_notify_device_availibility_maybe_changed (priv->manager);
 
-	nm_bluez5_manager_query_devices (manager);
+	return G_SOURCE_CONTINUE;
 }
 
 static void
-watch_name_on_appeared (GDBusConnection *connection,
-                        const char *name,
-                        const char *name_owner,
-                        gpointer user_data)
+_process_change_idle_schedule (NMBluezManager *self,
+                               BzDBusObj *bzobj)
 {
-	check_bluez_and_try_setup (NM_BLUEZ_MANAGER (user_data));
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+
+	nm_c_list_move_tail (&priv->process_change_lst_head, &bzobj->process_change_lst);
+	if (priv->process_change_idle_id == 0)
+		priv->process_change_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE + 1, _process_change_idle_cb, self, NULL);
 }
 
 static void
-check_bluez_and_try_setup_final_step (NMBluezManager *self, int bluez_version, const char *reason)
+_dbus_process_changes (NMBluezManager *self,
+                       BzDBusObj *bzobj,
+                       const char *log_reason)
 {
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	gboolean network_server_is_usable;
+	gboolean adapter_is_usable_for_device;
+	gboolean device_is_usable;
+	gboolean changes = FALSE;
+	gboolean recheck_devices_for_adapter = FALSE;
+
+	nm_assert (bzobj);
+
+	_LOG_bzobj (bzobj, log_reason);
+
+	device_is_usable = _bzobjs_device_is_usable (bzobj, NULL, NULL);
+
+	if (bzobj->x_device_is_usable != device_is_usable)
+		changes = TRUE;
+	else if (bzobj->x_device.device_bt) {
+		if (!device_is_usable)
+			changes = TRUE;
+		else {
+			if (   bzobj->x_device_is_connected != _bzobjs_device_is_connected (bzobj)
+			    || !_nm_device_bt_for_same_device (bzobj->x_device.device_bt,
+			                                       bzobj->object_path,
+			                                       bzobj->d_device.address,
+			                                       bzobj->d_device.name,
+			                                       bzobj->d_device_capabilities))
+				changes = TRUE;
+		}
+	}
+
+	adapter_is_usable_for_device = _bzobjs_adapter_is_usable_for_device (bzobj);
+	if (adapter_is_usable_for_device != bzobj->was_usable_adapter_for_device_before) {
+		/* this function does not modify bzobj in any other cases except here.
+		 * Usually changes are processed delayed, in the idle handler.
+		 *
+		 * But the bzobj->was_usable_adapter_for_device_before only exists to know whether
+		 * we need to re-check device availability. It is correct to set the flag
+		 * here, right before we checked. */
+		bzobj->was_usable_adapter_for_device_before = adapter_is_usable_for_device;
+		recheck_devices_for_adapter = TRUE;
+		changes = TRUE;
+	}
+
+	if (!changes) {
+		network_server_is_usable = _bzobjs_network_server_is_usable (bzobj, TRUE);
+
+		if (network_server_is_usable != (!c_list_is_empty (&bzobj->x_network_server.lst)))
+			changes = TRUE;
+		else if (   bzobj->x_network_server.device_br
+		         && !network_server_is_usable)
+			changes = TRUE;
+		else if (!nm_streq0 (bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL,
+		                     bzobj->x_network_server.adapter_address))
+			changes = TRUE;
+	}
+
+	if (changes)
+		_process_change_idle_schedule (self, bzobj);
+
+	if (recheck_devices_for_adapter) {
+		GHashTableIter iter;
+		BzDBusObj *bzobj2;
+
+		/* we got a change to the availability of an adapter. We might need to recheck
+		 * all devices that use this adapter... */
+		g_hash_table_iter_init (&iter, priv->bzobjs);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &bzobj2, NULL)) {
+			if (bzobj2 == bzobj)
+				continue;
+			if (!nm_streq0 (bzobj2->d_device.adapter, bzobj->object_path))
+				continue;
+			if (c_list_is_empty (&bzobj2->process_change_lst))
+				_dbus_process_changes (self, bzobj2, "adapter-changed");
+			else
+				nm_c_list_move_tail (&priv->process_change_lst_head, &bzobj2->process_change_lst);
+		}
+	}
+
+	_bzobjs_del_if_dead (bzobj);
+}
+
+/*****************************************************************************/
+
+#define ALL_RELEVANT_INTERFACE_NAMES NM_MAKE_STRV (NM_BLUEZ5_ADAPTER_INTERFACE, \
+                                                   NM_BLUEZ5_DEVICE_INTERFACE, \
+                                                   NM_BLUEZ5_NETWORK_INTERFACE, \
+                                                   NM_BLUEZ5_NETWORK_SERVER_INTERFACE)
+
+static gboolean
+_dbus_handle_properties_changed (NMBluezManager *self,
+                                 const char *object_path,
+                                 const char *interface_name,
+                                 GVariant *changed_properties,
+                                 const char *const*invalidated_properties,
+                                 BzDBusObj **inout_bzobj)
+{
+	BzDBusObj *bzobj = NULL;
+	gboolean changed = FALSE;
+	const char *property_name;
+	GVariant *property_value;
+	GVariantIter iter_prop;
+	gsize i;
 
-	g_return_if_fail (!priv->bluez_version);
+	if (!invalidated_properties)
+		invalidated_properties = NM_PTRARRAY_EMPTY (const char *);
 
-	switch (bluez_version) {
-	case 4:
-		setup_bluez4 (self);
-		break;
-	case 5:
-		setup_bluez5 (self);
-		break;
-	default:
-		_LOGD ("detecting BlueZ version failed: %s", reason);
+	nm_assert (g_variant_is_of_type (changed_properties, G_VARIANT_TYPE ("a{sv}")));
 
-		/* cancel current attempts to detect the version. */
-		cleanup_checking (self, FALSE);
-		if (!priv->watch_name_id) {
-			priv->watch_name_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
-			                                        NM_BLUEZ_SERVICE,
-			                                        G_BUS_NAME_WATCHER_FLAGS_NONE,
-			                                        watch_name_on_appeared,
-			                                        NULL,
-			                                        self,
-			                                        NULL);
+	if (inout_bzobj) {
+		bzobj = *inout_bzobj;
+		nm_assert (!bzobj || nm_streq (object_path, bzobj->object_path));
+	}
+
+	if (changed_properties)
+		g_variant_iter_init (&iter_prop, changed_properties);
+
+	if (nm_streq (interface_name, NM_BLUEZ5_ADAPTER_INTERFACE)) {
+		_bzobjs_init (self, &bzobj, object_path);
+		if (!bzobj->d_has_adapter_iface) {
+			changed = TRUE;
+			bzobj->d_has_adapter_iface = TRUE;
+		}
+
+		while (   changed_properties
+		       && g_variant_iter_next (&iter_prop, "{&sv}", &property_name, &property_value)) {
+			_nm_unused gs_unref_variant GVariant *property_value_free = property_value;
+
+			if (nm_streq (property_name, "Address")) {
+				gs_free char *s =   g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING)
+				                  ? nm_utils_hwaddr_canonical (g_variant_get_string (property_value, NULL), ETH_ALEN)
+				                  : NULL;
+
+				if (!nm_streq0 (bzobj->d_adapter.address, s)) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_adapter.address);
+					bzobj->d_adapter.address = g_steal_pointer (&s);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Powered")) {
+				bool v =    g_variant_is_of_type (property_value, G_VARIANT_TYPE_BOOLEAN)
+				         && g_variant_get_boolean (property_value);
+
+				if (bzobj->d_adapter_powered != v) {
+					changed = TRUE;
+					bzobj->d_adapter_powered = v;
+				}
+				continue;
+			}
+		}
+
+		for (i = 0; (property_name = invalidated_properties[i]); i++) {
+			if (nm_streq (property_name, "Address")) {
+				if (bzobj->d_adapter.address) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_adapter.address);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Powered")) {
+				if (bzobj->d_adapter_powered) {
+					changed = TRUE;
+					bzobj->d_adapter_powered = FALSE;
+				}
+				continue;
+			}
+		}
+
+	} else if (nm_streq (interface_name, NM_BLUEZ5_DEVICE_INTERFACE)) {
+		_bzobjs_init (self, &bzobj, object_path);
+		if (!bzobj->d_has_device_iface) {
+			changed = TRUE;
+			bzobj->d_has_device_iface = TRUE;
+		}
+
+		while (   changed_properties
+		       && g_variant_iter_next (&iter_prop, "{&sv}", &property_name, &property_value)) {
+			_nm_unused gs_unref_variant GVariant *property_value_free = property_value;
+
+			if (nm_streq (property_name, "Address")) {
+				gs_free char *s =   g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING)
+				                  ? nm_utils_hwaddr_canonical (g_variant_get_string (property_value, NULL), ETH_ALEN)
+				                  : NULL;
+
+				if (!nm_streq0 (bzobj->d_device.address, s)) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_device.address);
+					bzobj->d_device.address = g_steal_pointer (&s);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Name")) {
+				const char *s =   g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING)
+				                ? g_variant_get_string (property_value, NULL)
+				                : NULL;
+
+				if (!nm_streq0 (bzobj->d_device.name, s)) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_device.name);
+					bzobj->d_device.name = g_strdup (s);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Adapter")) {
+				const char *s =   g_variant_is_of_type (property_value, G_VARIANT_TYPE_OBJECT_PATH)
+				                ? g_variant_get_string (property_value, NULL)
+				                : NULL;
+
+				if (!nm_streq0 (bzobj->d_device.adapter, s)) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_device.adapter);
+					bzobj->d_device.adapter = g_strdup (s);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "UUIDs")) {
+				NMBluetoothCapabilities capabilities = NM_BT_CAPABILITY_NONE;
+
+				if (g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING_ARRAY)) {
+					gs_free const char **s = g_variant_get_strv (property_value, NULL);
+
+					capabilities = convert_uuids_to_capabilities (s);
+				}
+				if (bzobj->d_device_capabilities != capabilities) {
+					changed = TRUE;
+					bzobj->d_device_capabilities = capabilities;
+					nm_assert (bzobj->d_device_capabilities == capabilities);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Connected")) {
+				bool v =    g_variant_is_of_type (property_value, G_VARIANT_TYPE_BOOLEAN)
+				         && g_variant_get_boolean (property_value);
+
+				if (bzobj->d_device_connected != v) {
+					changed = TRUE;
+					bzobj->d_device_connected = v;
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Paired")) {
+				bool v =    g_variant_is_of_type (property_value, G_VARIANT_TYPE_BOOLEAN)
+				         && g_variant_get_boolean (property_value);
+
+				if (bzobj->d_device_paired != v) {
+					changed = TRUE;
+					bzobj->d_device_paired = v;
+				}
+				continue;
+			}
+		}
+
+		for (i = 0; (property_name = invalidated_properties[i]); i++) {
+			if (nm_streq (property_name, "Address")) {
+				if (bzobj->d_device.address) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_device.address);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Name")) {
+				if (bzobj->d_device.name) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_device.name);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Adapter")) {
+				if (bzobj->d_device.adapter) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_device.adapter);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "UUIDs")) {
+				if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) {
+					changed = TRUE;
+					bzobj->d_device_capabilities = NM_BT_CAPABILITY_NONE;
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Connected")) {
+				if (bzobj->d_device_connected) {
+					changed = TRUE;
+					bzobj->d_device_connected = FALSE;
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Paired")) {
+				if (bzobj->d_device_paired) {
+					changed = TRUE;
+					bzobj->d_device_paired = FALSE;
+				}
+				continue;
+			}
+		}
+
+	} else if (nm_streq (interface_name, NM_BLUEZ5_NETWORK_INTERFACE)) {
+		_bzobjs_init (self, &bzobj, object_path);
+		if (!bzobj->d_has_network_iface) {
+			changed = TRUE;
+			bzobj->d_has_network_iface = TRUE;
+		}
+
+		while (   changed_properties
+		       && g_variant_iter_next (&iter_prop, "{&sv}", &property_name, &property_value)) {
+			_nm_unused gs_unref_variant GVariant *property_value_free = property_value;
+
+			if (nm_streq (property_name, "Interface")) {
+				const char *s =   g_variant_is_of_type (property_value, G_VARIANT_TYPE_STRING)
+				                ? g_variant_get_string (property_value, NULL)
+				                : NULL;
+
+				if (!nm_streq0 (bzobj->d_network.interface, s)) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_network.interface);
+					bzobj->d_network.interface = g_strdup (s);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Connected")) {
+				bool v =    g_variant_is_of_type (property_value, G_VARIANT_TYPE_BOOLEAN)
+				         && g_variant_get_boolean (property_value);
+
+				if (bzobj->d_network_connected != v) {
+					changed = TRUE;
+					bzobj->d_network_connected = v;
+				}
+				continue;
+			}
+		}
+
+		for (i = 0; (property_name = invalidated_properties[i]); i++) {
+			if (nm_streq (property_name, "Interface")) {
+				if (bzobj->d_network.interface) {
+					changed = TRUE;
+					nm_clear_g_free (&bzobj->d_network.interface);
+				}
+				continue;
+			}
+			if (nm_streq (property_name, "Connected")) {
+				if (bzobj->d_network_connected) {
+					changed = TRUE;
+					bzobj->d_network_connected = FALSE;
+				}
+				continue;
+			}
+		}
+
+	} else if (nm_streq (interface_name, NM_BLUEZ5_NETWORK_SERVER_INTERFACE)) {
+		_bzobjs_init (self, &bzobj, object_path);
+		if (!bzobj->d_has_network_server_iface) {
+			changed = TRUE;
+			bzobj->d_has_network_server_iface = TRUE;
 		}
-		break;
 	}
+
+	nm_assert (!changed || bzobj);
+
+	if (inout_bzobj)
+		*inout_bzobj = bzobj;
+
+	return changed;
 }
 
 static void
-check_bluez_and_try_setup_do_introspect (GObject *source_object,
-                                         GAsyncResult *res,
-                                         gpointer user_data)
+_dbus_handle_interface_added (NMBluezManager *self,
+                              const char *object_path,
+                              GVariant *ifaces,
+                              gboolean initial_get_managed_objects)
+{
+	BzDBusObj *bzobj = NULL;
+	gboolean changed = FALSE;
+	const char *interface_name;
+	GVariant *changed_properties;
+	GVariantIter iter_ifaces;
+
+	nm_assert (g_variant_is_of_type (ifaces, G_VARIANT_TYPE ("a{sa{sv}}")));
+
+	g_variant_iter_init (&iter_ifaces, ifaces);
+	while (g_variant_iter_next (&iter_ifaces, "{&s@a{sv}}", &interface_name, &changed_properties)) {
+		_nm_unused gs_unref_variant GVariant *changed_properties_free = changed_properties;
+
+		if (_dbus_handle_properties_changed (self, object_path, interface_name, changed_properties, NULL, &bzobj))
+			changed = TRUE;
+	}
+
+	if (changed) {
+		_dbus_process_changes (self,
+		                       bzobj,
+		                         initial_get_managed_objects
+		                       ? "dbus-init"
+		                       : "dbus-iface-added");
+	}
+}
+
+static gboolean
+_dbus_handle_interface_removed (NMBluezManager *self,
+                                const char *object_path,
+                                BzDBusObj **inout_bzobj,
+                                const char *const*removed_interfaces)
 {
-	NMBluezManager *self = async_data_unpack (user_data);
+	gboolean changed = FALSE;
+	BzDBusObj *bzobj;
+	gsize i;
+
+	if (   inout_bzobj
+	    && *inout_bzobj) {
+		bzobj = *inout_bzobj;
+		nm_assert (bzobj == _bzobjs_get (self, object_path));
+	} else {
+		bzobj = _bzobjs_get (self, object_path);
+		if (!bzobj)
+			return FALSE;
+		NM_SET_OUT (inout_bzobj, bzobj);
+	}
+
+	for (i = 0; removed_interfaces[i]; i++) {
+		const char *interface_name = removed_interfaces[i];
+
+		if (nm_streq (interface_name, NM_BLUEZ5_ADAPTER_INTERFACE)) {
+			if (bzobj->d_has_adapter_iface) {
+				changed = TRUE;
+				bzobj->d_has_adapter_iface = FALSE;
+			}
+			if (bzobj->d_adapter.address) {
+				changed = TRUE;
+				nm_clear_g_free (&bzobj->d_adapter.address);
+			}
+			if (bzobj->d_adapter_powered) {
+				changed = TRUE;
+				bzobj->d_adapter_powered = FALSE;
+			}
+		} else if (nm_streq (interface_name, NM_BLUEZ5_DEVICE_INTERFACE)) {
+			if (bzobj->d_has_device_iface) {
+				changed = TRUE;
+				bzobj->d_has_device_iface = FALSE;
+			}
+			if (bzobj->d_device.address) {
+				changed = TRUE;
+				nm_clear_g_free (&bzobj->d_device.address);
+			}
+			if (bzobj->d_device.name) {
+				changed = TRUE;
+				nm_clear_g_free (&bzobj->d_device.name);
+			}
+			if (bzobj->d_device.adapter) {
+				changed = TRUE;
+				nm_clear_g_free (&bzobj->d_device.adapter);
+			}
+			if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) {
+				changed = TRUE;
+				bzobj->d_device_capabilities = NM_BT_CAPABILITY_NONE;
+			}
+			if (bzobj->d_device_connected) {
+				changed = TRUE;
+				bzobj->d_device_connected = FALSE;
+			}
+			if (bzobj->d_device_paired) {
+				changed = TRUE;
+				bzobj->d_device_paired = FALSE;
+			}
+		} else if (nm_streq (interface_name, NM_BLUEZ5_NETWORK_INTERFACE)) {
+			if (bzobj->d_has_network_iface) {
+				changed = TRUE;
+				bzobj->d_has_network_iface = FALSE;
+			}
+			if (bzobj->d_network.interface) {
+				changed = TRUE;
+				nm_clear_g_free (&bzobj->d_network.interface);
+			}
+			if (bzobj->d_network_connected) {
+				changed = TRUE;
+				bzobj->d_network_connected = FALSE;
+			}
+		} else if (nm_streq (interface_name, NM_BLUEZ5_NETWORK_SERVER_INTERFACE)) {
+			if (bzobj->d_has_network_server_iface) {
+				changed = TRUE;
+				bzobj->d_has_network_server_iface = FALSE;
+			}
+		}
+	}
+
+	return changed;
+}
+
+static void
+_dbus_managed_objects_changed_cb (const char *object_path,
+                                  GVariant *added_interfaces_and_properties,
+                                  const char *const*removed_interfaces,
+                                  gpointer user_data)
+{
+	NMBluezManager *self = user_data;
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	BzDBusObj *bzobj = NULL;
+	gboolean changed;
+
+	if (priv->get_managed_objects_cancellable) {
+		/* we still wait for the initial GetManagedObjects(). Ignore the event. */
+		return;
+	}
+
+	if (!added_interfaces_and_properties) {
+		changed = _dbus_handle_interface_removed (self, object_path, &bzobj, removed_interfaces);
+		if (changed)
+			_dbus_process_changes (self, bzobj, "dbus-iface-removed");
+	} else
+		_dbus_handle_interface_added (self, object_path, added_interfaces_and_properties, FALSE);
+}
+
+static void
+_dbus_properties_changed_cb (GDBusConnection *connection,
+                             const char *sender_name,
+                             const char *object_path,
+                             const char *signal_interface_name,
+                             const char *signal_name,
+                             GVariant *parameters,
+                             gpointer user_data)
+{
+	NMBluezManager *self = user_data;
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	const char *interface_name;
+	gs_unref_variant GVariant *changed_properties = NULL;
+	gs_free const char **invalidated_properties = NULL;
+	BzDBusObj *bzobj = NULL;
+
+	if (priv->get_managed_objects_cancellable) {
+		/* we still wait for the initial GetManagedObjects(). Ignore the event. */
+		return;
+	}
+
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")))
+		return;
+
+	g_variant_get (parameters,
+	               "(&s@a{sv}^a&s)",
+	               &interface_name,
+	               &changed_properties,
+	               &invalidated_properties);
+
+	if (_dbus_handle_properties_changed (self, object_path, interface_name, changed_properties, invalidated_properties, &bzobj))
+		_dbus_process_changes (self, bzobj, "dbus-property-changed");
+}
+
+static void
+_dbus_get_managed_objects_cb (GVariant *result,
+                              GError *error,
+                              gpointer user_data)
+{
+	NMBluezManager *self;
 	NMBluezManagerPrivate *priv;
-	GError *error = NULL;
-	GVariant *result;
-	const char *xml_data;
-	int bluez_version = 0;
-	const char *reason = NULL;
+	GVariantIter iter;
+	const char *object_path;
+	GVariant *ifaces;
 
-	if (!self)
+	if (   !result
+	    && nm_utils_error_is_cancelled (error, FALSE))
 		return;
 
+	self = user_data;
 	priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (priv->introspect_proxy);
-	g_return_if_fail (!g_cancellable_is_cancelled (priv->async_cancellable));
-	g_return_if_fail (!priv->bluez_version);
-
-	g_clear_object (&priv->async_cancellable);
+	g_clear_object (&priv->get_managed_objects_cancellable);
 
-	result = _nm_dbus_proxy_call_finish (priv->introspect_proxy, res,
-	                                     G_VARIANT_TYPE ("(s)"), &error);
 	if (!result) {
-		char *reason2;
-
-		g_dbus_error_strip_remote_error (error);
-		reason2 = g_strdup_printf ("introspect failed with %s", error->message);
-		check_bluez_and_try_setup_final_step (self, 0, reason2);
-		g_error_free (error);
-		g_free (reason2);
+		_LOGT ("initial GetManagedObjects() call failed: %s", error->message);
+		_cleanup_for_name_owner (self);
 		return;
 	}
 
-	g_variant_get (result, "(&s)", &xml_data);
+	_LOGT ("initial GetManagedObjects call succeeded");
+
+	g_variant_iter_init (&iter, result);
+	while (g_variant_iter_next (&iter, "{&o@a{sa{sv}}}", &object_path, &ifaces)) {
+		_nm_unused gs_unref_variant GVariant *ifaces_free = ifaces;
+
+		_dbus_handle_interface_added (self, object_path, ifaces, TRUE);
+	}
+}
+
+/*****************************************************************************/
+
+static void
+_cleanup_for_name_owner (NMBluezManager *self)
+{
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	gboolean emit_device_availability_changed = FALSE;
+	GHashTableIter iter;
+	BzDBusObj *bzobj;
+	gboolean first = TRUE;
+
+	nm_clear_g_cancellable (&priv->get_managed_objects_cancellable);
+
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->managed_objects_changed_id);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->properties_changed_id);
+
+	nm_clear_g_free (&priv->name_owner);
+
+	g_hash_table_iter_init (&iter, priv->bzobjs);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &bzobj, NULL)) {
+		if (first) {
+			first = FALSE;
+			_LOGT ("drop all objects form D-Bus cache...");
+		}
+		_dbus_handle_interface_removed (self,
+		                                bzobj->object_path,
+		                                &bzobj,
+		                                ALL_RELEVANT_INTERFACE_NAMES);
+		nm_c_list_move_tail (&priv->process_change_lst_head, &bzobj->process_change_lst);
+	}
+	_process_change_idle_all (self, &emit_device_availability_changed);
+	nm_assert (g_hash_table_size (priv->bzobjs) == 0);
+
+	if (emit_device_availability_changed)
+		nm_manager_notify_device_availibility_maybe_changed (priv->manager);
+}
 
-	/* might not be the best approach to detect the version, but it's good enough in practice. */
-	if (strstr (xml_data, "org.freedesktop.DBus.ObjectManager"))
-		bluez_version = 5;
-	else if (strstr (xml_data, NM_BLUEZ4_MANAGER_INTERFACE))
-		bluez_version = 4;
+static void
+name_owner_changed (NMBluezManager *self,
+                    const char *owner)
+{
+	_nm_unused gs_unref_object NMBluezManager *self_keep_alive = g_object_ref (self);
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+
+	owner = nm_str_not_empty (owner);
+
+	if (!owner)
+		_LOGT ("D-Bus name for bluez has no owner");
 	else
-		reason = "unexpected introspect result";
+		_LOGT ("D-Bus name for bluez has owner %s", owner);
+
+	nm_clear_g_cancellable (&priv->name_owner_get_cancellable);
+
+	if (nm_streq0 (priv->name_owner, owner))
+		return;
+
+	_cleanup_for_name_owner (self);
 
-	g_variant_unref (result);
+	if (!owner)
+		return;
 
-	check_bluez_and_try_setup_final_step (self, bluez_version, reason);
+	priv->name_owner = g_strdup (owner);
+
+	priv->get_managed_objects_cancellable = g_cancellable_new ();
+
+	priv->managed_objects_changed_id = nm_dbus_connection_signal_subscribe_object_manager (priv->dbus_connection,
+	                                                                                       priv->name_owner,
+	                                                                                       NM_BLUEZ_MANAGER_PATH,
+	                                                                                       _dbus_managed_objects_changed_cb,
+	                                                                                       self,
+	                                                                                       NULL);
+
+	priv->properties_changed_id = nm_dbus_connection_signal_subscribe_properties_changed (priv->dbus_connection,
+	                                                                                      priv->name_owner,
+	                                                                                      NULL,
+	                                                                                      NULL,
+	                                                                                      _dbus_properties_changed_cb,
+	                                                                                      self,
+	                                                                                      NULL);
+
+	nm_dbus_connection_call_get_managed_objects (priv->dbus_connection,
+	                                             priv->name_owner,
+	                                             NM_BLUEZ_MANAGER_PATH,
+	                                             G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                                             20000,
+	                                             priv->get_managed_objects_cancellable,
+	                                             _dbus_get_managed_objects_cb,
+	                                             self);
 }
 
 static void
-check_bluez_and_try_setup_on_new_proxy (GObject *source_object,
-                                        GAsyncResult *res,
-                                        gpointer user_data)
+name_owner_changed_cb (GDBusConnection *connection,
+                       const char *sender_name,
+                       const char *object_path,
+                       const char *interface_name,
+                       const char *signal_name,
+                       GVariant *parameters,
+                       gpointer user_data)
 {
-	NMBluezManager *self = async_data_unpack (user_data);
-	NMBluezManagerPrivate *priv;
-	GError *error = NULL;
+	NMBluezManager *self = user_data;
+	const char *new_owner;
 
-	if (!self)
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sss)")))
 		return;
 
+	g_variant_get (parameters,
+	               "(&s&s&s)",
+	               NULL,
+	               NULL,
+	               &new_owner);
+
+	name_owner_changed (self, new_owner);
+}
+
+static void
+name_owner_get_cb (const char *name_owner,
+                   GError *error,
+                   gpointer user_data)
+{
+	if (   name_owner
+	    || !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		name_owner_changed (user_data, name_owner);
+}
+
+/*****************************************************************************/
+
+static void
+_cleanup_all (NMBluezManager *self)
+{
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+
+	priv->settings_registered = FALSE;
+
+	g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_added, self);
+	g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_updated, self);
+	g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_removed, self);
+
+	g_hash_table_remove_all (priv->conn_data_elems);
+	g_hash_table_remove_all (priv->conn_data_heads);
+
+	_cleanup_for_name_owner (self);
+
+	nm_clear_g_cancellable (&priv->name_owner_get_cancellable);
+
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->name_owner_changed_id);
+}
+
+static void
+start (NMDeviceFactory *factory)
+{
+	NMBluezManager *self;
+	NMBluezManagerPrivate *priv;
+	NMSettingsConnection *const*sett_conns;
+	guint n_sett_conns;
+	guint i;
+
+	g_return_if_fail (NM_IS_BLUEZ_MANAGER (factory));
+
+	self = NM_BLUEZ_MANAGER (factory);
 	priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (!priv->introspect_proxy);
-	g_return_if_fail (!g_cancellable_is_cancelled (priv->async_cancellable));
-	g_return_if_fail (!priv->bluez_version);
+	_cleanup_all (self);
+
+	if (!priv->dbus_connection) {
+		_LOGI ("no D-Bus connection available");
+		return;
+	}
+
+	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_ADDED,   G_CALLBACK (cp_connection_added),   self);
+	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, G_CALLBACK (cp_connection_updated), self);
+	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_REMOVED, G_CALLBACK (cp_connection_removed), self);
 
-	priv->introspect_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+	priv->settings_registered = TRUE;
 
-	if (!priv->introspect_proxy) {
-		char *reason = g_strdup_printf ("bluez error creating dbus proxy: %s", error->message);
-		check_bluez_and_try_setup_final_step (self, 0, reason);
-		g_error_free (error);
-		g_free (reason);
+	sett_conns = nm_settings_get_connections (priv->settings, &n_sett_conns);
+	for (i = 0; i < n_sett_conns; i++)
+		_conn_track_update (self, sett_conns[i], TRUE, NULL, NULL, NULL);
+
+	priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed (priv->dbus_connection,
+	                                                                                      NM_BLUEZ_SERVICE,
+	                                                                                      name_owner_changed_cb,
+	                                                                                      self,
+	                                                                                      NULL);
+
+	priv->name_owner_get_cancellable = g_cancellable_new ();
+
+	nm_dbus_connection_call_get_name_owner (priv->dbus_connection,
+	                                        NM_BLUEZ_SERVICE,
+	                                        10000,
+	                                        priv->name_owner_get_cancellable,
+	                                        name_owner_get_cb,
+	                                        self);
+}
+
+/*****************************************************************************/
+
+static void
+_connect_returned (NMBluezManager *self,
+                   BzDBusObj *bzobj,
+                   NMBluetoothCapabilities bt_type,
+                   const char *device_name,
+                   NMBluez5DunContext *dun_context,
+                   GError *error)
+{
+	char sbuf_cap[100];
+
+	if (error) {
+		nm_assert (!device_name);
+		nm_assert (!dun_context);
+
+		_LOGI ("%s [%s]: connect failed: %s",
+		       nm_bluetooth_capability_to_string (bzobj->x_device_connect_bt_type, sbuf_cap, sizeof (sbuf_cap)),
+		       bzobj->object_path,
+		       error->message);
+
+		_device_connect_req_data_complete (g_steal_pointer (&bzobj->x_device.c_req_data),
+		                                   self,
+		                                   NULL,
+		                                   error);
+		_connect_disconnect (self, bzobj, "cleanup after connect failure");
 		return;
 	}
 
-	g_dbus_proxy_call (priv->introspect_proxy,
-	                   "Introspect",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NO_AUTO_START,
-	                   3000,
-	                   priv->async_cancellable,
-	                   check_bluez_and_try_setup_do_introspect,
-	                   async_data_pack (self));
+	nm_assert (bzobj->x_device_connect_bt_type == bt_type);
+	nm_assert (device_name);
+	nm_assert ((bt_type == NM_BT_CAPABILITY_DUN) == (!!dun_context));
+	nm_assert (bzobj->x_device.c_req_data);
+
+	g_clear_object (&bzobj->x_device.c_req_data->int_cancellable);
+
+	bzobj->x_device.connect_dun_context = dun_context;
+
+	_LOGD ("%s [%s]: connect successful to device %s",
+	       nm_bluetooth_capability_to_string (bzobj->x_device_connect_bt_type, sbuf_cap, sizeof (sbuf_cap)),
+	       bzobj->object_path,
+	       device_name);
+
+	/* we already have another over-all timer running. But after we connected the device,
+	 * we still need to wait for bluez to acknowledge the connected state (via D-Bus, for NAP).
+	 * For DUN profiles we likely are already fully connected by now.
+	 *
+	 * Anyway, schedule another timeout that is possibly shorter than the overall, original
+	 * timeout. Now this should go down fast. */
+	bzobj->x_device.c_req_data->timeout_wait_connect_id = g_timeout_add (5000,
+	                                                                     _connect_timeout_wait_connected_cb,
+	                                                                     bzobj),
+	bzobj->x_device.c_req_data->device_name = g_strdup (device_name);
+
+	if (   _bzobjs_device_is_usable (bzobj, NULL, NULL)
+	    && _bzobjs_device_is_connected (bzobj)) {
+		/* We are now connected. Schedule the task that completes the state. */
+		_process_change_idle_schedule (self, bzobj);
+	}
+}
+
+#if WITH_BLUEZ5_DUN
+static void
+_connect_dun_notify_tty_hangup_cb (NMBluez5DunContext *context,
+                                   gpointer user_data)
+{
+	BzDBusObj *bzobj = user_data;
+
+	_connect_disconnect (bzobj->self,
+	                     bzobj,
+	                     "DUN connection hung up");
 }
 
 static void
-check_bluez_and_try_setup (NMBluezManager *self)
+_connect_dun_step2_cb (NMBluez5DunContext *context,
+                       const char *rfcomm_dev,
+                       GError *error,
+                       gpointer user_data)
 {
-	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	BzDBusObj *bzobj;
+
+	if (nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	bzobj = user_data;
+
+	if (rfcomm_dev) {
+		/* We want to early notifiy about the rfcomm path. That is because we might still delay
+		 * to signal full activation longer (asynchronously). But the earliest time the callback
+		 * is invoked with the rfcomm path, we just created the device synchronously.
+		 *
+		 * By already notifying the caller about the path early, it avoids a race where ModemManager
+		 * would find the modem before the bluetooth code considers the profile fully activated. */
 
-	g_return_if_fail (!priv->bluez_version);
+		nm_assert (!error);
+		nm_assert (bzobj->x_device.c_req_data);
 
-	/* there should be no ongoing detection. Anyway, cleanup_checking. */
-	cleanup_checking (self, FALSE);
+		if (!g_cancellable_is_cancelled (bzobj->x_device.c_req_data->ext_cancellable))
+			bzobj->x_device.c_req_data->callback (bzobj->self, FALSE, rfcomm_dev, NULL, bzobj->x_device.c_req_data->callback_user_data);
 
-	priv->async_cancellable = g_cancellable_new ();
+		if (!context) {
+			/* No context set. This means, we just got notified about the rfcomm path and need to wait
+			 * longer, for the next callback. */
+			return;
+		}
+	}
 
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
-	                          NULL,
-	                          NM_BLUEZ_SERVICE,
-	                          "/",
-	                          DBUS_INTERFACE_INTROSPECTABLE,
-	                          priv->async_cancellable,
-	                          check_bluez_and_try_setup_on_new_proxy,
-	                          async_data_pack (self));
+	_connect_returned (bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, rfcomm_dev, context, error);
 }
 
 static void
-start (NMDeviceFactory *factory)
+_connect_dun_step1_cb (GObject *source_object,
+                       GAsyncResult *res,
+                       gpointer user_data)
+{
+	gs_unref_variant GVariant *ret = NULL;
+	gs_free_error GError *error = NULL;
+	BzDBusObj *bzobj_adapter;
+	BzDBusObj *bzobj;
+
+	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
+
+	if (   !ret
+	    && nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	bzobj = user_data;
+
+	if (error) {
+		_LOGT ("DUN: [%s]: bluetooth device connect failed: %s", bzobj->object_path, error->message);
+		/* we actually ignore this error. Let's try, maybe we still can connect via DUN. */
+		g_clear_error (&error);
+	} else
+		_LOGT ("DUN: [%s]: bluetooth device connected successfully", bzobj->object_path);
+
+	if (!_bzobjs_device_is_usable (bzobj, &bzobj_adapter, NULL)) {
+		nm_utils_error_set (&error, NM_UTILS_ERROR_UNKNOWN,
+		                    "device %s is not usable for DUN after connect",
+		                    bzobj->object_path);
+		_connect_returned (bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, NULL, NULL, error);
+		return;
+	}
+
+	if (!nm_bluez5_dun_connect (bzobj_adapter->d_adapter.address,
+	                            bzobj->d_device.address,
+	                            bzobj->x_device.c_req_data->int_cancellable,
+	                            _connect_dun_step2_cb,
+	                            bzobj,
+	                            _connect_dun_notify_tty_hangup_cb,
+	                            bzobj,
+	                            &error)) {
+		_connect_returned (bzobj->self, bzobj, NM_BT_CAPABILITY_DUN, NULL, NULL, error);
+		return;
+	}
+}
+#endif
+
+static void
+_connect_nap_cb (GObject *source_object,
+                 GAsyncResult *res,
+                 gpointer user_data)
 {
-	check_bluez_and_try_setup (NM_BLUEZ_MANAGER (factory));
+	gs_unref_variant GVariant *ret = NULL;
+	const char *network_iface_name = NULL;
+	gs_free_error GError *error = NULL;
+	BzDBusObj *bzobj;
+
+	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
+
+	if (   !ret
+	    && nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	if (ret)
+		g_variant_get (ret, "(&s)", &network_iface_name);
+
+	bzobj = user_data;
+
+	_connect_returned (bzobj->self, bzobj, NM_BT_CAPABILITY_NAP, network_iface_name, NULL, error);
+}
+
+static void
+_connect_cancelled_cb (GCancellable *cancellable,
+                       BzDBusObj *bzobj)
+{
+	_connect_disconnect (bzobj->self, bzobj, "connect cancelled");
 }
 
+static gboolean
+_connect_timeout_wait_connected_cb (gpointer user_data)
+{
+	BzDBusObj *bzobj = user_data;
+
+	bzobj->x_device.c_req_data->timeout_wait_connect_id = 0;
+	_connect_disconnect (bzobj->self, bzobj, "timeout waiting for connected");
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_connect_timeout_cb (gpointer user_data)
+{
+	BzDBusObj *bzobj = user_data;
+
+	bzobj->x_device.c_req_data->timeout_id = 0;
+	_connect_disconnect (bzobj->self, bzobj, "timeout connecting");
+	return G_SOURCE_REMOVE;
+}
+
+static void
+_connect_disconnect (NMBluezManager *self,
+                     BzDBusObj *bzobj,
+                     const char *reason)
+{
+	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+	DeviceConnectReqData *c_req_data;
+	char sbuf_cap[100];
+	gboolean bt_type;
+
+	if (bzobj->x_device_connect_bt_type == NM_BT_CAPABILITY_NONE) {
+		nm_assert (!bzobj->x_device.c_req_data);
+		return;
+	}
+
+	bt_type = bzobj->x_device_connect_bt_type;
+	nm_assert (NM_IN_SET (bt_type, NM_BT_CAPABILITY_DUN, NM_BT_CAPABILITY_NAP));
+	bzobj->x_device_connect_bt_type = NM_BT_CAPABILITY_NONE;
+
+	c_req_data = g_steal_pointer (&bzobj->x_device.c_req_data);
+
+	_LOGD ("%s [%s]: disconnect due to %s",
+	       nm_bluetooth_capability_to_string (bt_type, sbuf_cap, sizeof (sbuf_cap)),
+	       bzobj->object_path,
+	       reason);
+
+	if (c_req_data)
+		nm_clear_g_cancellable (&c_req_data->int_cancellable);
+
+	if (bt_type == NM_BT_CAPABILITY_DUN) {
+		/* For DUN devices, we also called org.bluez.Device1.Connect() (because in order
+		 * for nm_bluez5_dun_connect() to succeed, we need to be already connected *why??).
+		 *
+		 * But upon disconnect we don't call Disconnect() because we don't know whether somebody
+		 * else also uses the bluetooth device for other purposes. During disconnect we only
+		 * terminate the DUN connection, but don't disconnect entirely. I think that's the
+		 * best we can do. */
+#if WITH_BLUEZ5_DUN
+		nm_clear_pointer (&bzobj->x_device.connect_dun_context, nm_bluez5_dun_disconnect);
+#else
+		nm_assert_not_reached ();
+#endif
+	} else {
+		if (priv->name_owner) {
+			gs_unref_object GCancellable *cancellable = NULL;
+
+			cancellable = g_cancellable_new ();
+
+			nm_shutdown_wait_obj_register_cancellable_full (cancellable,
+			                                                g_strdup_printf ("bt-disconnect-nap[%s]", bzobj->object_path),
+			                                                TRUE);
+
+			g_dbus_connection_call (priv->dbus_connection,
+			                        priv->name_owner,
+			                        bzobj->object_path,
+			                        NM_BLUEZ5_NETWORK_INTERFACE,
+			                        "Disconnect",
+			                        g_variant_new("()"),
+			                        NULL,
+			                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+			                        -1,
+			                        cancellable,
+			                        _dbus_call_complete_cb_nop,
+			                        NULL);
+		}
+	}
+
+	if (c_req_data) {
+		gs_free_error GError *error = NULL;
+
+		nm_utils_error_set (&error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "connect aborted due to %s",
+		                    reason);
+		_device_connect_req_data_complete (c_req_data, self, NULL, error);
+	}
+}
+
+gboolean
+nm_bluez_manager_connect (NMBluezManager *self,
+                          const char *object_path,
+                          NMBluetoothCapabilities connection_bt_type,
+                          int timeout_msec,
+                          GCancellable *cancellable,
+                          NMBluezManagerConnectCb callback,
+                          gpointer callback_user_data,
+                          GError **error)
+{
+	gs_unref_object GCancellable *int_cancellable = NULL;
+	DeviceConnectReqData *c_req_data;
+	NMBluezManagerPrivate *priv;
+	BzDBusObj *bzobj;
+	char sbuf_cap[100];
+
+	g_return_val_if_fail (NM_IS_BLUEZ_MANAGER (self), FALSE);
+	g_return_val_if_fail (NM_IN_SET (connection_bt_type, NM_BT_CAPABILITY_DUN,
+	                                                     NM_BT_CAPABILITY_NAP), FALSE);
+	g_return_val_if_fail (callback, FALSE);
+
+	nm_assert (timeout_msec > 0);
+
+	priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
+
+	bzobj = _bzobjs_get (self, object_path);
+
+	if (!bzobj) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+		                    "device %s does not exist",
+		                    object_path);
+		return FALSE;
+	}
+
+	if (!_bzobjs_device_is_usable (bzobj, NULL, NULL)) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+		                    "device %s is not usable",
+		                    object_path);
+		return FALSE;
+	}
+
+	if (!NM_FLAGS_ALL (bzobj->d_device_capabilities, connection_bt_type)) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+		                    "device %s has not the required capabilities",
+		                    object_path);
+		return FALSE;
+	}
+
+#if !WITH_BLUEZ5_DUN
+	if (connection_bt_type == NM_BT_CAPABILITY_DUN) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+		                    "DUN is not supported");
+		return FALSE;
+	}
+#endif
+
+	_connect_disconnect (self, bzobj, "new activation");
+
+	_LOGD ("%s [%s]: connecting...",
+	       nm_bluetooth_capability_to_string (connection_bt_type, sbuf_cap, sizeof (sbuf_cap)),
+	       bzobj->object_path);
+
+	int_cancellable = g_cancellable_new();
+
+#if WITH_BLUEZ5_DUN
+	if (connection_bt_type == NM_BT_CAPABILITY_DUN) {
+		g_dbus_connection_call (priv->dbus_connection,
+		                        priv->name_owner,
+		                        bzobj->object_path,
+		                        NM_BLUEZ5_DEVICE_INTERFACE,
+		                        "Connect",
+		                        NULL,
+		                        NULL,
+		                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+		                        timeout_msec,
+		                        int_cancellable,
+		                        _connect_dun_step1_cb,
+		                        bzobj);
+	} else
+#endif
+	{
+		nm_assert (connection_bt_type == NM_BT_CAPABILITY_NAP);
+		g_dbus_connection_call (priv->dbus_connection,
+		                        priv->name_owner,
+		                        bzobj->object_path,
+		                        NM_BLUEZ5_NETWORK_INTERFACE,
+		                        "Connect",
+		                        g_variant_new ("(s)", BLUETOOTH_CONNECT_NAP),
+		                        G_VARIANT_TYPE ("(s)"),
+		                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+		                        timeout_msec,
+		                        int_cancellable,
+		                        _connect_nap_cb,
+		                        bzobj);
+	}
+
+	c_req_data = g_slice_new (DeviceConnectReqData);
+	*c_req_data = (DeviceConnectReqData) {
+		.int_cancellable     = g_steal_pointer (&int_cancellable),
+		.ext_cancellable     = g_object_ref (cancellable),
+		.callback            = callback,
+		.callback_user_data  = callback_user_data,
+		.ext_cancelled_id    = g_signal_connect (cancellable,
+		                                         "cancelled",
+		                                         G_CALLBACK (_connect_cancelled_cb),
+		                                         bzobj),
+		.timeout_id          = g_timeout_add (timeout_msec,
+		                                      _connect_timeout_cb,
+		                                      bzobj),
+	};
+
+	bzobj->x_device_connect_bt_type = connection_bt_type;
+	bzobj->x_device.c_req_data = c_req_data;
+
+	return TRUE;
+}
+
+void
+nm_bluez_manager_disconnect (NMBluezManager *self,
+                             const char *object_path)
+{
+	BzDBusObj *bzobj;
+
+	g_return_if_fail (NM_IS_BLUEZ_MANAGER (self));
+	g_return_if_fail (object_path);
+
+	bzobj = _bzobjs_get (self, object_path);
+	if (!bzobj)
+		return;
+
+	_connect_disconnect (self, bzobj, "disconnected by user");
+}
+
+/*****************************************************************************/
+
 static NMDevice *
 create_device (NMDeviceFactory *factory,
                const char *iface,
@@ -407,8 +2789,8 @@ create_device (NMDeviceFactory *factory,
                NMConnection *connection,
                gboolean *out_ignore)
 {
-	g_warn_if_fail (plink->type == NM_LINK_TYPE_BNEP);
 	*out_ignore = TRUE;
+	g_return_val_if_fail (plink->type == NM_LINK_TYPE_BNEP, NULL);
 	return NULL;
 }
 
@@ -433,7 +2815,25 @@ nm_bluez_manager_init (NMBluezManager *self)
 {
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
 
+	priv->vtable_network_server = (NMBtVTableNetworkServer) {
+		.is_available      = _network_server_vt_is_available,
+		.register_bridge   = _network_server_vt_register_bridge,
+		.unregister_bridge = _network_server_vt_unregister_bridge,
+	};
+
+	c_list_init (&priv->network_server_lst_head);
+	c_list_init (&priv->process_change_lst_head);
+
+	priv->conn_data_heads = g_hash_table_new_full (_conn_data_head_hash, _conn_data_head_equal, g_free, NULL);
+	priv->conn_data_elems = g_hash_table_new_full (nm_pdirect_hash, nm_pdirect_equal, nm_g_slice_free_fcn (ConnDataElem), NULL);
+
+	priv->bzobjs = g_hash_table_new_full (nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _bz_dbus_obj_free, NULL);
+
+	priv->manager = g_object_ref (NM_MANAGER_GET);
 	priv->settings = g_object_ref (NM_SETTINGS_GET);
+	priv->dbus_connection = nm_g_object_ref (NM_MAIN_DBUS_CONNECTION_GET);
+
+	g_atomic_pointer_compare_and_exchange (&nm_bt_vtable_network_server, NULL, &priv->vtable_network_server);
 }
 
 static void
@@ -442,22 +2842,25 @@ dispose (GObject *object)
 	NMBluezManager *self = NM_BLUEZ_MANAGER (object);
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
 
-	if (priv->manager4) {
-		g_signal_handlers_disconnect_by_func (priv->manager4, manager_bdaddr_added_cb, self);
-		g_clear_object (&priv->manager4);
-	}
-	if (priv->manager5) {
-		g_signal_handlers_disconnect_by_data (priv->manager5, self);
-		g_clear_object (&priv->manager5);
-	}
+	/* FIXME(shutdown): we need a nm_device_factory_stop() hook to first unregister all
+	 *   BzDBusObj instances and do necessary cleanup actions (like disconnecting devices
+	 *   or deleting panu_connection). */
 
-	cleanup_checking (self, TRUE);
+	nm_assert (c_list_is_empty (&priv->network_server_lst_head));
+	nm_assert (c_list_is_empty (&priv->process_change_lst_head));
+	nm_assert (priv->process_change_idle_id == 0);
 
-	priv->bluez_version = 0;
+	g_atomic_pointer_compare_and_exchange (&nm_bt_vtable_network_server, &priv->vtable_network_server, NULL);
+
+	_cleanup_all (self);
 
 	G_OBJECT_CLASS (nm_bluez_manager_parent_class)->dispose (object);
 
 	g_clear_object (&priv->settings);
+	g_clear_object (&priv->manager);
+	g_clear_object (&priv->dbus_connection);
+
+	nm_clear_pointer (&priv->bzobjs, g_hash_table_destroy);
 }
 
 static void
@@ -466,10 +2869,10 @@ nm_bluez_manager_class_init (NMBluezManagerClass *klass)
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS (klass);
 
-	object_class->dispose = dispose;
+	object_class->dispose     = dispose;
 
 	factory_class->get_supported_types = get_supported_types;
-	factory_class->create_device = create_device;
-	factory_class->match_connection = match_connection;
-	factory_class->start = start;
+	factory_class->create_device       = create_device;
+	factory_class->match_connection    = match_connection;
+	factory_class->start               = start;
 }
diff --git a/src/devices/bluetooth/nm-bluez-manager.h b/src/devices/bluetooth/nm-bluez-manager.h
new file mode 100644
index 00000000..85dbaa83
--- /dev/null
+++ b/src/devices/bluetooth/nm-bluez-manager.h
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2009 - 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_BLUEZ_MANAGER_H__
+#define __NM_BLUEZ_MANAGER_H__
+
+#define NM_TYPE_BLUEZ_MANAGER            (nm_bluez_manager_get_type ())
+#define NM_BLUEZ_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ_MANAGER, NMBluezManager))
+#define NM_BLUEZ_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass))
+#define NM_IS_BLUEZ_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ_MANAGER))
+#define NM_IS_BLUEZ_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_BLUEZ_MANAGER))
+#define NM_BLUEZ_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_BLUEZ_MANAGER, NMBluezManagerClass))
+
+typedef struct _NMBluezManager NMBluezManager;
+typedef struct _NMBluezManagerClass NMBluezManagerClass;
+
+GType nm_bluez_manager_get_type (void);
+
+typedef void (*NMBluezManagerConnectCb) (NMBluezManager *self,
+                                         gboolean is_completed /* or else is early notification with DUN path */,
+                                         const char *device_name,
+                                         GError *error,
+                                         gpointer user_data);
+
+gboolean nm_bluez_manager_connect (NMBluezManager *self,
+                                   const char *object_path,
+                                   NMBluetoothCapabilities connection_bt_type,
+                                   int timeout_msec,
+                                   GCancellable *cancellable,
+                                   NMBluezManagerConnectCb callback,
+                                   gpointer callback_user_data,
+                                   GError **error);
+
+void nm_bluez_manager_disconnect (NMBluezManager *self,
+                                  const char *object_path);
+
+#endif /* __NM_BLUEZ_MANAGER_H__ */
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.c b/src/devices/bluetooth/nm-bluez4-adapter.c
deleted file mode 100644
index bd230e90..00000000
--- a/src/devices/bluetooth/nm-bluez4-adapter.c
+++ /dev/null
@@ -1,456 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2009 - 2012 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-bluez4-adapter.h"
-
-#include "nm-dbus-interface.h"
-#include "nm-bluez-device.h"
-#include "nm-bluez-common.h"
-#include "nm-core-internal.h"
-#include "settings/nm-settings.h"
-
-/*****************************************************************************/
-
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
-	PROP_PATH,
-	PROP_ADDRESS,
-);
-
-enum {
-	INITIALIZED,
-	DEVICE_ADDED,
-	DEVICE_REMOVED,
-	LAST_SIGNAL,
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	char *path;
-	GDBusProxy *proxy;
-	GCancellable *proxy_cancellable;
-	gboolean initialized;
-
-	char *address;
-	GHashTable *devices;
-
-	/* Cached for devices */
-	NMSettings *settings;
-} NMBluez4AdapterPrivate;
-
-struct _NMBluez4Adapter {
-	GObject parent;
-	NMBluez4AdapterPrivate _priv;
-};
-
-struct _NMBluez4AdapterClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMBluez4Adapter, nm_bluez4_adapter, G_TYPE_OBJECT)
-
-#define NM_BLUEZ4_ADAPTER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluez4Adapter, NM_IS_BLUEZ4_ADAPTER)
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN      LOGD_BT
-#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez4-adapter", __VA_ARGS__)
-
-/*****************************************************************************/
-
-static void device_do_remove (NMBluez4Adapter *self, NMBluezDevice *device);
-
-/*****************************************************************************/
-
-const char *
-nm_bluez4_adapter_get_path (NMBluez4Adapter *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ4_ADAPTER (self), NULL);
-
-	return NM_BLUEZ4_ADAPTER_GET_PRIVATE (self)->path;
-}
-
-const char *
-nm_bluez4_adapter_get_address (NMBluez4Adapter *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ4_ADAPTER (self), NULL);
-
-	return NM_BLUEZ4_ADAPTER_GET_PRIVATE (self)->address;
-}
-
-gboolean
-nm_bluez4_adapter_get_initialized (NMBluez4Adapter *self)
-{
-	g_return_val_if_fail (NM_IS_BLUEZ4_ADAPTER (self), FALSE);
-
-	return NM_BLUEZ4_ADAPTER_GET_PRIVATE (self)->initialized;
-}
-
-GSList *
-nm_bluez4_adapter_get_devices (NMBluez4Adapter *self)
-{
-	GSList *devices = NULL;
-	GHashTableIter iter;
-	NMBluezDevice *device;
-
-	g_hash_table_iter_init (&iter, NM_BLUEZ4_ADAPTER_GET_PRIVATE (self)->devices);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &device)) {
-		if (nm_bluez_device_get_usable (device))
-			devices = g_slist_append (devices, device);
-	}
-	return devices;
-}
-
-static void
-emit_device_removed (NMBluez4Adapter *self, NMBluezDevice *device)
-{
-	_LOGD ("(%s): bluez device now unusable",
-	       nm_bluez_device_get_path (device));
-	g_signal_emit (self, signals[DEVICE_REMOVED], 0, device);
-}
-
-static void
-device_usable (NMBluezDevice *device, GParamSpec *pspec, gpointer user_data)
-{
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
-
-	if (nm_bluez_device_get_usable (device)) {
-		_LOGD ("(%s): bluez device now usable (device address is %s)",
-		       nm_bluez_device_get_path (device),
-		       nm_bluez_device_get_address (device));
-		g_signal_emit (self, signals[DEVICE_ADDED], 0, device);
-	} else
-		emit_device_removed (self, device);
-}
-
-static void
-device_initialized (NMBluezDevice *device, gboolean success, gpointer user_data)
-{
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
-
-	_LOGD ("(%s): bluez device %s",
-	       nm_bluez_device_get_path (device),
-	       success ? "initialized" : "failed to initialize");
-	if (!success)
-		device_do_remove (self, device);
-}
-
-static void
-device_do_remove (NMBluez4Adapter *self, NMBluezDevice *device)
-{
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-
-	if (g_hash_table_remove (priv->devices, nm_bluez_device_get_path (device))) {
-		g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_initialized), self);
-		g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_usable), self);
-
-		if (nm_bluez_device_get_usable (device))
-			emit_device_removed (self, device);
-
-		g_object_unref (device);
-	}
-}
-
-static void
-device_created (GDBusProxy *proxy, const char *path, gpointer user_data)
-{
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-	NMBluezDevice *device;
-
-	device = nm_bluez_device_new (path, priv->address, priv->settings, 4);
-	g_signal_connect (device, NM_BLUEZ_DEVICE_INITIALIZED, G_CALLBACK (device_initialized), self);
-	g_signal_connect (device, "notify::" NM_BLUEZ_DEVICE_USABLE, G_CALLBACK (device_usable), self);
-	g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device);
-
-	_LOGD ("(%s): new bluez device found", path);
-}
-
-static void
-device_removed (GDBusProxy *proxy, const char *path, gpointer user_data)
-{
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-	NMBluezDevice *device;
-
-	_LOGD ("(%s): bluez device removed", path);
-
-	device = g_hash_table_lookup (priv->devices, path);
-	if (device)
-		device_do_remove (self, device);
-}
-
-static void
-get_properties_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMBluez4Adapter *self;
-	NMBluez4AdapterPrivate *priv;
-	gs_free_error GError *error = NULL;
-	GVariant *ret, *properties;
-	char **devices;
-	int i;
-
-	ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result,
-	                                  G_VARIANT_TYPE ("(a{sv})"), &error);
-
-	if (   !ret
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = NM_BLUEZ4_ADAPTER (user_data);
-	priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-
-	g_clear_object (&priv->proxy_cancellable);
-
-	if (!ret) {
-		g_dbus_error_strip_remote_error (error);
-		_LOGW ("bluez error getting adapter properties: %s", error->message);
-		goto done;
-	}
-
-	properties = g_variant_get_child_value (ret, 0);
-
-	(void) g_variant_lookup (properties, "Address", "s", &priv->address);
-	if (g_variant_lookup (properties, "Devices", "^ao", &devices)) {
-		for (i = 0; devices[i]; i++)
-			device_created (priv->proxy, devices[i], self);
-		g_strfreev (devices);
-	}
-
-	g_variant_unref (properties);
-	g_variant_unref (ret);
-
-	priv->initialized = TRUE;
-
-done:
-	g_signal_emit (self, signals[INITIALIZED], 0, priv->initialized);
-}
-
-static void
-_proxy_new_cb (GObject *source_object,
-               GAsyncResult *result,
-               gpointer user_data)
-{
-	NMBluez4Adapter *self;
-	NMBluez4AdapterPrivate *priv;
-	gs_free_error GError *error = NULL;
-	GDBusProxy *proxy;
-
-	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
-	if (   !proxy
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = user_data;
-	priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-
-	if (!proxy) {
-		_LOGW ("bluez error creating D-Bus proxy: %s", error->message);
-		g_clear_object (&priv->proxy_cancellable);
-		g_signal_emit (self, signals[INITIALIZED], 0, priv->initialized);
-		return;
-	}
-
-	priv->proxy = proxy;
-
-	_nm_dbus_signal_connect (priv->proxy, "DeviceCreated", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (device_created), self);
-	_nm_dbus_signal_connect (priv->proxy, "DeviceRemoved", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (device_removed), self);
-
-	g_dbus_proxy_call (priv->proxy, "GetProperties",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE, -1,
-	                   priv->proxy_cancellable,
-	                   get_properties_cb,
-	                   self);
-}
-
-/*****************************************************************************/
-
-static gboolean
-_find_all (gpointer key, gpointer value, gpointer user_data)
-{
-	return TRUE;
-}
-
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id,
-              GValue *value, GParamSpec *pspec)
-{
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE ((NMBluez4Adapter *) object);
-
-	switch (prop_id) {
-	case PROP_PATH:
-		g_value_set_string (value, priv->path);
-		break;
-	case PROP_ADDRESS:
-		g_value_set_string (value, priv->address);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE ((NMBluez4Adapter *) object);
-
-	switch (prop_id) {
-	case PROP_PATH:
-		/* construct-only */
-		priv->path = g_value_dup_string (value);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-/*****************************************************************************/
-
-static void
-nm_bluez4_adapter_init (NMBluez4Adapter *self)
-{
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-
-	priv->devices = g_hash_table_new_full (nm_str_hash, g_str_equal,
-	                                       NULL, NULL);
-}
-
-NMBluez4Adapter *
-nm_bluez4_adapter_new (const char *path, NMSettings *settings)
-{
-	NMBluez4Adapter *self;
-	NMBluez4AdapterPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (settings), NULL);
-
-	self = (NMBluez4Adapter *) g_object_new (NM_TYPE_BLUEZ4_ADAPTER,
-	                                         NM_BLUEZ4_ADAPTER_PATH, path,
-	                                         NULL);
-	priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-
-	priv->settings = g_object_ref (settings);
-
-	priv->proxy_cancellable = g_cancellable_new ();
-
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-	                          NULL,
-	                          NM_BLUEZ_SERVICE,
-	                          priv->path,
-	                          NM_BLUEZ4_ADAPTER_INTERFACE,
-	                          priv->proxy_cancellable,
-	                          _proxy_new_cb,
-	                          self);
-	return self;
-}
-
-static void
-dispose (GObject *object)
-{
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (object);
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-	NMBluezDevice *device;
-
-	nm_clear_g_cancellable (&priv->proxy_cancellable);
-
-	while ((device = g_hash_table_find (priv->devices, _find_all, NULL)))
-		device_do_remove (self, device);
-
-	if (priv->proxy) {
-		g_signal_handlers_disconnect_by_data (priv->proxy, self);
-		g_clear_object (&priv->proxy);
-	}
-
-	G_OBJECT_CLASS (nm_bluez4_adapter_parent_class)->dispose (object);
-}
-
-static void
-finalize (GObject *object)
-{
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (object);
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-
-	g_hash_table_destroy (priv->devices);
-	g_free (priv->address);
-	g_free (priv->path);
-
-	G_OBJECT_CLASS (nm_bluez4_adapter_parent_class)->finalize (object);
-
-	g_object_unref (priv->settings);
-}
-
-static void
-nm_bluez4_adapter_class_init (NMBluez4AdapterClass *config_class)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
-
-	object_class->get_property = get_property;
-	object_class->set_property = set_property;
-	object_class->dispose = dispose;
-	object_class->finalize = finalize;
-
-	obj_properties[PROP_PATH] =
-	     g_param_spec_string (NM_BLUEZ4_ADAPTER_PATH, "", "",
-	                          NULL,
-	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_ADDRESS] =
-	     g_param_spec_string (NM_BLUEZ4_ADAPTER_ADDRESS, "", "",
-	                          NULL,
-	                          G_PARAM_READABLE |
-	                          G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	signals[INITIALIZED] = g_signal_new (NM_BLUEZ4_ADAPTER_INITIALIZED,
-	                                     G_OBJECT_CLASS_TYPE (object_class),
-	                                     G_SIGNAL_RUN_LAST,
-	                                     0,
-	                                     NULL, NULL,
-	                                     g_cclosure_marshal_VOID__BOOLEAN,
-	                                     G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
-
-	signals[DEVICE_ADDED] = g_signal_new (NM_BLUEZ4_ADAPTER_DEVICE_ADDED,
-	                                      G_OBJECT_CLASS_TYPE (object_class),
-	                                      G_SIGNAL_RUN_LAST,
-	                                      0,
-	                                      NULL, NULL,
-	                                      g_cclosure_marshal_VOID__OBJECT,
-	                                      G_TYPE_NONE, 1, G_TYPE_OBJECT);
-
-	signals[DEVICE_REMOVED] = g_signal_new (NM_BLUEZ4_ADAPTER_DEVICE_REMOVED,
-	                                        G_OBJECT_CLASS_TYPE (object_class),
-	                                        G_SIGNAL_RUN_LAST,
-	                                        0,
-	                                        NULL, NULL,
-	                                        g_cclosure_marshal_VOID__OBJECT,
-	                                        G_TYPE_NONE, 1, G_TYPE_OBJECT);
-}
-
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.h b/src/devices/bluetooth/nm-bluez4-adapter.h
deleted file mode 100644
index 82bd2de8..00000000
--- a/src/devices/bluetooth/nm-bluez4-adapter.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2009 - 2012 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_BLUEZ4_ADAPTER_H__
-#define __NETWORKMANAGER_BLUEZ4_ADAPTER_H__
-
-#include "nm-bluez-device.h"
-
-#define NM_TYPE_BLUEZ4_ADAPTER            (nm_bluez4_adapter_get_type ())
-#define NM_BLUEZ4_ADAPTER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ4_ADAPTER, NMBluez4Adapter))
-#define NM_BLUEZ4_ADAPTER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ4_ADAPTER, NMBluez4AdapterClass))
-#define NM_IS_BLUEZ4_ADAPTER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ4_ADAPTER))
-#define NM_IS_BLUEZ4_ADAPTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ4_ADAPTER))
-#define NM_BLUEZ4_ADAPTER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ4_ADAPTER, NMBluez4AdapterClass))
-
-/* Properties */
-#define NM_BLUEZ4_ADAPTER_PATH    "path"
-#define NM_BLUEZ4_ADAPTER_ADDRESS "address"
-
-/* Signals */
-#define NM_BLUEZ4_ADAPTER_INITIALIZED      "initialized"
-#define NM_BLUEZ4_ADAPTER_DEVICE_ADDED     "device-added"
-#define NM_BLUEZ4_ADAPTER_DEVICE_REMOVED   "device-removed"
-
-typedef struct _NMBluez4Adapter NMBluez4Adapter;
-typedef struct _NMBluez4AdapterClass NMBluez4AdapterClass;
-
-GType nm_bluez4_adapter_get_type (void);
-
-NMBluez4Adapter *nm_bluez4_adapter_new (const char *path,
-                                        NMSettings *settings);
-
-const char *nm_bluez4_adapter_get_path (NMBluez4Adapter *self);
-
-const char *nm_bluez4_adapter_get_address (NMBluez4Adapter *self);
-
-gboolean nm_bluez4_adapter_get_initialized (NMBluez4Adapter *self);
-
-GSList *nm_bluez4_adapter_get_devices (NMBluez4Adapter *self);
-
-#endif /* __NETWORKMANAGER_BLUEZ4_ADAPTER_H__ */
diff --git a/src/devices/bluetooth/nm-bluez4-manager.c b/src/devices/bluetooth/nm-bluez4-manager.c
deleted file mode 100644
index 8327776d..00000000
--- a/src/devices/bluetooth/nm-bluez4-manager.c
+++ /dev/null
@@ -1,355 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2007 - 2008 Novell, Inc.
- * Copyright (C) 2007 - 2013 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-bluez4-manager.h"
-
-#include <signal.h>
-#include <stdlib.h>
-
-#include "nm-bluez4-adapter.h"
-#include "nm-bluez-common.h"
-#include "nm-core-internal.h"
-#include "settings/nm-settings.h"
-
-/*****************************************************************************/
-
-enum {
-	BDADDR_ADDED,
-	LAST_SIGNAL,
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	gulong name_owner_changed_id;
-
-	NMSettings *settings;
-
-	GDBusProxy *proxy;
-	GCancellable *proxy_cancellable;
-
-	NMBluez4Adapter *adapter;
-} NMBluez4ManagerPrivate;
-
-struct _NMBluez4Manager {
-	GObject parent;
-	NMBluez4ManagerPrivate _priv;
-};
-
-struct _NMBluez4ManagerClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMBluez4Manager, nm_bluez4_manager, G_TYPE_OBJECT)
-
-#define NM_BLUEZ4_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluez4Manager, NM_IS_BLUEZ4_MANAGER)
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN      LOGD_BT
-#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez4-manager", __VA_ARGS__)
-
-/*****************************************************************************/
-
-static void
-emit_bdaddr_added (NMBluez4Manager *self, NMBluezDevice *device)
-{
-	g_signal_emit (self, signals[BDADDR_ADDED], 0,
-	               device,
-	               nm_bluez_device_get_address (device),
-	               nm_bluez_device_get_name (device),
-	               nm_bluez_device_get_path (device),
-	               nm_bluez_device_get_capabilities (device));
-}
-
-void
-nm_bluez4_manager_query_devices (NMBluez4Manager *self)
-{
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-	GSList *devices, *iter;
-
-	if (!priv->adapter)
-		return;
-
-	devices = nm_bluez4_adapter_get_devices (priv->adapter);
-	for (iter = devices; iter; iter = g_slist_next (iter))
-		emit_bdaddr_added (self, NM_BLUEZ_DEVICE (iter->data));
-	g_slist_free (devices);
-}
-
-static void
-device_added (NMBluez4Adapter *adapter, NMBluezDevice *device, gpointer user_data)
-{
-	emit_bdaddr_added (NM_BLUEZ4_MANAGER (user_data), device);
-}
-
-static void
-device_removed (NMBluez4Adapter *adapter, NMBluezDevice *device, gpointer user_data)
-{
-	/* Re-emit the signal on the device for now; flatten this later */
-	g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED);
-}
-
-static void
-adapter_initialized (NMBluez4Adapter *adapter, gboolean success, gpointer user_data)
-{
-	NMBluez4Manager *self = NM_BLUEZ4_MANAGER (user_data);
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-
-	if (success) {
-		GSList *devices, *iter;
-
-		devices = nm_bluez4_adapter_get_devices (adapter);
-		for (iter = devices; iter; iter = g_slist_next (iter))
-			emit_bdaddr_added (self, NM_BLUEZ_DEVICE (iter->data));
-		g_slist_free (devices);
-
-		g_signal_connect (adapter, NM_BLUEZ4_ADAPTER_DEVICE_ADDED,
-		                  G_CALLBACK (device_added), self);
-		g_signal_connect (adapter, NM_BLUEZ4_ADAPTER_DEVICE_REMOVED,
-		                  G_CALLBACK (device_removed), self);
-	} else {
-		g_object_unref (priv->adapter);
-		priv->adapter = NULL;
-	}
-}
-
-static void
-adapter_removed (GDBusProxy *proxy, const char *path, NMBluez4Manager *self)
-{
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-
-	if (priv->adapter && !strcmp (path, nm_bluez4_adapter_get_path (priv->adapter))) {
-		if (nm_bluez4_adapter_get_initialized (priv->adapter)) {
-			GSList *devices, *iter;
-
-			devices = nm_bluez4_adapter_get_devices (priv->adapter);
-			for (iter = devices; iter; iter = g_slist_next (iter))
-				g_signal_emit_by_name (NM_BLUEZ_DEVICE (iter->data), NM_BLUEZ_DEVICE_REMOVED);
-			g_slist_free (devices);
-		}
-
-		g_object_unref (priv->adapter);
-		priv->adapter = NULL;
-	}
-}
-
-static void
-default_adapter_changed (GDBusProxy *proxy, const char *path, NMBluez4Manager *self)
-{
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-	const char *cur_path = NULL;
-
-	if (priv->adapter)
-		cur_path = nm_bluez4_adapter_get_path (priv->adapter);
-
-	if (cur_path) {
-		if (!path || strcmp (path, cur_path)) {
-			/* Default adapter changed */
-			adapter_removed (priv->proxy, cur_path, self);
-		} else {
-			/* This adapter is already the default */
-			return;
-		}
-	}
-
-	/* Add the new default adapter */
-	if (path) {
-		priv->adapter = nm_bluez4_adapter_new (path, priv->settings);
-		g_signal_connect (priv->adapter, NM_BLUEZ4_ADAPTER_INITIALIZED,
-		                  G_CALLBACK (adapter_initialized), self);
-	}
-}
-
-static void
-default_adapter_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMBluez4Manager *self;
-	NMBluez4ManagerPrivate *priv;
-	gs_unref_variant GVariant *ret = NULL;
-	gs_free_error GError *error = NULL;
-	const char *default_adapter;
-
-	ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result,
-	                                  G_VARIANT_TYPE ("(o)"), &error);
-	if (   !ret
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = NM_BLUEZ4_MANAGER (user_data);
-	priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-
-	g_clear_object (&priv->proxy_cancellable);
-
-	if (!ret) {
-		/* Ignore "No such adapter" errors; just means bluetooth isn't active */
-		if (   !_nm_dbus_error_has_name (error, "org.bluez.Error.NoSuchAdapter")
-		    && !_nm_dbus_error_has_name (error, "org.freedesktop.systemd1.LoadFailed")
-		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
-			g_dbus_error_strip_remote_error (error);
-			_LOGW ("bluez error getting default adapter: %s",
-			       error->message);
-		}
-		return;
-	}
-
-	g_variant_get (ret, "(&o)", &default_adapter);
-	default_adapter_changed (priv->proxy, default_adapter, self);
-}
-
-static void
-name_owner_changed (NMBluez4Manager *self)
-{
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-	gs_free char *owner = NULL;
-
-	nm_clear_g_cancellable (&priv->proxy_cancellable);
-
-	owner = g_dbus_proxy_get_name_owner (priv->proxy);
-	if (!owner) {
-		/* Throwing away the adapter removes all devices too */
-		g_clear_object (&priv->adapter);
-		return;
-	}
-
-	priv->proxy_cancellable = g_cancellable_new ();
-
-	g_dbus_proxy_call (priv->proxy, "DefaultAdapter",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE, -1,
-	                   priv->proxy_cancellable,
-	                   default_adapter_cb,
-	                   self);
-}
-
-static void
-name_owner_changed_cb (GObject *object,
-                       GParamSpec *pspec,
-                       gpointer user_data)
-{
-	name_owner_changed (user_data);
-}
-
-static void
-_proxy_new_cb (GObject *source_object,
-               GAsyncResult *result,
-               gpointer user_data)
-{
-	NMBluez4Manager *self;
-	NMBluez4ManagerPrivate *priv;
-	gs_free_error GError *error = NULL;
-	GDBusProxy *proxy;
-
-	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
-	if (   !proxy
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = user_data;
-	priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-
-	if (!proxy) {
-		_LOGW ("bluez error creating D-Bus proxy: %s", error->message);
-		g_clear_object (&priv->proxy_cancellable);
-		return;
-	}
-
-	priv->proxy = proxy;
-
-	_nm_dbus_signal_connect (priv->proxy, "AdapterRemoved", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (adapter_removed), self);
-	_nm_dbus_signal_connect (priv->proxy, "DefaultAdapterChanged", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (default_adapter_changed), self);
-	g_signal_connect (priv->proxy, "notify::g-name-owner",
-	                  G_CALLBACK (name_owner_changed_cb), self);
-
-	name_owner_changed (self);
-}
-
-/*****************************************************************************/
-
-static void
-nm_bluez4_manager_init (NMBluez4Manager *self)
-{
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-
-	priv->proxy_cancellable = g_cancellable_new ();
-
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-	                          NULL,
-	                          NM_BLUEZ_SERVICE,
-	                          NM_BLUEZ_MANAGER_PATH,
-	                          NM_BLUEZ4_MANAGER_INTERFACE,
-	                          priv->proxy_cancellable,
-	                          _proxy_new_cb,
-	                          self);
-}
-
-NMBluez4Manager *
-nm_bluez4_manager_new (NMSettings *settings)
-{
-	NMBluez4Manager *instance;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (settings), NULL);
-
-	instance = g_object_new (NM_TYPE_BLUEZ4_MANAGER, NULL);
-	NM_BLUEZ4_MANAGER_GET_PRIVATE (instance)->settings = g_object_ref (settings);
-	return instance;
-}
-
-static void
-dispose (GObject *object)
-{
-	NMBluez4Manager *self = NM_BLUEZ4_MANAGER (object);
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-
-	nm_clear_g_cancellable (&priv->proxy_cancellable);
-
-	if (priv->proxy) {
-		g_signal_handlers_disconnect_by_data (priv->proxy, self);
-		g_clear_object (&priv->proxy);
-	}
-
-	g_clear_object (&priv->adapter);
-
-	G_OBJECT_CLASS (nm_bluez4_manager_parent_class)->dispose (object);
-
-	g_clear_object (&priv->settings);
-}
-
-static void
-nm_bluez4_manager_class_init (NMBluez4ManagerClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->dispose = dispose;
-
-	signals[BDADDR_ADDED] =
-	    g_signal_new (NM_BLUEZ_MANAGER_BDADDR_ADDED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING,
-	                  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);
-}
-
diff --git a/src/devices/bluetooth/nm-bluez4-manager.h b/src/devices/bluetooth/nm-bluez4-manager.h
deleted file mode 100644
index f46379ff..00000000
--- a/src/devices/bluetooth/nm-bluez4-manager.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2007 - 2008 Novell, Inc.
- * Copyright (C) 2007 - 2013 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_BLUEZ4_MANAGER_H__
-#define __NETWORKMANAGER_BLUEZ4_MANAGER_H__
-
-#define NM_TYPE_BLUEZ4_MANAGER            (nm_bluez4_manager_get_type ())
-#define NM_BLUEZ4_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ4_MANAGER, NMBluez4Manager))
-#define NM_BLUEZ4_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ4_MANAGER, NMBluez4ManagerClass))
-#define NM_IS_BLUEZ4_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ4_MANAGER))
-#define NM_IS_BLUEZ4_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ4_MANAGER))
-#define NM_BLUEZ4_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ4_MANAGER, NMBluez4ManagerClass))
-
-typedef struct _NMBluez4Manager NMBluez4Manager;
-typedef struct _NMBluez4ManagerClass NMBluez4ManagerClass;
-
-GType nm_bluez4_manager_get_type (void);
-
-NMBluez4Manager *nm_bluez4_manager_new (NMSettings *settings);
-
-void nm_bluez4_manager_query_devices (NMBluez4Manager *manager);
-
-#endif /* __NETWORKMANAGER_BLUEZ4_MANAGER_H__ */
diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/devices/bluetooth/nm-bluez5-dun.c
index b9a1fa0a..af463d1a 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/devices/bluetooth/nm-bluez5-dun.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
@@ -32,145 +18,407 @@
 #include "nm-bt-error.h"
 #include "NetworkManagerUtils.h"
 
+#define RFCOMM_FMT "/dev/rfcomm%d"
+
+/*****************************************************************************/
+
+typedef struct {
+	GCancellable *cancellable;
+	NMBluez5DunConnectCb callback;
+	gpointer callback_user_data;
+
+	sdp_session_t *sdp_session;
+
+	GError *rfcomm_sdp_search_error;
+
+	gint64 connect_open_tty_started_at;
+
+	gulong cancelled_id;
+
+	guint source_id;
+
+	guint8 sdp_session_try_count;
+} ConnectData;
+
 struct _NMBluez5DunContext {
+	const char *dst_str;
+
+	ConnectData *cdat;
+
+	NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb;
+	gpointer notify_tty_hangup_user_data;
+
+	char *rfcomm_tty_path;
+
+	int rfcomm_sock_fd;
+	int rfcomm_tty_fd;
+	int rfcomm_tty_no;
+	int rfcomm_channel;
+
+	guint rfcomm_tty_poll_id;
+
 	bdaddr_t src;
 	bdaddr_t dst;
-	char *src_str;
-	char *dst_str;
-	int rfcomm_channel;
-	int rfcomm_fd;
-	int rfcomm_tty_fd;
-	int rfcomm_id;
-	NMBluez5DunFunc callback;
-	gpointer user_data;
-	sdp_session_t *sdp_session;
-	guint sdp_watch_id;
+
+	char src_str[];
 };
 
-static void
-dun_connect (NMBluez5DunContext *context)
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_BT
+#define _NMLOG_PREFIX_NAME "bluez"
+#define _NMLOG(level, context, ...) \
+    G_STMT_START { \
+        if (nm_logging_enabled ((level), (_NMLOG_DOMAIN))) { \
+            const NMBluez5DunContext *const _context = (context); \
+            \
+            _nm_log ((level), (_NMLOG_DOMAIN), 0, NULL, NULL, \
+                     "%s: DUN[%s] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME, \
+                     _context->src_str \
+                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*****************************************************************************/
+
+static void _context_invoke_callback_success (NMBluez5DunContext *context);
+static void _context_invoke_callback_fail_and_free (NMBluez5DunContext *context,
+                                                    GError *error);
+static void _context_free (NMBluez5DunContext *context);
+static int _connect_open_tty (NMBluez5DunContext *context);
+static gboolean _connect_sdp_session_start (NMBluez5DunContext *context,
+                                            GError **error);
+
+/*****************************************************************************/
+
+NM_AUTO_DEFINE_FCN0 (NMBluez5DunContext *, _nm_auto_free_context, _context_free)
+#define nm_auto_free_context nm_auto(_nm_auto_free_context)
+
+/*****************************************************************************/
+
+const char *
+nm_bluez5_dun_context_get_adapter (const NMBluez5DunContext *context)
 {
-	struct sockaddr_rc sa;
-	int devid, try = 30;
-	char tty[100];
-	const int ttylen = sizeof (tty) - 1;
-	GError *error = NULL;
-	int errsv;
+	return context->src_str;
+}
 
-	struct rfcomm_dev_req req = {
-		.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP),
-		.dev_id = -1,
-		.channel = context->rfcomm_channel
-	};
+const char *
+nm_bluez5_dun_context_get_remote (const NMBluez5DunContext *context)
+{
+	return context->dst_str;
+}
 
-	context->rfcomm_fd = socket (AF_BLUETOOTH, SOCK_STREAM | SOCK_CLOEXEC, BTPROTO_RFCOMM);
-	if (context->rfcomm_fd < 0) {
-		errsv = errno;
-		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Failed to create RFCOMM socket: (%d) %s",
-		                     errsv, nm_strerror_native (errsv));
-		goto done;
-	}
+const char *
+nm_bluez5_dun_context_get_rfcomm_dev (const NMBluez5DunContext *context)
+{
+	return context->rfcomm_tty_path;
+}
 
-	/* Connect to the remote device */
-	sa.rc_family = AF_BLUETOOTH;
-	sa.rc_channel = 0;
-	memcpy (&sa.rc_bdaddr, &context->src, ETH_ALEN);
-	if (bind (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof(sa))) {
-		errsv = errno;
-		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Failed to bind socket: (%d) %s",
-		                     errsv, nm_strerror_native (errsv));
-		goto done;
+/*****************************************************************************/
+
+static gboolean
+_rfcomm_tty_poll_cb (GIOChannel *stream,
+                     GIOCondition condition,
+                     gpointer user_data)
+{
+	NMBluez5DunContext *context = user_data;
+
+	_LOGD (context, "receive %s%s%s signal on rfcomm file descriptor",
+	       NM_FLAGS_HAS (condition, G_IO_ERR)            ? "ERR" : "",
+	       NM_FLAGS_ALL (condition, G_IO_HUP | G_IO_ERR) ? ","   : "",
+	       NM_FLAGS_HAS (condition, G_IO_HUP)            ? "HUP" : "");
+
+	context->rfcomm_tty_poll_id = 0;
+	context->notify_tty_hangup_cb (context,
+	                               context->notify_tty_hangup_user_data);
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_connect_open_tty_retry_cb (gpointer user_data)
+{
+	NMBluez5DunContext *context = user_data;
+	int r;
+
+	r = _connect_open_tty (context);
+	if (r >= 0)
+		return G_SOURCE_REMOVE;
+
+	if (nm_utils_get_monotonic_timestamp_ns () > context->cdat->connect_open_tty_started_at + (30 * 100 * NM_UTILS_NS_PER_MSEC)) {
+		gs_free_error GError *error = NULL;
+
+		context->cdat->source_id = 0;
+		g_set_error (&error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "give up waiting to open %s device: %s (%d)",
+		             context->rfcomm_tty_path,
+		             nm_strerror_native (r),
+		             -r);
+		_context_invoke_callback_fail_and_free (context, error);
+		return G_SOURCE_REMOVE;
 	}
 
-	sa.rc_channel = context->rfcomm_channel;
-	memcpy (&sa.rc_bdaddr, &context->dst, ETH_ALEN);
-	if (connect (context->rfcomm_fd, (struct sockaddr *) &sa, sizeof (sa)) ) {
-		errsv = errno;
-		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Failed to connect to remote device: (%d) %s",
-		                     errsv, nm_strerror_native (errsv));
-		goto done;
+	return G_SOURCE_CONTINUE;
+}
+
+static int
+_connect_open_tty (NMBluez5DunContext *context)
+{
+	nm_auto_unref_io_channel GIOChannel *io_channel = NULL;
+	int fd;
+	int errsv;
+
+	fd = open (context->rfcomm_tty_path, O_RDONLY | O_NOCTTY | O_CLOEXEC);
+	if (fd < 0) {
+		errsv = NM_ERRNO_NATIVE (errno);
+
+		if (context->cdat->source_id == 0) {
+			_LOGD (context, "failed opening tty "RFCOMM_FMT": %s (%d). Start polling...",
+			       context->rfcomm_tty_no,
+			       nm_strerror_native (errsv),
+			       errsv);
+			context->cdat->connect_open_tty_started_at = nm_utils_get_monotonic_timestamp_ns ();
+			context->cdat->source_id = g_timeout_add (100,
+			                                          _connect_open_tty_retry_cb,
+			                                          context);
+		}
+		return -errsv;
 	}
 
-	nm_log_dbg (LOGD_BT, "(%s): connected to %s on channel %d",
-	            context->src_str, context->dst_str, context->rfcomm_channel);
+	context->rfcomm_tty_fd = fd;
+
+	io_channel = g_io_channel_unix_new (context->rfcomm_tty_fd);
+	context->rfcomm_tty_poll_id = g_io_add_watch (io_channel,
+	                                              G_IO_ERR | G_IO_HUP,
+	                                              _rfcomm_tty_poll_cb,
+	                                              context);
+
+	_context_invoke_callback_success (context);
+	return 0;
+}
+
+static void
+_connect_create_rfcomm (NMBluez5DunContext *context)
+{
+	gs_free_error GError *error = NULL;
+	struct rfcomm_dev_req req;
+	int devid;
+	int errsv;
+	int r;
+
+	_LOGD (context, "connected to %s on channel %d",
+	       context->dst_str, context->rfcomm_channel);
 
 	/* Create an RFCOMM kernel device for the DUN channel */
+	memset (&req, 0, sizeof (req));
+	req.dev_id  = -1;
+	req.flags   = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP);
+	req.channel = context->rfcomm_channel;
 	memcpy (&req.src, &context->src, ETH_ALEN);
 	memcpy (&req.dst, &context->dst, ETH_ALEN);
-	devid = ioctl (context->rfcomm_fd, RFCOMMCREATEDEV, &req);
+	devid = ioctl (context->rfcomm_sock_fd, RFCOMMCREATEDEV, &req);
 	if (devid < 0) {
-		errsv = errno;
-		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Failed to create rfcomm device: (%d) %s",
-		                     errsv, nm_strerror_native (errsv));
-		goto done;
+		errsv = NM_ERRNO_NATIVE (errno);
+		if (errsv == EBADFD) {
+			/* hm. We use a non-blocking socket to connect. Above getsockopt(SOL_SOCKET,SO_ERROR) indicated
+			 * success, but still now we fail with EBADFD. I think that is a bug and we should get the
+			 * failure during connect().
+			 *
+			 * Anyway, craft a less confusing error message than
+			 * "failed to create rfcomm device: File descriptor in bad state (77)". */
+			g_set_error (&error,
+			             NM_BT_ERROR,
+			             NM_BT_ERROR_DUN_CONNECT_FAILED,
+			             "unknown failure to connect to DUN device");
+		} else {
+			g_set_error (&error,
+			             NM_BT_ERROR,
+			             NM_BT_ERROR_DUN_CONNECT_FAILED,
+			             "failed to create rfcomm device: %s (%d)",
+			             nm_strerror_native (errsv), errsv);
+		}
+		_context_invoke_callback_fail_and_free (context, error);
+		return;
 	}
-	context->rfcomm_id = devid;
 
-	snprintf (tty, ttylen, "/dev/rfcomm%d", devid);
-	while ((context->rfcomm_tty_fd = open (tty, O_RDONLY | O_NOCTTY | O_CLOEXEC)) < 0 && try--) {
-		if (try) {
-			g_usleep (100 * 1000);
-			continue;
-		}
+	context->rfcomm_tty_no = devid;
+	context->rfcomm_tty_path = g_strdup_printf (RFCOMM_FMT, devid);
+
+	r = _connect_open_tty (context);
+	if (r < 0) {
+		/* we created the rfcomm device, but cannot yet open it. That means, we are
+		 * not yet fully connected. However, we notify the caller about "what we learned
+		 * so far". Note that this happens synchronously.
+		 *
+		 * The purpose is that once we proceed synchrnously, modem-manager races with
+		 * the detection of the modem. We want to notify the caller first about the
+		 * device name. */
+		context->cdat->callback (NULL,
+		                         context->rfcomm_tty_path,
+		                         NULL,
+		                         context->cdat->callback_user_data);
+	}
+}
 
-		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Failed to find rfcomm device: %s",
-		                     tty);
-		break;
+static gboolean
+_connect_socket_connect_cb (GIOChannel *stream,
+                            GIOCondition condition,
+                            gpointer user_data)
+{
+	NMBluez5DunContext *context = user_data;
+	gs_free_error GError *error = NULL;
+	int errsv = 0;
+	socklen_t slen = sizeof(errsv);
+	int r;
+
+	context->cdat->source_id = 0;
+
+	r = getsockopt (context->rfcomm_sock_fd, SOL_SOCKET, SO_ERROR, &errsv, &slen);
+
+	if (r < 0) {
+		errsv = errno;
+		g_set_error (&error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "failed to complete connecting RFCOMM socket: %s (%d)",
+		             nm_strerror_native (errsv), errsv);
+		_context_invoke_callback_fail_and_free (context, error);
+		return G_SOURCE_REMOVE;
 	}
 
-done:
-	context->callback (context, tty, error, context->user_data);
+	if (errsv != 0) {
+		g_set_error (&error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "failed to connect RFCOMM socket: %s (%d)",
+		             nm_strerror_native (errsv), errsv);
+		_context_invoke_callback_fail_and_free (context, error);
+		return G_SOURCE_REMOVE;
+	}
+
+	_connect_create_rfcomm (context);
+	return G_SOURCE_REMOVE;
 }
 
 static void
-sdp_search_cleanup (NMBluez5DunContext *context)
+_connect_socket_connect (NMBluez5DunContext *context)
 {
-	if (context->sdp_session) {
-		sdp_close (context->sdp_session);
-		context->sdp_session = NULL;
+	gs_free_error GError *error = NULL;
+	struct sockaddr_rc sa;
+	int errsv;
+
+	context->rfcomm_sock_fd = socket (AF_BLUETOOTH, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_RFCOMM);
+	if (context->rfcomm_sock_fd < 0) {
+		errsv = errno;
+		g_set_error (&error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "failed to create RFCOMM socket: %s (%d)",
+		             nm_strerror_native (errsv), errsv);
+		_context_invoke_callback_fail_and_free (context, error);
+		return;
+	}
+
+	/* Connect to the remote device */
+	memset (&sa, 0, sizeof (sa));
+	sa.rc_family = AF_BLUETOOTH;
+	sa.rc_channel = 0;
+	memcpy (&sa.rc_bdaddr, &context->src, ETH_ALEN);
+	if (bind (context->rfcomm_sock_fd,
+	          (struct sockaddr *) &sa,
+	          sizeof(sa)) != 0) {
+		errsv = errno;
+		g_set_error (&error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "failed to bind socket: %s (%d)",
+		             nm_strerror_native (errsv), errsv);
+		_context_invoke_callback_fail_and_free (context, error);
+		return;
+	}
+
+	memset (&sa, 0, sizeof (sa));
+	sa.rc_family = AF_BLUETOOTH;
+	sa.rc_channel = context->rfcomm_channel;
+	memcpy (&sa.rc_bdaddr, &context->dst, ETH_ALEN);
+	if (connect (context->rfcomm_sock_fd,
+	             (struct sockaddr *) &sa,
+	             sizeof (sa)) != 0) {
+		nm_auto_unref_io_channel GIOChannel *io_channel = NULL;
+
+		errsv = errno;
+		if (errsv != EINPROGRESS) {
+			g_set_error (&error,
+			             NM_BT_ERROR,
+			             NM_BT_ERROR_DUN_CONNECT_FAILED,
+			             "failed to connect to remote device: %s (%d)",
+			             nm_strerror_native (errsv), errsv);
+			_context_invoke_callback_fail_and_free (context, error);
+			return;
+		}
+
+		_LOGD (context, "connecting to %s on channel %d...",
+		       context->dst_str,
+		       context->rfcomm_channel);
+
+		io_channel = g_io_channel_unix_new (context->rfcomm_sock_fd);
+		context->cdat->source_id = g_io_add_watch (io_channel,
+		                                           G_IO_OUT,
+		                                           _connect_socket_connect_cb,
+		                                           context);
+		return;
 	}
 
-	nm_clear_g_source (&context->sdp_watch_id);
+	_connect_create_rfcomm (context);
 }
 
 static void
-sdp_search_completed_cb (uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *user_data)
+_connect_sdp_search_cb (uint8_t type,
+                        uint16_t status,
+                        uint8_t *rsp,
+                        size_t size,
+                        void *user_data)
 {
 	NMBluez5DunContext *context = user_data;
-	int scanned, seqlen = 0, bytesleft = size;
+	int scanned;
+	int seqlen = 0;
+	int bytesleft = size;
 	uint8_t dataType;
 	int channel = -1;
 
-	nm_log_dbg (LOGD_BT, "(%s -> %s): SDP search finished with type=%d status=%d",
-	            context->src_str, context->dst_str, status, type);
+	if (   context->cdat->rfcomm_sdp_search_error
+	    || context->rfcomm_channel >= 0)
+		return;
+
+	_LOGD (context, "SDP search finished with type=%d status=%d",
+	       status, type);
 
 	/* SDP response received */
-	if (status || type != SDP_SVC_SEARCH_ATTR_RSP) {
-		GError *error = g_error_new (NM_BT_ERROR,
-		                             NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                             "Did not get a Service Discovery response");
-		context->callback (context, NULL, error, context->user_data);
-		goto done;
+	if (   status
+	    || type != SDP_SVC_SEARCH_ATTR_RSP) {
+		g_set_error (&context->cdat->rfcomm_sdp_search_error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "did not get a Service Discovery response");
+		return;
 	}
 
 	scanned = sdp_extract_seqtype (rsp, bytesleft, &dataType, &seqlen);
 
-	nm_log_dbg (LOGD_BT, "(%s -> %s): SDP sequence type scanned=%d length=%d",
-	            context->src_str, context->dst_str, scanned, seqlen);
+	_LOGD (context, "SDP sequence type scanned=%d length=%d",
+	       scanned, seqlen);
 
 	scanned = sdp_extract_seqtype (rsp, bytesleft, &dataType, &seqlen);
-	if (!scanned || !seqlen) {
+	if (   !scanned
+	    || !seqlen) {
 		/* Short read or unknown sequence type */
-		GError *error = g_error_new (NM_BT_ERROR,
-		                             NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                             "Improper Service Discovery response");
-		context->callback (context, NULL, error, context->user_data);
-		goto done;
+		g_set_error (&context->cdat->rfcomm_sdp_search_error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "improper Service Discovery response");
+		return;
 	}
 
 	rsp += scanned;
@@ -194,90 +442,153 @@ sdp_search_completed_cb (uint8_t type, uint16_t status, uint8_t *rsp, size_t siz
 			channel = sdp_get_proto_port (protos, RFCOMM_UUID);
 			sdp_list_free (protos, NULL);
 
-			nm_log_dbg (LOGD_BT, "(%s -> %s): SDP channel=%d",
-			            context->src_str, context->dst_str, channel);
+			_LOGD (context, "SDP channel=%d",
+			       channel);
 		}
 		sdp_record_free (rec);
 
 		scanned += recsize;
 		rsp += recsize;
 		bytesleft -= recsize;
-	} while ((scanned < (ssize_t) size) && (bytesleft > 0) && (channel < 0));
-
-done:
-	if (channel != -1) {
-		context->rfcomm_channel = channel;
-		dun_connect (context);
+	} while (   scanned < (ssize_t) size
+	         && bytesleft > 0
+	         && channel < 0);
+
+	if (channel == -1) {
+		g_set_error (&context->cdat->rfcomm_sdp_search_error,
+		             NM_BT_ERROR,
+		             NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "did not receive rfcomm-channel");
+		return;
 	}
 
-	sdp_search_cleanup (context);
+	context->rfcomm_channel = channel;
 }
 
 static gboolean
-sdp_search_process_cb (GIOChannel *channel, GIOCondition condition, gpointer user_data)
+_connect_sdp_search_io_cb (GIOChannel *io_channel,
+                           GIOCondition condition,
+                           gpointer user_data)
 {
 	NMBluez5DunContext *context = user_data;
-
-	nm_log_dbg (LOGD_BT, "(%s -> %s): SDP search progressed with condition=%d",
-	            context->src_str, context->dst_str, condition);
+	gs_free_error GError *error = NULL;
+	int errsv;
 
 	if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
-		GError *error = g_error_new (NM_BT_ERROR,
-		                             NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                             "Service Discovery interrupted");
-		context->callback (context, NULL, error, context->user_data);
-		sdp_search_cleanup (context);
-		return FALSE;
+		_LOGD (context, "SDP search returned with invalid IO condition 0x%x",
+		       (guint) condition);
+		error = g_error_new (NM_BT_ERROR,
+		                     NM_BT_ERROR_DUN_CONNECT_FAILED,
+		                     "Service Discovery interrupted");
+		context->cdat->source_id = 0;
+		_context_invoke_callback_fail_and_free (context, error);
+		return G_SOURCE_REMOVE;
 	}
 
-	if (sdp_process (context->sdp_session) < 0) {
-		nm_log_dbg (LOGD_BT, "(%s -> %s): SDP search finished",
-		            context->src_str, context->dst_str);
+	if (sdp_process (context->cdat->sdp_session) == 0) {
+		_LOGD (context, "SDP search still not finished");
+		return G_SOURCE_CONTINUE;
+	}
 
-		/* Search finished successfully. */
-		return FALSE;
+	context->cdat->source_id = 0;
+
+	if (   context->rfcomm_channel < 0
+	    && !context->cdat->rfcomm_sdp_search_error) {
+		errsv = sdp_get_error (context->cdat->sdp_session);
+		_LOGD (context, "SDP search failed: %s (%d)",
+		       nm_strerror_native (errsv), errsv);
+		error = g_error_new (NM_BT_ERROR,
+		                     NM_BT_ERROR_DUN_CONNECT_FAILED,
+		                     "Service Discovery failed with %s (%d)",
+		                     nm_strerror_native (errsv), errsv);
+		_context_invoke_callback_fail_and_free (context, error);
+		return G_SOURCE_REMOVE;
 	}
 
-	/* Search progressed successfully. */
-	return TRUE;
+	if (context->cdat->rfcomm_sdp_search_error) {
+		_LOGD (context, "SDP search failed to complete: %s", context->cdat->rfcomm_sdp_search_error->message);
+		_context_invoke_callback_fail_and_free (context, context->cdat->rfcomm_sdp_search_error);
+		return G_SOURCE_REMOVE;
+	}
+
+	nm_clear_pointer (&context->cdat->sdp_session, sdp_close);
+
+	_connect_socket_connect (context);
+
+	return G_SOURCE_REMOVE;
 }
 
 static gboolean
-sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_data)
+_connect_sdp_session_start_on_idle_cb (gpointer user_data)
 {
 	NMBluez5DunContext *context = user_data;
-	sdp_list_t *search, *attrs;
+	gs_free_error GError *error = NULL;
+
+	context->cdat->source_id = 0;
+
+	_LOGD (context, "retry starting sdp-session...");
+
+	if (!_connect_sdp_session_start (context, &error))
+		_context_invoke_callback_fail_and_free (context, error);
+
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_connect_sdp_io_cb (GIOChannel *io_channel,
+                    GIOCondition condition,
+                    gpointer user_data)
+{
+	NMBluez5DunContext *context = user_data;
+	sdp_list_t *search;
+	sdp_list_t *attrs;
 	uuid_t svclass;
 	uint16_t attr;
-	int fd, fd_err = 0;
-	int err;
+	int fd;
+	int errsv;
+	int fd_err = 0;
+	int r;
 	socklen_t len = sizeof (fd_err);
-	GError *error = NULL;
+	gs_free_error GError *error = NULL;
+
+	context->cdat->source_id = 0;
 
-	context->sdp_watch_id = 0;
+	fd = g_io_channel_unix_get_fd (io_channel);
+
+	_LOGD (context, "sdp-session ready to connect with fd=%d", fd);
 
-	fd = g_io_channel_unix_get_fd (channel);
 	if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &fd_err, &len) < 0) {
-		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);
+		errsv = NM_ERRNO_NATIVE (errno);
+		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
+		                     "error for getsockopt on Service Discovery socket: %s (%d)",
+		                     nm_strerror_native (errsv), errsv);
+		goto done;
 	}
 
-	if (err != 0) {
+	if (fd_err != 0) {
+		errsv = nm_errno_native (fd_err);
+
+		if (   NM_IN_SET (errsv, ECONNREFUSED, EHOSTDOWN)
+		    && --context->cdat->sdp_session_try_count > 0) {
+			/* *sigh* */
+			_LOGD (context, "sdp-session failed with %s (%d). Retry in a bit", nm_strerror_native (errsv), errsv);
+			nm_clear_g_source (&context->cdat->source_id);
+			context->cdat->source_id = g_timeout_add (1000,
+			                                          _connect_sdp_session_start_on_idle_cb,
+			                                          context);
+			return G_SOURCE_REMOVE;
+		}
+
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Error on Service Discovery socket: (%d) %s",
-		                     err, nm_strerror_native (err));
+		                     "error on Service Discovery socket: %s (%d)",
+		                     nm_strerror_native (errsv), errsv);
 		goto done;
 	}
 
-	if (sdp_set_notify (context->sdp_session, sdp_search_completed_cb, context) < 0) {
+	if (sdp_set_notify (context->cdat->sdp_session, _connect_sdp_search_cb, context) < 0) {
 		/* Should not be reached, only can fail if we passed bad sdp_session. */
 		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Could not request Service Discovery notification");
+		                     "could not set Service Discovery notification");
 		goto done;
 	}
 
@@ -286,124 +597,261 @@ sdp_connect_watch (GIOChannel *channel, GIOCondition condition, gpointer user_da
 	attr = SDP_ATTR_PROTO_DESC_LIST;
 	attrs = sdp_list_append (NULL, &attr);
 
-	if (!sdp_service_search_attr_async (context->sdp_session, search, SDP_ATTR_REQ_INDIVIDUAL, attrs)) {
-		/* Set callback responsible for update the internal SDP transaction */
-		context->sdp_watch_id = g_io_add_watch (channel,
-		                                        G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-		                                        sdp_search_process_cb,
-		                                        context);
-	} else {
-		err = sdp_get_error (context->sdp_session);
-		error = g_error_new (NM_BT_ERROR,
-		                     NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Error starting Service Discovery: (%d) %s",
-		                     err, nm_strerror_native (err));
-	}
+	r = sdp_service_search_attr_async (context->cdat->sdp_session,
+	                                   search,
+	                                   SDP_ATTR_REQ_INDIVIDUAL,
+	                                   attrs);
 
 	sdp_list_free (attrs, NULL);
 	sdp_list_free (search, NULL);
 
-done:
-	if (error) {
-		context->callback (context, NULL, error, context->user_data);
-		sdp_search_cleanup (context);
+	if (r < 0) {
+		errsv = nm_errno_native (sdp_get_error (context->cdat->sdp_session));
+		error = g_error_new (NM_BT_ERROR,
+		                     NM_BT_ERROR_DUN_CONNECT_FAILED,
+		                     "error starting Service Discovery: %s (%d)",
+		                     nm_strerror_native (errsv), errsv);
+		goto done;
 	}
 
+	/* Set callback responsible for update the internal SDP transaction */
+	context->cdat->source_id = g_io_add_watch (io_channel,
+	                                           G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+	                                           _connect_sdp_search_io_cb,
+	                                           context);
+
+done:
+	if (error)
+		_context_invoke_callback_fail_and_free (context, error);
 	return G_SOURCE_REMOVE;
 }
 
-NMBluez5DunContext *
-nm_bluez5_dun_new (const char *adapter,
-                   const char *remote)
+/*****************************************************************************/
 
+static void
+_connect_cancelled_cb (GCancellable *cancellable,
+                       NMBluez5DunContext *context)
 {
-	NMBluez5DunContext *context;
-
-	context = g_slice_new0 (NMBluez5DunContext);
-	str2ba (adapter, &context->src);
-	str2ba (remote, &context->dst);
-	context->src_str = g_strdup (adapter);
-	context->dst_str = g_strdup (remote);
-	context->rfcomm_channel = -1;
-	context->rfcomm_id = -1;
-	context->rfcomm_fd = -1;
-	return context;
+	gs_free_error GError *error = NULL;
+
+	if (!g_cancellable_set_error_if_cancelled (cancellable, &error))
+		g_return_if_reached ();
+
+	_context_invoke_callback_fail_and_free (context, error);
 }
 
-void
-nm_bluez5_dun_connect (NMBluez5DunContext *context,
-                       NMBluez5DunFunc callback,
-                       gpointer user_data)
+static gboolean
+_connect_sdp_session_start (NMBluez5DunContext *context,
+                            GError **error)
 {
-	GIOChannel *channel;
+	nm_auto_unref_io_channel GIOChannel *io_channel = NULL;
 
-	context->callback = callback;
-	context->user_data = user_data;
+	nm_assert (context->cdat);
 
-	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);
-		/* FIXME: don't invoke the callback synchronously. */
-		dun_connect (context);
-		return;
+	nm_clear_g_source (&context->cdat->source_id);
+	nm_clear_pointer (&context->cdat->sdp_session, sdp_close);
+
+	context->cdat->sdp_session = sdp_connect (&context->src, &context->dst, SDP_NON_BLOCKING);
+	if (!context->cdat->sdp_session) {
+		int errsv = nm_errno_native (errno);
+
+		g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "failed to connect to the SDP server: %s (%d)",
+		             nm_strerror_native (errsv), errsv);
+		return FALSE;
 	}
 
-	nm_log_dbg (LOGD_BT, "(%s): starting channel number discovery for device %s",
-	            context->src_str, context->dst_str);
+	io_channel = g_io_channel_unix_new (sdp_get_socket (context->cdat->sdp_session));
+	context->cdat->source_id = g_io_add_watch (io_channel,
+	                                           G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+	                                           _connect_sdp_io_cb,
+	                                           context);
+	return TRUE;
+}
 
-	context->sdp_session = sdp_connect (&context->src, &context->dst, SDP_NON_BLOCKING);
-	if (!context->sdp_session) {
-		GError *error;
-		int err = sdp_get_error (context->sdp_session);
+/*****************************************************************************/
+
+gboolean
+nm_bluez5_dun_connect (const char *adapter,
+                       const char *remote,
+                       GCancellable *cancellable,
+                       NMBluez5DunConnectCb callback,
+                       gpointer callback_user_data,
+                       NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb,
+                       gpointer notify_tty_hangup_user_data,
+                       GError **error)
+{
+	nm_auto_free_context NMBluez5DunContext *context = NULL;
+	ConnectData *cdat;
+	gsize src_l;
+	gsize dst_l;
+
+	g_return_val_if_fail (adapter, FALSE);
+	g_return_val_if_fail (remote, FALSE);
+	g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), FALSE);
+	g_return_val_if_fail (callback, FALSE);
+	g_return_val_if_fail (notify_tty_hangup_cb, FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+	nm_assert (!g_cancellable_is_cancelled (cancellable));
+
+	src_l = strlen (adapter) + 1;
+	dst_l = strlen (remote) + 1;
+
+	cdat = g_slice_new (ConnectData);
+	*cdat = (ConnectData) {
+		.callback              = callback,
+		.callback_user_data    = callback_user_data,
+		.cancellable           = g_object_ref (cancellable),
+		.sdp_session_try_count = 5,
+	};
 
-		error = g_error_new (NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
-		                     "Failed to connect to the SDP server: (%d) %s",
-		                      err, nm_strerror_native (err));
-		/* FIXME: don't invoke the callback synchronously. */
-		context->callback (context, NULL, error, context->user_data);
-		return;
+	context = g_malloc (sizeof (NMBluez5DunContext) + src_l + dst_l);
+	*context = (NMBluez5DunContext) {
+		.cdat                        = cdat,
+		.notify_tty_hangup_cb        = notify_tty_hangup_cb,
+		.notify_tty_hangup_user_data = notify_tty_hangup_user_data,
+		.rfcomm_tty_no               = -1,
+		.rfcomm_sock_fd              = -1,
+		.rfcomm_tty_fd               = -1,
+		.rfcomm_channel              = -1,
+	};
+	memcpy (&context->src_str[0], adapter, src_l);
+	context->dst_str = &context->src_str[src_l];
+	memcpy ((char *) context->dst_str, remote, dst_l);
+
+	if (str2ba (adapter, &context->src) < 0) {
+		g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "invalid source");
+		return FALSE;
 	}
 
-	/* 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,
-	                                        sdp_connect_watch,
-	                                        context);
-	g_io_channel_unref (channel);
+	if (str2ba (remote, &context->dst) < 0) {
+		g_set_error (error, NM_BT_ERROR, NM_BT_ERROR_DUN_CONNECT_FAILED,
+		             "invalid remote");
+		return FALSE;
+	}
+
+	context->cdat->cancelled_id = g_signal_connect (context->cdat->cancellable,
+	                                                "cancelled",
+	                                                G_CALLBACK (_connect_cancelled_cb),
+	                                                context);
+
+	if (!_connect_sdp_session_start (context, error))
+		return FALSE;
+
+	_LOGD (context, "starting channel number discovery for device %s",
+	       context->dst_str);
+
+	g_steal_pointer (&context);
+	return TRUE;
 }
 
-/* Only clean up connection-related stuff to allow reconnect */
+/*****************************************************************************/
+
 void
-nm_bluez5_dun_cleanup (NMBluez5DunContext *context)
+nm_bluez5_dun_disconnect (NMBluez5DunContext *context)
 {
-	g_return_if_fail (context != NULL);
+	nm_assert (context);
+	nm_assert (!context->cdat);
 
-	sdp_search_cleanup (context);
+	_LOGD (context, "disconnecting DUN connection");
 
-	if (context->rfcomm_fd >= 0) {
-		if (context->rfcomm_id >= 0) {
-			struct rfcomm_dev_req req = { 0 };
+	_context_free (context);
+}
 
-			req.dev_id = context->rfcomm_id;
-			(void) ioctl (context->rfcomm_fd, RFCOMMRELEASEDEV, &req);
-			context->rfcomm_id = -1;
-		}
-		nm_close (context->rfcomm_fd);
-		context->rfcomm_fd = -1;
-	}
+/*****************************************************************************/
 
-	nm_close (context->rfcomm_tty_fd);
-	context->rfcomm_tty_fd = -1;
+static void
+_context_cleanup_connect_data (NMBluez5DunContext *context)
+{
+	ConnectData *cdat;
+
+	cdat = g_steal_pointer (&context->cdat);
+	if (!cdat)
+		return;
+
+	nm_clear_g_signal_handler (cdat->cancellable, &cdat->cancelled_id);
+
+	nm_clear_g_source (&cdat->source_id);
+
+	nm_clear_pointer (&cdat->sdp_session, sdp_close);
+
+	g_clear_object (&cdat->cancellable);
+
+	g_clear_error (&cdat->rfcomm_sdp_search_error);
+
+	nm_g_slice_free (cdat);
 }
 
-void
-nm_bluez5_dun_free (NMBluez5DunContext *context)
+static void
+_context_invoke_callback (NMBluez5DunContext *context,
+                          GError *error)
+{
+	NMBluez5DunConnectCb callback;
+	gpointer callback_user_data;
+
+	nm_assert (context);
+	nm_assert (context->cdat);
+	nm_assert (context->cdat->callback);
+	nm_assert (error || context->rfcomm_tty_path);
+
+	if (!error)
+		_LOGD (context, "connected via \"%s\"", context->rfcomm_tty_path);
+	else if (nm_utils_error_is_cancelled (error, FALSE))
+		_LOGD (context, "cancelled");
+	else
+		_LOGD (context, "failed to connect: %s", error->message);
+
+	callback = context->cdat->callback;
+	callback_user_data = context->cdat->callback_user_data;
+
+	_context_cleanup_connect_data (context);
+
+	callback (error ? NULL : context,
+	          error ? NULL : context->rfcomm_tty_path,
+	          error,
+	          callback_user_data);
+}
+
+static void
+_context_invoke_callback_success (NMBluez5DunContext *context)
 {
-	g_return_if_fail (context != NULL);
+	nm_assert (context->rfcomm_tty_path);
+	_context_invoke_callback (context, NULL);
+}
+
+static void
+_context_invoke_callback_fail_and_free (NMBluez5DunContext *context,
+                                        GError *error)
+{
+	nm_assert (error);
+	_context_invoke_callback (context, error);
+	_context_free (context);
+}
+
+static void
+_context_free (NMBluez5DunContext *context)
+{
+	nm_assert (context);
+
+	_context_cleanup_connect_data (context);
+
+	nm_clear_g_source (&context->rfcomm_tty_poll_id);
+
+	if (context->rfcomm_sock_fd >= 0) {
+		if (context->rfcomm_tty_no >= 0) {
+			struct rfcomm_dev_req req;
+
+			memset (&req, 0, sizeof (struct rfcomm_dev_req));
+			req.dev_id = context->rfcomm_tty_no;
+			context->rfcomm_tty_no = -1;
+			(void) ioctl (context->rfcomm_sock_fd, RFCOMMRELEASEDEV, &req);
+		}
+		nm_close (nm_steal_fd (&context->rfcomm_sock_fd));
+	}
 
-	nm_bluez5_dun_cleanup (context);
-	g_clear_pointer (&context->src_str, g_free);
-	g_clear_pointer (&context->dst_str, g_free);
-	g_slice_free (NMBluez5DunContext, context);
+	if (context->rfcomm_tty_fd >= 0)
+		nm_close (nm_steal_fd (&context->rfcomm_tty_fd));
+	nm_clear_g_free (&context->rfcomm_tty_path);
+	g_free (context);
 }
diff --git a/src/devices/bluetooth/nm-bluez5-dun.h b/src/devices/bluetooth/nm-bluez5-dun.h
index b605414b..b3f22965 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.h
+++ b/src/devices/bluetooth/nm-bluez5-dun.h
@@ -1,43 +1,38 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
-#ifndef _NM_BLUEZ5_UTILS_H_
-#define _NM_BLUEZ5_UTILS_H_
+#ifndef __NM_BLUEZ5_DUN_H__
+#define __NM_BLUEZ5_DUN_H__
 
 typedef struct _NMBluez5DunContext NMBluez5DunContext;
 
-typedef void (*NMBluez5DunFunc) (NMBluez5DunContext *context,
-                                 const char *rfcomm_dev,
-                                 GError *error,
-                                 gpointer user_data);
+#if WITH_BLUEZ5_DUN
 
-NMBluez5DunContext *nm_bluez5_dun_new (const char *adapter,
-                                       const char *remote);
+typedef void (*NMBluez5DunConnectCb) (NMBluez5DunContext *context,
+                                      const char *rfcomm_dev,
+                                      GError *error,
+                                      gpointer user_data);
 
-void nm_bluez5_dun_connect (NMBluez5DunContext *context,
-                            NMBluez5DunFunc callback,
-                            gpointer user_data);
+typedef void (*NMBluez5DunNotifyTtyHangupCb) (NMBluez5DunContext *context,
+                                              gpointer user_data);
 
-/* Clean up connection resources */
-void nm_bluez5_dun_cleanup (NMBluez5DunContext *context);
+gboolean nm_bluez5_dun_connect (const char *adapter,
+                                const char *remote,
+                                GCancellable *cancellable,
+                                NMBluez5DunConnectCb callback,
+                                gpointer callback_user_data,
+                                NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb,
+                                gpointer notify_tty_hangup_user_data,
+                                GError **error);
 
-/* Clean up and dispose all resources */
-void nm_bluez5_dun_free (NMBluez5DunContext *context);
+void nm_bluez5_dun_disconnect (NMBluez5DunContext *context);
 
-#endif  /* _NM_BLUEZ5_UTILS_H_ */
+const char *nm_bluez5_dun_context_get_adapter (const NMBluez5DunContext *context);
+const char *nm_bluez5_dun_context_get_remote (const NMBluez5DunContext *context);
+const char *nm_bluez5_dun_context_get_rfcomm_dev (const NMBluez5DunContext *context);
+
+#endif /* WITH_BLUEZ5_DUN */
+
+#endif  /* __NM_BLUEZ5_DUN_H__ */
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
deleted file mode 100644
index 9c5e8644..00000000
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ /dev/null
@@ -1,596 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2007 - 2008 Novell, Inc.
- * Copyright (C) 2007 - 2017 Red Hat, Inc.
- * Copyright (C) 2013 Intel Corporation.
- */
-
-#include "nm-default.h"
-
-#include "nm-bluez5-manager.h"
-
-#include <signal.h>
-#include <stdlib.h>
-
-#include "nm-core-internal.h"
-
-#include "c-list/src/c-list.h"
-#include "nm-bluez-device.h"
-#include "nm-bluez-common.h"
-#include "devices/nm-device-bridge.h"
-#include "settings/nm-settings.h"
-
-/*****************************************************************************/
-
-enum {
-	BDADDR_ADDED,
-	NETWORK_SERVER_ADDED,
-	LAST_SIGNAL,
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	NMSettings *settings;
-
-	GDBusProxy *proxy;
-
-	GHashTable *devices;
-
-	CList network_servers;
-} NMBluez5ManagerPrivate;
-
-struct _NMBluez5Manager {
-	GObject parent;
-	NMBtVTableNetworkServer network_server_vtable;
-	NMBluez5ManagerPrivate _priv;
-};
-
-struct _NMBluez5ManagerClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMBluez5Manager, nm_bluez5_manager, G_TYPE_OBJECT)
-
-#define NM_BLUEZ5_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluez5Manager, NM_IS_BLUEZ5_MANAGER)
-
-#define NM_BLUEZ5_MANAGER_GET_NETWORK_SERVER_VTABLE(self) (&(self)->network_server_vtable)
-#define NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER(vtable) \
-	NM_BLUEZ5_MANAGER(((char *)(vtable)) - offsetof (struct _NMBluez5Manager, network_server_vtable))
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN LOGD_BT
-#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez5", __VA_ARGS__)
-
-/*****************************************************************************/
-
-static void device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *self);
-static void device_usable (NMBluezDevice *device, GParamSpec *pspec, NMBluez5Manager *self);
-
-/*****************************************************************************/
-
-typedef struct {
-	char *path;
-	char *addr;
-	NMDevice *device;
-	CList lst_ns;
-} NetworkServer;
-
-static NetworkServer *
-_find_network_server (NMBluez5Manager *self, const char *path, NMDevice *device)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NetworkServer *network_server;
-
-	nm_assert (path || NM_IS_DEVICE (device));
-
-	c_list_for_each_entry (network_server, &priv->network_servers, lst_ns) {
-		if (path && !nm_streq (network_server->path, path))
-			continue;
-		if (device && network_server->device != device)
-			continue;
-		return network_server;
-	}
-	return NULL;
-}
-
-static NetworkServer *
-_find_network_server_for_addr (NMBluez5Manager *self, const char *addr)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NetworkServer *network_server;
-
-	c_list_for_each_entry (network_server, &priv->network_servers, lst_ns) {
-		/* The address lookups need a server not assigned to a device
-		 * and tolerate an empty address as a wildcard for "any". */
-		if (   !network_server->device
-		    && (!addr || nm_streq (network_server->addr, addr)))
-			return network_server;
-	}
-	return NULL;
-}
-
-static void
-_network_server_unregister (NMBluez5Manager *self, NetworkServer *network_server)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-
-	if (!network_server->device) {
-		/* Not connected. */
-		return;
-	}
-
-	_LOGI ("NAP: unregistering %s from %s",
-	       nm_device_get_iface (network_server->device),
-	       network_server->addr);
-
-	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
-	                        NM_BLUEZ_SERVICE,
-	                        network_server->path,
-	                        NM_BLUEZ5_NETWORK_SERVER_INTERFACE,
-	                        "Unregister",
-	                        g_variant_new ("(s)", BLUETOOTH_CONNECT_NAP),
-	                        NULL,
-	                        G_DBUS_CALL_FLAGS_NONE,
-	                        -1, NULL, NULL, NULL);
-
-	g_clear_object (&network_server->device);
-}
-
-static void
-_network_server_free (NMBluez5Manager *self, NetworkServer *network_server)
-{
-	_network_server_unregister (self, network_server);
-	c_list_unlink_stale (&network_server->lst_ns);
-	g_free (network_server->path);
-	g_free (network_server->addr);
-	g_slice_free (NetworkServer, network_server);
-}
-
-static gboolean
-network_server_is_available (const NMBtVTableNetworkServer *vtable,
-                             const char *addr)
-{
-	NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable);
-
-	return !!_find_network_server_for_addr (self, addr);
-}
-
-static gboolean
-network_server_register_bridge (const NMBtVTableNetworkServer *vtable,
-                                const char *addr,
-                                NMDevice *device)
-{
-	NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable);
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NetworkServer *network_server = _find_network_server_for_addr (self, addr);
-
-	nm_assert (NM_IS_DEVICE (device));
-	nm_assert (!_find_network_server (self, NULL, device));
-
-	if (!network_server) {
-		/* The device checked that a network server is available, before
-		 * starting the activation, but for some reason it no longer is.
-		 * Indicate that the activation should not proceed. */
-		_LOGI ("NAP: %s is not available for %s", addr, nm_device_get_iface (device));
-		return FALSE;
-	}
-
-	_LOGI ("NAP: registering %s on %s", nm_device_get_iface (device), network_server->addr);
-
-	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
-	                        NM_BLUEZ_SERVICE,
-	                        network_server->path,
-	                        NM_BLUEZ5_NETWORK_SERVER_INTERFACE,
-	                        "Register",
-	                        g_variant_new ("(ss)", BLUETOOTH_CONNECT_NAP, nm_device_get_iface (device)),
-	                        NULL,
-	                        G_DBUS_CALL_FLAGS_NONE,
-	                        -1, NULL, NULL, NULL);
-
-	network_server->device = g_object_ref (device);
-
-	return TRUE;
-}
-
-static gboolean
-network_server_unregister_bridge (const NMBtVTableNetworkServer *vtable,
-                                  NMDevice *device)
-{
-	NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable);
-	NetworkServer *network_server = _find_network_server (self, NULL, device);
-
-	if (network_server)
-		_network_server_unregister (self, network_server);
-
-	return TRUE;
-}
-
-static void
-network_server_removed (GDBusProxy *proxy, const char *path, NMBluez5Manager *self)
-{
-	NetworkServer *network_server;
-
-	network_server = _find_network_server (self, path, NULL);
-	if (!network_server)
-		return;
-
-	if (network_server->device) {
-		nm_device_queue_state (network_server->device, NM_DEVICE_STATE_DISCONNECTED,
-		                       NM_DEVICE_STATE_REASON_BT_FAILED);
-	}
-	_LOGI ("NAP: removed interface %s", network_server->addr);
-	_network_server_free (self, network_server);
-}
-
-static void
-network_server_added (GDBusProxy *proxy, const char *path, const char *addr, NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NetworkServer *network_server;
-
-	/* If BlueZ messes up and announces a single network server twice,
-	 * make sure we get rid of the older instance first. */
-	network_server_removed (proxy, path, self);
-
-	network_server = g_slice_new0 (NetworkServer);
-	network_server->path = g_strdup (path);
-	network_server->addr = g_strdup (addr);
-	c_list_link_before (&priv->network_servers, &network_server->lst_ns);
-
-	_LOGI ("NAP: added interface %s", addr);
-
-	g_signal_emit (self, signals[NETWORK_SERVER_ADDED], 0);
-}
-
-/*****************************************************************************/
-
-static void
-emit_bdaddr_added (NMBluez5Manager *self, NMBluezDevice *device)
-{
-	g_signal_emit (self, signals[BDADDR_ADDED], 0,
-	               device,
-	               nm_bluez_device_get_address (device),
-	               nm_bluez_device_get_name (device),
-	               nm_bluez_device_get_path (device),
-	               nm_bluez_device_get_capabilities (device));
-}
-
-void
-nm_bluez5_manager_query_devices (NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NMBluezDevice *device;
-	GHashTableIter iter;
-
-	g_hash_table_iter_init (&iter, priv->devices);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &device)) {
-		if (nm_bluez_device_get_usable (device))
-			emit_bdaddr_added (self, device);
-	}
-}
-
-static void
-remove_device (NMBluez5Manager *self, NMBluezDevice *device)
-{
-	g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_initialized), self);
-	g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_usable), self);
-	if (nm_bluez_device_get_usable (device))
-		g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED);
-}
-
-static void
-remove_all_devices (NMBluez5Manager *self)
-{
-	GHashTableIter iter;
-	NMBluezDevice *device;
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-
-	g_hash_table_iter_init (&iter, priv->devices);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &device)) {
-		g_hash_table_iter_steal (&iter);
-		remove_device (self, device);
-		g_object_unref (device);
-	}
-}
-
-static void
-device_usable (NMBluezDevice *device, GParamSpec *pspec, NMBluez5Manager *self)
-{
-	gboolean usable = nm_bluez_device_get_usable (device);
-
-	_LOGD ("(%s): bluez device now %s",
-	       nm_bluez_device_get_path (device),
-	       usable ? "usable" : "unusable");
-
-	if (usable) {
-		_LOGD ("(%s): bluez device address %s",
-		       nm_bluez_device_get_path (device),
-		       nm_bluez_device_get_address (device));
-		emit_bdaddr_added (self, device);
-	} else
-		g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED);
-}
-
-static void
-device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-
-	_LOGD ("(%s): bluez device %s",
-	       nm_bluez_device_get_path (device),
-	       success ? "initialized" : "failed to initialize");
-	if (!success)
-		g_hash_table_remove (priv->devices, nm_bluez_device_get_path (device));
-}
-
-static void
-device_added (GDBusProxy *proxy, const char *path, NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NMBluezDevice *device;
-
-	device = nm_bluez_device_new (path, NULL, priv->settings, 5);
-	g_signal_connect (device, NM_BLUEZ_DEVICE_INITIALIZED, G_CALLBACK (device_initialized), self);
-	g_signal_connect (device, "notify::" NM_BLUEZ_DEVICE_USABLE, G_CALLBACK (device_usable), self);
-	g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device);
-
-	_LOGD ("(%s): new bluez device found", path);
-}
-
-static void
-device_removed (GDBusProxy *proxy, const char *path, NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NMBluezDevice *device;
-
-	_LOGD ("(%s): bluez device removed", path);
-
-	device = g_hash_table_lookup (priv->devices, path);
-	if (device) {
-		g_hash_table_steal (priv->devices, nm_bluez_device_get_path (device));
-		remove_device (NM_BLUEZ5_MANAGER (self), device);
-		g_object_unref (device);
-	}
-}
-
-static void
-object_manager_interfaces_added (GDBusProxy      *proxy,
-                                 const char      *path,
-                                 GVariant        *dict,
-                                 NMBluez5Manager *self)
-{
-	if (g_variant_lookup (dict, NM_BLUEZ5_DEVICE_INTERFACE, "a{sv}", NULL))
-		device_added (proxy, path, self);
-	if (g_variant_lookup (dict, NM_BLUEZ5_NETWORK_SERVER_INTERFACE, "a{sv}", NULL)) {
-		gs_unref_variant GVariant *adapter = g_variant_lookup_value (dict, NM_BLUEZ5_ADAPTER_INTERFACE, G_VARIANT_TYPE_DICTIONARY);
-		const char *address;
-
-		if (   adapter
-		    && g_variant_lookup (adapter, "Address", "&s", &address))
-			network_server_added (proxy, path, address, self);
-	}
-}
-
-static void
-object_manager_interfaces_removed (GDBusProxy       *proxy,
-                                   const char       *path,
-                                   const char      **ifaces,
-                                   NMBluez5Manager  *self)
-{
-	if (ifaces && g_strv_contains (ifaces, NM_BLUEZ5_DEVICE_INTERFACE))
-		device_removed (proxy, path, self);
-	if (ifaces && g_strv_contains (ifaces, NM_BLUEZ5_NETWORK_SERVER_INTERFACE))
-		network_server_removed (proxy, path, self);
-}
-
-static void
-get_managed_objects_cb (GDBusProxy *proxy,
-                        GAsyncResult *res,
-                        NMBluez5Manager *self)
-{
-	gs_unref_variant GVariant *variant0 = NULL;
-	GVariant *variant, *ifaces;
-	GVariantIter i;
-	GError *error = NULL;
-	const char *path;
-
-	variant = _nm_dbus_proxy_call_finish (proxy, res,
-	                                      G_VARIANT_TYPE ("(a{oa{sa{sv}}})"),
-	                                      &error);
-	if (!variant) {
-		if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
-			_LOGW ("Couldn't get managed objects: not running Bluez5?");
-		else {
-			g_dbus_error_strip_remote_error (error);
-			_LOGW ("Couldn't get managed objects: %s", error->message);
-		}
-		g_clear_error (&error);
-		return;
-	}
-	variant0 = g_variant_get_child_value (variant, 0);
-	g_variant_iter_init (&i, variant0);
-	while ((g_variant_iter_next (&i, "{&o*}", &path, &ifaces))) {
-		object_manager_interfaces_added (proxy, path, ifaces, self);
-		g_variant_unref (ifaces);
-	}
-
-	g_variant_unref (variant);
-}
-
-static void name_owner_changed_cb (GObject *object, GParamSpec *pspec, gpointer user_data);
-
-static void
-on_proxy_acquired (GObject *object,
-                   GAsyncResult *res,
-                   NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	GError *error = NULL;
-
-	priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
-
-	if (!priv->proxy) {
-		_LOGW ("Couldn't acquire object manager proxy: %s", error->message);
-		g_clear_error (&error);
-		return;
-	}
-
-	g_signal_connect (priv->proxy, "notify::g-name-owner",
-	                  G_CALLBACK (name_owner_changed_cb), self);
-
-	/* Get already managed devices. */
-	g_dbus_proxy_call (priv->proxy, "GetManagedObjects",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   NULL,
-	                   (GAsyncReadyCallback) get_managed_objects_cb,
-	                   self);
-
-	_nm_dbus_signal_connect (priv->proxy, "InterfacesAdded", G_VARIANT_TYPE ("(oa{sa{sv}})"),
-	                         G_CALLBACK (object_manager_interfaces_added), self);
-	_nm_dbus_signal_connect (priv->proxy, "InterfacesRemoved", G_VARIANT_TYPE ("(oas)"),
-	                         G_CALLBACK (object_manager_interfaces_removed), self);
-}
-
-static void
-bluez_connect (NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-
-	g_return_if_fail (priv->proxy == NULL);
-
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_NONE,
-	                          NULL,
-	                          NM_BLUEZ_SERVICE,
-	                          NM_BLUEZ_MANAGER_PATH,
-	                          NM_OBJECT_MANAGER_INTERFACE,
-	                          NULL,
-	                          (GAsyncReadyCallback) on_proxy_acquired,
-	                          self);
-}
-
-static void
-name_owner_changed_cb (GObject *object,
-                       GParamSpec *pspec,
-                       gpointer user_data)
-{
-	NMBluez5Manager *self = NM_BLUEZ5_MANAGER (user_data);
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	char *owner;
-
-	if (priv->devices) {
-		owner = g_dbus_proxy_get_name_owner (priv->proxy);
-		if (!owner)
-			remove_all_devices (self);
-		g_free (owner);
-	}
-}
-
-/*****************************************************************************/
-
-static void
-nm_bluez5_manager_init (NMBluez5Manager *self)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	NMBtVTableNetworkServer *network_server_vtable = NM_BLUEZ5_MANAGER_GET_NETWORK_SERVER_VTABLE (self);
-
-	bluez_connect (self);
-
-	priv->devices = g_hash_table_new_full (nm_str_hash, g_str_equal,
-	                                       NULL, g_object_unref);
-
-	c_list_init (&priv->network_servers);
-
-	nm_assert (!nm_bt_vtable_network_server);
-	network_server_vtable->is_available = network_server_is_available;
-	network_server_vtable->register_bridge = network_server_register_bridge;
-	network_server_vtable->unregister_bridge = network_server_unregister_bridge;
-	nm_bt_vtable_network_server = network_server_vtable;
-}
-
-NMBluez5Manager *
-nm_bluez5_manager_new (NMSettings *settings)
-{
-	NMBluez5Manager *instance = NULL;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (settings), NULL);
-
-	instance = g_object_new (NM_TYPE_BLUEZ5_MANAGER, NULL);
-	NM_BLUEZ5_MANAGER_GET_PRIVATE (instance)->settings = g_object_ref (settings);
-	return instance;
-}
-
-static void
-dispose (GObject *object)
-{
-	NMBluez5Manager *self = NM_BLUEZ5_MANAGER (object);
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-	CList *iter, *safe;
-
-	c_list_for_each_safe (iter, safe, &priv->network_servers)
-		_network_server_free (self, c_list_entry (iter, NetworkServer, lst_ns));
-
-	if (priv->proxy) {
-		g_signal_handlers_disconnect_by_func (priv->proxy, G_CALLBACK (name_owner_changed_cb), self);
-		g_clear_object (&priv->proxy);
-	}
-
-	g_hash_table_remove_all (priv->devices);
-
-	G_OBJECT_CLASS (nm_bluez5_manager_parent_class)->dispose (object);
-}
-
-static void
-finalize (GObject *object)
-{
-	NMBluez5Manager *self = NM_BLUEZ5_MANAGER (object);
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-
-	g_hash_table_destroy (priv->devices);
-
-	G_OBJECT_CLASS (nm_bluez5_manager_parent_class)->finalize (object);
-
-	g_object_unref (priv->settings);
-}
-
-static void
-nm_bluez5_manager_class_init (NMBluez5ManagerClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->dispose = dispose;
-	object_class->finalize = finalize;
-
-	signals[BDADDR_ADDED] =
-	    g_signal_new (NM_BLUEZ_MANAGER_BDADDR_ADDED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING,
-	                  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);
-
-	signals[NETWORK_SERVER_ADDED] =
-	    g_signal_new (NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 0);
-}
diff --git a/src/devices/bluetooth/nm-bluez5-manager.h b/src/devices/bluetooth/nm-bluez5-manager.h
deleted file mode 100644
index 14ac842e..00000000
--- a/src/devices/bluetooth/nm-bluez5-manager.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2007 - 2008 Novell, Inc.
- * Copyright (C) 2007 - 2013 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_BLUEZ5_MANAGER_H__
-#define __NETWORKMANAGER_BLUEZ5_MANAGER_H__
-
-#define NM_TYPE_BLUEZ5_MANAGER            (nm_bluez5_manager_get_type ())
-#define NM_BLUEZ5_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_BLUEZ5_MANAGER, NMBluez5Manager))
-#define NM_BLUEZ5_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_BLUEZ5_MANAGER, NMBluez5ManagerClass))
-#define NM_IS_BLUEZ5_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_BLUEZ5_MANAGER))
-#define NM_IS_BLUEZ5_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ5_MANAGER))
-#define NM_BLUEZ5_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ5_MANAGER, NMBluez5ManagerClass))
-
-typedef struct _NMBluez5Manager NMBluez5Manager;
-typedef struct _NMBluez5ManagerClass NMBluez5ManagerClass;
-
-GType nm_bluez5_manager_get_type (void);
-
-NMBluez5Manager *nm_bluez5_manager_new (NMSettings *settings);
-
-void nm_bluez5_manager_query_devices (NMBluez5Manager *manager);
-
-#endif /* __NETWORKMANAGER_BLUEZ5_MANAGER_H__ */
diff --git a/src/devices/bluetooth/nm-bt-error.c b/src/devices/bluetooth/nm-bt-error.c
index bc9e5aa4..8aa4283d 100644
--- a/src/devices/bluetooth/nm-bt-error.c
+++ b/src/devices/bluetooth/nm-bt-error.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
diff --git a/src/devices/bluetooth/nm-bt-error.h b/src/devices/bluetooth/nm-bt-error.h
index ed7ed90d..258fca8d 100644
--- a/src/devices/bluetooth/nm-bt-error.h
+++ b/src/devices/bluetooth/nm-bt-error.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index f8626011..497810c3 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2009 - 2011 Red Hat, Inc.
  */
 
@@ -23,8 +9,9 @@
 
 #include <stdio.h>
 
+#include "nm-core-internal.h"
 #include "nm-bluez-common.h"
-#include "nm-bluez-device.h"
+#include "nm-bluez-manager.h"
 #include "devices/nm-device-private.h"
 #include "ppp/nm-ppp-manager.h"
 #include "nm-setting-connection.h"
@@ -48,10 +35,12 @@ _LOG_DECLARE_SELF(NMDeviceBt);
 
 /*****************************************************************************/
 
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
-	PROP_BT_NAME,
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceBt,
+	PROP_BT_BDADDR,
+	PROP_BT_BZ_MGR,
 	PROP_BT_CAPABILITIES,
-	PROP_BT_DEVICE,
+	PROP_BT_DBUS_PATH,
+	PROP_BT_NAME,
 );
 
 enum {
@@ -64,24 +53,38 @@ static guint signals[LAST_SIGNAL] = { 0 };
 typedef struct {
 	NMModemManager *modem_manager;
 
-	gboolean mm_running;
+	NMBluezManager *bz_mgr;
 
-	NMBluezDevice *bt_device;
+	char *dbus_path;
 
 	char *bdaddr;
 	char *name;
-	guint32 capabilities;
 
-	gboolean connected;
-	gboolean have_iface;
+	char *connect_rfcomm_iface;
+
+	GSList *connect_modem_candidates;
 
-	char *rfcomm_iface;
 	NMModem *modem;
-	guint timeout_id;
 
-	GCancellable *cancellable;
+	GCancellable *connect_bz_cancellable;
+
+	gulong connect_watch_link_id;
+
+	guint connect_watch_link_idle_id;
+
+	guint connect_wait_modem_id;
+
+	NMBluetoothCapabilities capabilities:6;
+
+	NMBluetoothCapabilities connect_bt_type:6;  /* BT type of the current connection */
+
+	NMDeviceStageState stage1_bt_state:3;
+	NMDeviceStageState stage1_modem_prepare_state:3;
+
+	bool is_connected:1;
+
+	bool mm_running:1;
 
-	guint32 bt_type;  /* BT type of the current connection */
 } NMDeviceBtPrivate;
 
 struct _NMDeviceBt {
@@ -95,42 +98,65 @@ struct _NMDeviceBtClass {
 
 G_DEFINE_TYPE (NMDeviceBt, nm_device_bt, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_BT_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceBt, NM_IS_DEVICE_BT)
-
-/*****************************************************************************/
-
-static gboolean modem_stage1 (NMDeviceBt *self, NMModem *modem, NMDeviceStateReason *out_failure_reason);
+#define NM_DEVICE_BT_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceBt, NM_IS_DEVICE_BT, NMDevice)
 
 /*****************************************************************************/
 
-guint32 nm_device_bt_get_capabilities (NMDeviceBt *self)
+NMBluetoothCapabilities nm_device_bt_get_capabilities (NMDeviceBt *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE_BT (self), NM_BT_CAPABILITY_NONE);
 
 	return NM_DEVICE_BT_GET_PRIVATE (self)->capabilities;
 }
 
-static guint32
+static NMBluetoothCapabilities
 get_connection_bt_type (NMConnection *connection)
 {
 	NMSettingBluetooth *s_bt;
 	const char *bt_type;
 
 	s_bt = nm_connection_get_setting_bluetooth (connection);
-	if (!s_bt)
-		return NM_BT_CAPABILITY_NONE;
 
-	bt_type = nm_setting_bluetooth_get_connection_type (s_bt);
-	g_assert (bt_type);
-
-	if (!strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN))
-		return NM_BT_CAPABILITY_DUN;
-	else if (!strcmp (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU))
-		return NM_BT_CAPABILITY_NAP;
+	if (s_bt) {
+		bt_type = nm_setting_bluetooth_get_connection_type (s_bt);
+		if (bt_type) {
+			if (nm_streq (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN))
+				return NM_BT_CAPABILITY_DUN;
+			else if (nm_streq (bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU))
+				return NM_BT_CAPABILITY_NAP;
+		}
+	}
 
 	return NM_BT_CAPABILITY_NONE;
 }
 
+static gboolean
+get_connection_bt_type_check (NMDeviceBt *self,
+                              NMConnection *connection,
+                              NMBluetoothCapabilities *out_bt_type,
+                              GError **error)
+{
+	NMBluetoothCapabilities bt_type;
+
+	bt_type = get_connection_bt_type (connection);
+
+	NM_SET_OUT (out_bt_type, bt_type);
+
+	if (bt_type == NM_BT_CAPABILITY_NONE) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "profile is not a PANU/DUN bluetooth type");
+		return FALSE;
+	}
+
+	if (!NM_FLAGS_ALL (NM_DEVICE_BT_GET_PRIVATE (self)->capabilities, bt_type)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "device does not support bluetooth type");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 static NMDeviceCapabilities
 get_generic_capabilities (NMDevice *device)
 {
@@ -142,17 +168,24 @@ can_auto_connect (NMDevice *device,
                   NMSettingsConnection *sett_conn,
                   char **specific_object)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
-	guint32 bt_type;
+	NMDeviceBt *self = NM_DEVICE_BT (device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+	NMBluetoothCapabilities bt_type;
 
 	nm_assert (!specific_object || !*specific_object);
 
 	if (!NM_DEVICE_CLASS (nm_device_bt_parent_class)->can_auto_connect (device, sett_conn, NULL))
 		return FALSE;
 
+	if (!get_connection_bt_type_check (self,
+	                                   nm_settings_connection_get_connection (sett_conn),
+	                                   &bt_type,
+	                                   NULL))
+		return FALSE;
+
 	/* Can't auto-activate a DUN connection without ModemManager */
-	bt_type = get_connection_bt_type (nm_settings_connection_get_connection (sett_conn));
-	if (bt_type == NM_BT_CAPABILITY_DUN && priv->mm_running == FALSE)
+	if (   bt_type == NM_BT_CAPABILITY_DUN
+	    && priv->mm_running == FALSE)
 		return FALSE;
 
 	return TRUE;
@@ -161,20 +194,16 @@ can_auto_connect (NMDevice *device,
 static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
+	NMDeviceBt *self = NM_DEVICE_BT (device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 	NMSettingBluetooth *s_bt;
 	const char *bdaddr;
-	guint32 bt_type;
 
 	if (!NM_DEVICE_CLASS (nm_device_bt_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
-	bt_type = get_connection_bt_type (connection);
-	if (!NM_FLAGS_ALL (priv->capabilities, bt_type)) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "device does not support bluetooth type of profile");
+	if (!get_connection_bt_type_check (self, connection, NULL, error))
 		return FALSE;
-	}
 
 	s_bt = nm_connection_get_setting_bluetooth (connection);
 
@@ -200,18 +229,15 @@ check_connection_available (NMDevice *device,
                             const char *specific_object,
                             GError **error)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
-	guint32 bt_type;
+	NMDeviceBt *self = NM_DEVICE_BT (device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+	NMBluetoothCapabilities bt_type;
 
-	bt_type = get_connection_bt_type (connection);
-	if (!(bt_type & priv->capabilities)) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "device does not support bluetooth type");
+	if (!get_connection_bt_type_check (self, connection, &bt_type, error))
 		return FALSE;
-	}
 
-	/* DUN connections aren't available without ModemManager */
-	if (bt_type == NM_BT_CAPABILITY_DUN && priv->mm_running == FALSE) {
+	if (   bt_type == NM_BT_CAPABILITY_DUN
+	    && !priv->mm_running) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
 		                            "ModemManager missing for DUN profile");
 		return FALSE;
@@ -227,11 +253,12 @@ complete_connection (NMDevice *device,
                      NMConnection *const*existing_connections,
                      GError **error)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
 	NMSettingBluetooth *s_bt;
 	const char *setting_bdaddr;
 	const char *ctype;
-	gboolean is_dun = FALSE, is_pan = FALSE;
+	gboolean is_dun = FALSE;
+	gboolean is_pan = FALSE;
 	NMSettingGsm *s_gsm;
 	NMSettingCdma *s_cdma;
 	NMSettingSerial *s_serial;
@@ -274,7 +301,10 @@ complete_connection (NMDevice *device,
 		}
 
 		/* PAN can't use any DUN-related settings */
-		if (s_gsm || s_cdma || s_serial || s_ppp) {
+		if (   s_gsm
+		    || s_cdma
+		    || s_serial
+		    || s_ppp) {
 			g_set_error_literal (error,
 			                     NM_CONNECTION_ERROR,
 			                     NM_CONNECTION_ERROR_INVALID_SETTING,
@@ -304,7 +334,8 @@ complete_connection (NMDevice *device,
 		}
 
 		/* Need at least a GSM or a CDMA setting */
-		if (!s_gsm && !s_cdma) {
+		if (   !s_gsm
+		    && !s_cdma) {
 			g_set_error_literal (error,
 			                     NM_CONNECTION_ERROR,
 			                     NM_CONNECTION_ERROR_INVALID_SETTING,
@@ -441,20 +472,19 @@ static void
 modem_auth_result (NMModem *modem, GError *error, gpointer user_data)
 {
 	NMDevice *device = NM_DEVICE (user_data);
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
+
+	g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
 
 	if (error) {
 		nm_device_state_changed (device,
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
-	} else {
-		NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-
-		/* Otherwise, on success for GSM/CDMA secrets we need to schedule modem stage1 again */
-		g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
-		if (!modem_stage1 (NM_DEVICE_BT (device), priv->modem, &failure_reason))
-			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, failure_reason);
+		return;
 	}
+
+	priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_INIT;
+	nm_device_activate_schedule_stage1_device_prepare (device);
 }
 
 static void
@@ -463,45 +493,33 @@ modem_prepare_result (NMModem *modem,
                       guint i_reason,
                       gpointer user_data)
 {
-	NMDeviceBt *self = NM_DEVICE_BT (user_data);
-	NMDevice *device = NM_DEVICE (self);
+	NMDeviceBt *self = user_data;
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 	NMDeviceStateReason reason = i_reason;
 	NMDeviceState state;
 
-	state = nm_device_get_state (device);
-	g_return_if_fail (state == NM_DEVICE_STATE_CONFIG || state == NM_DEVICE_STATE_NEED_AUTH);
-
-	if (success) {
-		NMActRequest *req;
-		NMActStageReturn ret;
-		NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-
-		req = nm_device_get_act_request (device);
-		g_return_if_fail (req);
-
-		ret = nm_modem_act_stage2_config (modem, req, &failure_reason);
-		switch (ret) {
-		case NM_ACT_STAGE_RETURN_POSTPONE:
-			break;
-		case NM_ACT_STAGE_RETURN_SUCCESS:
-			nm_device_activate_schedule_stage3_ip_config_start (device);
-			break;
-		case NM_ACT_STAGE_RETURN_FAILURE:
-		default:
-			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, failure_reason);
-			break;
-		}
-	} else {
+	state = nm_device_get_state (NM_DEVICE (self));
+
+	g_return_if_fail (NM_IN_SET (state, NM_DEVICE_STATE_PREPARE,
+	                                    NM_DEVICE_STATE_NEED_AUTH));
+
+	nm_assert (priv->stage1_modem_prepare_state == NM_DEVICE_STAGE_STATE_PENDING);
+
+	if (!success) {
 		if (nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT) {
 			/* If the connect failed because the SIM PIN was wrong don't allow
 			 * the device to be auto-activated anymore, which would risk locking
 			 * the SIM if the incorrect PIN continues to be used.
 			 */
-			nm_device_autoconnect_blocked_set (device, NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN);
+			nm_device_autoconnect_blocked_set (NM_DEVICE (self), NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN);
 		}
 
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
+		nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, reason);
+		return;
 	}
+
+	priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_COMPLETED;
+	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
 }
 
 static void
@@ -510,7 +528,7 @@ device_state_changed (NMDevice *device,
                       NMDeviceState old_state,
                       NMDeviceStateReason reason)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
 
 	if (priv->modem)
 		nm_modem_device_state_changed (priv->modem, new_state, old_state);
@@ -519,7 +537,8 @@ device_state_changed (NMDevice *device,
 	 * since the device could be both DUN and NAP capable and thus may not
 	 * change state (which rechecks available connections) when MM comes and goes.
 	 */
-	if (priv->mm_running && (priv->capabilities & NM_BT_CAPABILITY_DUN))
+	if (   priv->mm_running
+	    && NM_FLAGS_HAS (priv->capabilities, NM_BT_CAPABILITY_DUN))
 	    nm_device_recheck_available_connections (device);
 }
 
@@ -541,8 +560,10 @@ modem_ip4_config_result (NMModem *modem,
 		nm_device_ip_method_failed (device,
 		                            AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-	} else
-		nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
+		return;
+	}
+
+	nm_device_activate_schedule_ip_config_result (device, AF_INET, NM_IP_CONFIG_CAST (config));
 }
 
 static void
@@ -561,29 +582,6 @@ ip_ifindex_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
 	}
 }
 
-static gboolean
-modem_stage1 (NMDeviceBt *self, NMModem *modem, NMDeviceStateReason *out_failure_reason)
-{
-	NMActRequest *req;
-	NMActStageReturn ret;
-
-	req = nm_device_get_act_request (NM_DEVICE (self));
-	g_return_val_if_fail (req, FALSE);
-
-	ret = nm_modem_act_stage1_prepare (modem, req, out_failure_reason);
-	switch (ret) {
-	case NM_ACT_STAGE_RETURN_POSTPONE:
-	case NM_ACT_STAGE_RETURN_SUCCESS:
-		/* Success, wait for the 'prepare-result' signal */
-		return TRUE;
-	case NM_ACT_STAGE_RETURN_FAILURE:
-	default:
-		break;
-	}
-
-	return FALSE;
-}
-
 /*****************************************************************************/
 
 static void
@@ -593,7 +591,7 @@ modem_cleanup (NMDeviceBt *self)
 
 	if (priv->modem) {
 		g_signal_handlers_disconnect_matched (priv->modem, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
-		g_clear_object (&priv->modem);
+		nm_clear_pointer (&priv->modem, nm_modem_unclaim);
 	}
 }
 
@@ -608,11 +606,13 @@ modem_state_cb (NMModem *modem,
 	NMDevice *device = NM_DEVICE (user_data);
 	NMDeviceState dev_state = nm_device_get_state (device);
 
-	if (new_state <= NM_MODEM_STATE_DISABLING && old_state > NM_MODEM_STATE_DISABLING) {
+	if (   new_state <= NM_MODEM_STATE_DISABLING
+	    && old_state > NM_MODEM_STATE_DISABLING) {
 		/* Will be called whenever something external to NM disables the
 		 * modem directly through ModemManager.
 		 */
-		if (nm_device_is_activating (device) || dev_state == NM_DEVICE_STATE_ACTIVATED) {
+		if (   nm_device_is_activating (device)
+		    || dev_state == NM_DEVICE_STATE_ACTIVATED) {
 			nm_device_state_changed (device,
 			                         NM_DEVICE_STATE_DISCONNECTED,
 			                         NM_DEVICE_STATE_REASON_USER_REQUESTED);
@@ -620,13 +620,15 @@ modem_state_cb (NMModem *modem,
 		}
 	}
 
-	if (new_state < NM_MODEM_STATE_CONNECTING &&
-	    old_state >= NM_MODEM_STATE_CONNECTING &&
-	    dev_state >= NM_DEVICE_STATE_NEED_AUTH &&
-	    dev_state <= NM_DEVICE_STATE_ACTIVATED) {
+	if (   new_state < NM_MODEM_STATE_CONNECTING
+	    && old_state >= NM_MODEM_STATE_CONNECTING
+	    && dev_state >= NM_DEVICE_STATE_NEED_AUTH
+	    && dev_state <= NM_DEVICE_STATE_ACTIVATED) {
 		/* Fail the device if the modem disconnects unexpectedly while the
 		 * device is activating/activated. */
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
 		return;
 	}
 }
@@ -637,66 +639,56 @@ modem_removed_cb (NMModem *modem, gpointer user_data)
 	NMDeviceBt *self = NM_DEVICE_BT (user_data);
 	NMDeviceState state;
 
-	/* Fail the device if the modem was removed while active */
 	state = nm_device_get_state (NM_DEVICE (self));
-	if (   state == NM_DEVICE_STATE_ACTIVATED
-	    || nm_device_is_activating (NM_DEVICE (self))) {
+	if (   nm_device_is_activating (NM_DEVICE (self))
+	    || state == NM_DEVICE_STATE_ACTIVATED) {
 		nm_device_state_changed (NM_DEVICE (self),
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_BT_FAILED);
-	} else
-		modem_cleanup (self);
+		return;
+	}
+
+	modem_cleanup (self);
 }
 
 static gboolean
-component_added (NMDevice *device, GObject *component)
+modem_try_claim (NMDeviceBt *self,
+                 NMModem *modem)
 {
-	NMDeviceBt *self = NM_DEVICE_BT (device);
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-	NMModem *modem;
+	gs_free char *rfcomm_base_name = NULL;
 	NMDeviceState state;
-	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-	if (   !component
-	    || !NM_IS_MODEM (component))
+	if (priv->modem) {
+		if (priv->modem == modem)
+			return TRUE;
 		return FALSE;
+	}
 
-	modem = NM_MODEM (component);
-	if (!priv->rfcomm_iface)
+	if (nm_modem_is_claimed (modem))
 		return FALSE;
 
-	{
-		gs_free char *base = NULL;
-
-		base = g_path_get_basename (priv->rfcomm_iface);
-		if (!nm_streq (base, nm_modem_get_control_port (modem)))
-			return FALSE;
-	}
+	if (!priv->connect_rfcomm_iface)
+		return FALSE;
 
-	/* Got the modem */
-	nm_clear_g_source (&priv->timeout_id);
-	nm_clear_g_cancellable (&priv->cancellable);
+	rfcomm_base_name = g_path_get_basename (priv->connect_rfcomm_iface);
+	if (!nm_streq0 (rfcomm_base_name, nm_modem_get_control_port (modem)))
+		return FALSE;
 
-	/* Can only accept the modem in stage2, but since the interface matched
+	/* Can only accept the modem in stage1, but since the interface matched
 	 * what we were expecting, don't let anything else claim the modem either.
 	 */
 	state = nm_device_get_state (NM_DEVICE (self));
-	if (state != NM_DEVICE_STATE_CONFIG) {
-		_LOGW (LOGD_BT | LOGD_MB,
+	if (state != NM_DEVICE_STATE_PREPARE) {
+		_LOGD (LOGD_BT | LOGD_MB,
 		       "modem found but device not in correct state (%d)",
 		       nm_device_get_state (NM_DEVICE (self)));
-		return TRUE;
+		return FALSE;
 	}
 
-	_LOGI (LOGD_BT | LOGD_MB,
-	       "Activation: (bluetooth) Stage 2 of 5 (Device Configure) modem found.");
+	priv->modem = nm_modem_claim (modem);
+	priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_INIT;
 
-	if (priv->modem) {
-		g_warn_if_reached ();
-		modem_cleanup (self);
-	}
-
-	priv->modem = g_object_ref (modem);
 	g_signal_connect (modem, NM_MODEM_PPP_STATS, G_CALLBACK (ppp_stats), self);
 	g_signal_connect (modem, NM_MODEM_PPP_FAILED, G_CALLBACK (ppp_failed), self);
 	g_signal_connect (modem, NM_MODEM_PREPARE_RESULT, G_CALLBACK (modem_prepare_result), self);
@@ -705,92 +697,187 @@ component_added (NMDevice *device, GObject *component)
 	g_signal_connect (modem, NM_MODEM_AUTH_RESULT, G_CALLBACK (modem_auth_result), self);
 	g_signal_connect (modem, NM_MODEM_STATE_CHANGED, G_CALLBACK (modem_state_cb), self);
 	g_signal_connect (modem, NM_MODEM_REMOVED, G_CALLBACK (modem_removed_cb), self);
-
 	g_signal_connect (modem, "notify::" NM_MODEM_IP_IFINDEX, G_CALLBACK (ip_ifindex_changed_cb), self);
 
-	/* Kick off the modem connection */
-	if (!modem_stage1 (self, modem, &failure_reason))
-		nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, failure_reason);
+	_LOGD (LOGD_BT | LOGD_MB,
+	       "modem found");
 
 	return TRUE;
 }
 
-static gboolean
-modem_find_timeout (gpointer user_data)
+static void
+mm_modem_added_cb (NMModemManager *manager,
+                   NMModem *modem,
+                   gpointer user_data)
+{
+	NMDeviceBt *self = user_data;
+	NMDeviceBtPrivate *priv;
+
+	if (!modem_try_claim (user_data, modem))
+		return;
+
+	priv = NM_DEVICE_BT_GET_PRIVATE (self);
+
+	if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_COMPLETED)
+		nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+}
+
+/*****************************************************************************/
+
+void
+_nm_device_bt_notify_set_connected (NMDeviceBt *self,
+                                    gboolean connected)
 {
-	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);
+	connected = !!connected;
+	if (priv->is_connected == connected)
+		return;
+
+	priv->is_connected = connected;
 
+	if (   connected
+	    || priv->stage1_bt_state != NM_DEVICE_STAGE_STATE_COMPLETED
+	    || nm_device_get_state (NM_DEVICE (self)) > NM_DEVICE_STATE_ACTIVATED) {
+		_LOGT (LOGD_BT, "set-connected: %d", connected);
+		return;
+	}
+
+	_LOGT (LOGD_BT, "set-connected: %d (disconnecting device...)", connected);
 	nm_device_state_changed (NM_DEVICE (self),
 	                         NM_DEVICE_STATE_FAILED,
-	                         NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND);
-	return FALSE;
+	                         NM_DEVICE_STATE_REASON_CARRIER);
 }
 
-static void
-check_connect_continue (NMDeviceBt *self)
+static gboolean
+connect_watch_link_idle_cb (gpointer user_data)
 {
-	NMDevice *device = NM_DEVICE (self);
+	NMDeviceBt *self = user_data;
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-	gboolean pan = (priv->bt_type == NM_BT_CAPABILITY_NAP);
-	gboolean dun = (priv->bt_type == NM_BT_CAPABILITY_DUN);
+	int ifindex;
 
-	if (!priv->connected || !priv->have_iface)
-		return;
+	priv->connect_watch_link_idle_id = 0;
+
+	if (nm_device_get_state (NM_DEVICE (self)) <= NM_DEVICE_STATE_ACTIVATED) {
+		ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self));
+		if (   ifindex > 0
+		    && !nm_platform_link_get (nm_device_get_platform (NM_DEVICE (self)), ifindex)) {
+			_LOGT (LOGD_BT, "device disappeared");
+			nm_device_state_changed (NM_DEVICE (self),
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_BT_FAILED);
+		}
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+connect_watch_link_cb (NMPlatform *platform,
+                       int obj_type_i,
+                       int ifindex,
+                       NMPlatformLink *info,
+                       int change_type_i,
+                       NMDevice *self)
+{
+	const NMPlatformSignalChangeType change_type = change_type_i;
+	NMDeviceBtPrivate *priv;
+
+	/* bluez doesn't notify us when the connection disconnects.
+	 * Neither does NMManager (or NMDevice) tell us when the ip-ifindex goes away.
+	 * This is horrible, and should be improved. For now, watch the link ourself... */
+
+	if (NM_IN_SET (change_type, NM_PLATFORM_SIGNAL_CHANGED,
+	                            NM_PLATFORM_SIGNAL_REMOVED)) {
+		priv = NM_DEVICE_BT_GET_PRIVATE (self);
+		if (priv->connect_watch_link_idle_id == 0)
+			priv->connect_watch_link_idle_id = g_idle_add (connect_watch_link_idle_cb, self);
+	}
+}
 
-	_LOGI (LOGD_BT,
-	       "Activation: (bluetooth) Stage 2 of 5 (Device Configure) successful. Will connect via %s.",
-	       dun ? "DUN" : (pan ? "PAN" : "unknown"));
+static gboolean
+connect_wait_modem_timeout (gpointer user_data)
+{
+	NMDeviceBt *self = NM_DEVICE_BT (user_data);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->timeout_id);
-	nm_clear_g_cancellable (&priv->cancellable);
+	/* since this timeout is longer than the connect timeout, we must have already
+	 * hit the connect-timeout first or being connected. */
+	nm_assert (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_COMPLETED);
 
-	if (pan) {
-		/* Bluez says we're connected now.  Start IP config. */
-		nm_device_activate_schedule_stage3_ip_config_start (device);
-	} else if (dun) {
-		/* Wait for ModemManager to find the modem */
-		priv->timeout_id = g_timeout_add_seconds (30, modem_find_timeout, self);
+	priv->connect_wait_modem_id = 0;
+	nm_clear_g_cancellable (&priv->connect_bz_cancellable);
 
-		_LOGI (LOGD_BT | LOGD_MB,
-		       "Activation: (bluetooth) Stage 2 of 5 (Device Configure) waiting for modem to appear.");
-	} else
-		g_assert_not_reached ();
+	if (priv->modem)
+		_LOGD (LOGD_BT, "timeout connecting modem for DUN connection");
+	else
+		_LOGD (LOGD_BT, "timeout finding modem for DUN connection");
+
+	nm_device_state_changed (NM_DEVICE (self),
+	                         NM_DEVICE_STATE_FAILED,
+	                         NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND);
+	return G_SOURCE_REMOVE;
 }
 
 static void
-bluez_connect_cb (NMBluezDevice *bt_device,
-                  const char *device_name,
-                  GError *error,
-                  gpointer user_data)
+connect_bz_cb (NMBluezManager *bz_mgr,
+               gboolean is_complete,
+               const char *device_name,
+               GError *error,
+               gpointer user_data)
 {
-	gs_unref_object NMDeviceBt *self = user_data;
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+	NMDeviceBt *self;
+	NMDeviceBtPrivate *priv;
+	char sbuf[100];
 
 	if (nm_utils_error_is_cancelled (error, FALSE))
 		return;
 
-	nm_clear_g_source (&priv->timeout_id);
-	g_clear_object (&priv->cancellable);
+	self = user_data;
+	priv = NM_DEVICE_BT_GET_PRIVATE (self);
+
+	nm_assert (nm_device_is_activating (NM_DEVICE (self)));
+	nm_assert (NM_IN_SET ((NMBluetoothCapabilities) priv->connect_bt_type, NM_BT_CAPABILITY_DUN,
+	                                                                       NM_BT_CAPABILITY_NAP));
 
-	if (!nm_device_is_activating (NM_DEVICE (self)))
+	if (!is_complete) {
+		nm_assert (priv->connect_bt_type == NM_BT_CAPABILITY_DUN);
+		nm_assert (device_name);
+		nm_assert (!error);
+
+		if (!nm_streq0 (priv->connect_rfcomm_iface, device_name)) {
+			nm_assert (!priv->connect_rfcomm_iface);
+			_LOGD (LOGD_BT, "DUN is still connecting but got serial port \"%s\" to claim modem", device_name);
+			g_free (priv->connect_rfcomm_iface);
+			priv->connect_rfcomm_iface = g_strdup (device_name);
+		}
 		return;
+	}
+
+	g_clear_object (&priv->connect_bz_cancellable);
 
 	if (!device_name) {
-		_LOGW (LOGD_BT, "Error connecting with bluez: %s", error->message);
+		_LOGW (LOGD_BT, "%s connect request failed: %s",
+		       nm_bluetooth_capability_to_string (priv->connect_bt_type, sbuf, sizeof (sbuf)),
+		       error->message);
 		nm_device_state_changed (NM_DEVICE (self),
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_BT_FAILED);
 		return;
 	}
 
-	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
-		g_free (priv->rfcomm_iface);
-		priv->rfcomm_iface = g_strdup (device_name);
-	} else if (priv->bt_type == NM_BT_CAPABILITY_NAP) {
+	_LOGD (LOGD_BT, "%s connect request successful (%s)",
+	       nm_bluetooth_capability_to_string (priv->connect_bt_type, sbuf, sizeof (sbuf)),
+	       device_name);
+
+	if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) {
+		if (!nm_streq0 (priv->connect_rfcomm_iface, device_name)) {
+			nm_assert_not_reached ();
+			g_free (priv->connect_rfcomm_iface);
+			priv->connect_rfcomm_iface = g_strdup (device_name);
+		}
+	} else {
+		nm_assert (priv->connect_bt_type == NM_BT_CAPABILITY_NAP);
 		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),
@@ -798,107 +885,121 @@ bluez_connect_cb (NMBluezDevice *bt_device,
 			                         NM_DEVICE_STATE_REASON_BT_FAILED);
 			return;
 		}
+		priv->connect_watch_link_id = g_signal_connect (nm_device_get_platform (NM_DEVICE (self)),
+		                                                NM_PLATFORM_SIGNAL_LINK_CHANGED,
+		                                                G_CALLBACK (connect_watch_link_cb),
+		                                                self);
 	}
 
-	_LOGD (LOGD_BT, "connect request successful");
-
-	/* Stage 3 gets scheduled when Bluez says we're connected */
-	priv->have_iface = TRUE;
-	check_connect_continue (self);
-}
-
-static void
-bluez_connected_changed (NMBluezDevice *bt_device,
-                         GParamSpec *pspec,
-                         NMDevice *device)
-{
-	NMDeviceBt *self = NM_DEVICE_BT (device);
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-	gboolean connected;
-	NMDeviceState state;
-
-	state = nm_device_get_state (device);
-	connected = nm_bluez_device_get_connected (bt_device);
-	if (connected) {
-		if (state == NM_DEVICE_STATE_CONFIG) {
-			_LOGD (LOGD_BT, "connected to the device");
-
-			priv->connected = TRUE;
-			check_connect_continue (self);
-		}
-	} else {
-		gboolean fail = FALSE;
-
-		/* Bluez says we're disconnected from the device.  Suck. */
-
-		if (nm_device_is_activating (device)) {
-			_LOGI (LOGD_BT, "Activation: (bluetooth) bluetooth link disconnected.");
-			fail = TRUE;
-		} else if (state == NM_DEVICE_STATE_ACTIVATED) {
-			_LOGI (LOGD_BT, "bluetooth link disconnected.");
-			fail = TRUE;
-		}
-
-		if (fail) {
-			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CARRIER);
-			priv->connected = FALSE;
-		}
+	if (!priv->is_connected) {
+		/* we got the callback from NMBluezManager with succes. We actually should be
+		 * connected and this line shouldn't be reached. */
+		nm_assert_not_reached ();
+		_LOGE (LOGD_BT, "bluetooth is unexpectedly not in connected state");
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_BT_FAILED);
+		return;
 	}
-}
 
-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");
-
-	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);
-	return FALSE;
+	priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_COMPLETED;
+	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
 }
 
 static NMActStageReturn
-act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+act_stage1_prepare (NMDevice *device,
+                    NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceBt *self = NM_DEVICE_BT (device);
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+	gs_free_error GError *error = NULL;
 	NMConnection *connection;
 
 	connection = nm_device_get_applied_connection (device);
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
-	priv->bt_type = get_connection_bt_type (connection);
-	if (priv->bt_type == NM_BT_CAPABILITY_NONE) {
-		// FIXME: set a reason code
+	priv->connect_bt_type = get_connection_bt_type (connection);
+	if (priv->connect_bt_type == NM_BT_CAPABILITY_NONE) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_BT_FAILED);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	if (priv->bt_type == NM_BT_CAPABILITY_DUN && !priv->mm_running) {
+	if (   priv->connect_bt_type == NM_BT_CAPABILITY_DUN
+	    && !priv->mm_running) {
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	_LOGD (LOGD_BT, "requesting connection to the device");
+	if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_PENDING)
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+	else if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_INIT) {
+		gs_unref_object GCancellable *cancellable = NULL;
+		char sbuf[100];
+
+		_LOGD (LOGD_BT, "connecting to %s bluetooth device",
+		       nm_bluetooth_capability_to_string (priv->connect_bt_type, sbuf, sizeof (sbuf)));
+
+		cancellable = g_cancellable_new ();
+
+		if (!nm_bluez_manager_connect (priv->bz_mgr,
+		                               priv->dbus_path,
+		                               priv->connect_bt_type,
+		                               30000,
+		                               cancellable,
+		                               connect_bz_cb,
+		                               self,
+		                               &error)) {
+			_LOGD (LOGD_BT, "cannot connect to bluetooth device: %s", error->message);
+			*out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED;
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		}
+
+		priv->connect_bz_cancellable = g_steal_pointer (&cancellable);
+		priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_PENDING;
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+	}
+
+	if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) {
+		if (!priv->modem) {
+			gs_free NMModem **modems = NULL;
+			guint i, n;
+
+			if (priv->connect_wait_modem_id == 0)
+				priv->connect_wait_modem_id = g_timeout_add_seconds (30, connect_wait_modem_timeout, self);
+
+			modems = nm_modem_manager_get_modems (priv->modem_manager, &n);
+			for (i = 0; i < n; i++) {
+				if (modem_try_claim (self, modems[i]))
+					break;
+			}
+			if (!priv->modem)
+				return NM_ACT_STAGE_RETURN_POSTPONE;
+		}
+
+		if (priv->stage1_modem_prepare_state == NM_DEVICE_STAGE_STATE_PENDING)
+			return NM_ACT_STAGE_RETURN_POSTPONE;
+		if (priv->stage1_modem_prepare_state == NM_DEVICE_STAGE_STATE_INIT) {
+			priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_PENDING;
+			return nm_modem_act_stage1_prepare (priv->modem,
+			                                    nm_device_get_act_request (NM_DEVICE (self)),
+			                                    out_failure_reason);
+		}
+	}
 
-	nm_clear_g_source (&priv->timeout_id);
-	nm_clear_g_cancellable (&priv->cancellable);
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
 
-	priv->timeout_id = g_timeout_add_seconds (30, bt_connect_timeout, device);
-	priv->cancellable = g_cancellable_new ();
+static NMActStageReturn
+act_stage2_config (NMDevice *device,
+                   NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceBt *self = NM_DEVICE_BT (device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 
-	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));
+	if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN)
+		nm_modem_act_stage2_config (priv->modem);
 
-	return NM_ACT_STAGE_RETURN_POSTPONE;
+	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
 static NMActStageReturn
@@ -907,11 +1008,11 @@ act_stage3_ip_config_start (NMDevice *device,
                             gpointer *out_config,
                             NMDeviceStateReason *out_failure_reason)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
 
 	nm_assert_addr_family (addr_family);
 
-	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
+	if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) {
 		if (addr_family == AF_INET) {
 			return nm_modem_stage3_ip4_config_start (priv->modem,
 			                                         device,
@@ -930,15 +1031,17 @@ act_stage3_ip_config_start (NMDevice *device,
 static void
 deactivate (NMDevice *device)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device);
 
-	priv->have_iface = FALSE;
-	priv->connected = FALSE;
+	nm_clear_g_signal_handler (nm_device_get_platform (device), &priv->connect_watch_link_id);
+	nm_clear_g_source (&priv->connect_watch_link_idle_id);
+	priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_INIT;
+	nm_clear_g_source (&priv->connect_wait_modem_id);
+	nm_clear_g_cancellable (&priv->connect_bz_cancellable);
 
-	nm_clear_g_source (&priv->timeout_id);
-	nm_clear_g_cancellable (&priv->cancellable);
+	priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_INIT;
 
-	if (priv->bt_type == NM_BT_CAPABILITY_DUN) {
+	if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) {
 		if (priv->modem) {
 			nm_modem_deactivate (priv->modem, device);
 
@@ -952,22 +1055,53 @@ deactivate (NMDevice *device)
 		}
 	}
 
-	if (priv->bt_type != NM_BT_CAPABILITY_NONE)
-		nm_bluez_device_disconnect (priv->bt_device);
-
-	priv->bt_type = NM_BT_CAPABILITY_NONE;
+	if (priv->connect_bt_type != NM_BT_CAPABILITY_NONE) {
+		priv->connect_bt_type = NM_BT_CAPABILITY_NONE;
+		nm_bluez_manager_disconnect (priv->bz_mgr, priv->dbus_path);
+	}
 
-	g_free (priv->rfcomm_iface);
-	priv->rfcomm_iface = NULL;
+	nm_clear_g_free (&priv->connect_rfcomm_iface);
 
 	if (NM_DEVICE_CLASS (nm_device_bt_parent_class)->deactivate)
 		NM_DEVICE_CLASS (nm_device_bt_parent_class)->deactivate (device);
 }
 
-static void
-bluez_device_removed (NMBluezDevice *bdev, gpointer user_data)
+void
+_nm_device_bt_notify_removed (NMDeviceBt *self)
+{
+	g_signal_emit_by_name (self, NM_DEVICE_REMOVED);
+}
+
+/*****************************************************************************/
+
+gboolean
+_nm_device_bt_for_same_device (NMDeviceBt *self,
+                               const char *dbus_path,
+                               const char *bdaddr,
+                               const char *name,
+                               NMBluetoothCapabilities capabilities)
 {
-	g_signal_emit_by_name (NM_DEVICE_BT (user_data), NM_DEVICE_REMOVED);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+
+	return    nm_streq (priv->dbus_path, dbus_path)
+	       && nm_streq (priv->bdaddr, bdaddr)
+	       && capabilities == priv->capabilities
+	       && (!name || nm_streq (priv->name, name));
+}
+
+void
+_nm_device_bt_notify_set_name (NMDeviceBt *self, const char *name)
+{
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+
+	nm_assert (name);
+
+	if (!nm_streq (priv->name, name)) {
+		_LOGT (LOGD_BT, "set-name: %s", name);
+		g_free (priv->name);
+		priv->name = g_strdup (name);
+		_notify (self, PROP_BT_NAME);
+	}
 }
 
 /*****************************************************************************/
@@ -1028,9 +1162,6 @@ get_property (GObject *object, guint prop_id,
 	case PROP_BT_CAPABILITIES:
 		g_value_set_uint (value, priv->capabilities);
 		break;
-	case PROP_BT_DEVICE:
-		g_value_set_object (value, priv->bt_device);
-		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1044,19 +1175,32 @@ set_property (GObject *object, guint prop_id,
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
 
 	switch (prop_id) {
+	case PROP_BT_BZ_MGR:
+		/* construct-only */
+		priv->bz_mgr = g_object_ref (g_value_get_pointer (value));
+		nm_assert (NM_IS_BLUEZ_MANAGER (priv->bz_mgr));
+		break;
+	case PROP_BT_DBUS_PATH:
+		/* construct-only */
+		priv->dbus_path = g_value_dup_string (value);
+		nm_assert (priv->dbus_path);
+		break;
+	case PROP_BT_BDADDR:
+		/* construct-only */
+		priv->bdaddr = g_value_dup_string (value);
+		nm_assert (priv->bdaddr);
+		break;
 	case PROP_BT_NAME:
 		/* construct-only */
 		priv->name = g_value_dup_string (value);
+		nm_assert (priv->name);
 		break;
 	case PROP_BT_CAPABILITIES:
 		/* construct-only */
 		priv->capabilities = g_value_get_uint (value);
-		break;
-	case PROP_BT_DEVICE:
-		/* construct-only */
-		priv->bt_device = g_value_dup_object (value);
-		if (!priv->bt_device)
-			g_return_if_reached ();
+		nm_assert (NM_IN_SET ((NMBluetoothCapabilities) priv->capabilities, NM_BT_CAPABILITY_DUN,
+		                                                                    NM_BT_CAPABILITY_NAP,
+		                                                                    NM_BT_CAPABILITY_DUN | NM_BT_CAPABILITY_NAP));
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1076,7 +1220,6 @@ constructed (GObject *object)
 {
 	NMDeviceBt *self = NM_DEVICE_BT (object);
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-	const char *my_hwaddr;
 
 	G_OBJECT_CLASS (nm_device_bt_parent_class)->constructed (object);
 
@@ -1085,74 +1228,69 @@ constructed (GObject *object)
 	nm_modem_manager_name_owner_ref (priv->modem_manager);
 
 	g_signal_connect (priv->modem_manager,
+	                  NM_MODEM_MANAGER_MODEM_ADDED,
+	                  G_CALLBACK (mm_modem_added_cb),
+	                  self);
+
+	g_signal_connect (priv->modem_manager,
 	                  "notify::"NM_MODEM_MANAGER_NAME_OWNER,
 	                  G_CALLBACK (mm_name_owner_changed_cb),
 	                  self);
 
-	if (priv->bt_device) {
-		/* Watch for BT device property changes */
-		g_signal_connect (priv->bt_device, "notify::" NM_BLUEZ_DEVICE_CONNECTED,
-		                  G_CALLBACK (bluez_connected_changed),
-		                  object);
-		g_signal_connect (priv->bt_device, NM_BLUEZ_DEVICE_REMOVED,
-		                  G_CALLBACK (bluez_device_removed), object);
-	}
-
-	my_hwaddr = nm_device_get_hw_address (NM_DEVICE (object));
-	if (my_hwaddr)
-		priv->bdaddr = g_strdup (my_hwaddr);
-	else
-		g_warn_if_reached ();
-
 	set_mm_running (self);
 }
 
-NMDevice *
-nm_device_bt_new (NMBluezDevice *bt_device,
-                  const char *udi,
+NMDeviceBt *
+nm_device_bt_new (NMBluezManager *bz_mgr,
+                  const char *dbus_path,
                   const char *bdaddr,
                   const char *name,
-                  guint32 capabilities)
+                  NMBluetoothCapabilities capabilities)
 {
-	g_return_val_if_fail (udi != NULL, NULL);
-	g_return_val_if_fail (bdaddr != NULL, NULL);
-	g_return_val_if_fail (name != NULL, NULL);
+	g_return_val_if_fail (NM_IS_BLUEZ_MANAGER (bz_mgr), NULL);
+	g_return_val_if_fail (dbus_path, NULL);
+	g_return_val_if_fail (bdaddr, NULL);
+	g_return_val_if_fail (name, NULL);
 	g_return_val_if_fail (capabilities != NM_BT_CAPABILITY_NONE, NULL);
-	g_return_val_if_fail (NM_IS_BLUEZ_DEVICE (bt_device), NULL);
-
-	return (NMDevice *) g_object_new (NM_TYPE_DEVICE_BT,
-	                                  NM_DEVICE_UDI, udi,
-	                                  NM_DEVICE_IFACE, bdaddr,
-	                                  NM_DEVICE_DRIVER, "bluez",
-	                                  NM_DEVICE_PERM_HW_ADDRESS, bdaddr,
-	                                  NM_DEVICE_BT_DEVICE, bt_device,
-	                                  NM_DEVICE_BT_NAME, name,
-	                                  NM_DEVICE_BT_CAPABILITIES, capabilities,
-	                                  NM_DEVICE_TYPE_DESC, "Bluetooth",
-	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BT,
-	                                  NULL);
+
+	return g_object_new (NM_TYPE_DEVICE_BT,
+	                     NM_DEVICE_UDI, dbus_path,
+	                     NM_DEVICE_IFACE, bdaddr,
+	                     NM_DEVICE_DRIVER, "bluez",
+	                     NM_DEVICE_PERM_HW_ADDRESS, bdaddr,
+	                     NM_DEVICE_BT_BDADDR, bdaddr,
+	                     NM_DEVICE_BT_BZ_MGR, bz_mgr,
+	                     NM_DEVICE_BT_CAPABILITIES, (guint) capabilities,
+	                     NM_DEVICE_BT_DBUS_PATH, dbus_path,
+	                     NM_DEVICE_BT_NAME, name,
+	                     NM_DEVICE_TYPE_DESC, "Bluetooth",
+	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BT,
+	                     NULL);
 }
 
 static void
 dispose (GObject *object)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
+	NMDeviceBt *self = NM_DEVICE_BT (object);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->timeout_id);
-	nm_clear_g_cancellable (&priv->cancellable);
+	nm_clear_g_signal_handler (nm_device_get_platform (NM_DEVICE (self)), &priv->connect_watch_link_id);
+	nm_clear_g_source (&priv->connect_watch_link_idle_id);
 
-	g_signal_handlers_disconnect_matched (priv->bt_device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object);
+	nm_clear_g_source (&priv->connect_wait_modem_id);
+	nm_clear_g_cancellable (&priv->connect_bz_cancellable);
 
 	if (priv->modem_manager) {
-		g_signal_handlers_disconnect_by_func (priv->modem_manager, G_CALLBACK (mm_name_owner_changed_cb), object);
+		g_signal_handlers_disconnect_by_func (priv->modem_manager, G_CALLBACK (mm_name_owner_changed_cb), self);
 		nm_modem_manager_name_owner_unref (priv->modem_manager);
 		g_clear_object (&priv->modem_manager);
 	}
 
-	modem_cleanup (NM_DEVICE_BT (object));
-	g_clear_object (&priv->bt_device);
+	modem_cleanup (self);
 
 	G_OBJECT_CLASS (nm_device_bt_parent_class)->dispose (object);
+
+	g_clear_object (&priv->bz_mgr);
 }
 
 static void
@@ -1160,7 +1298,8 @@ finalize (GObject *object)
 {
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
 
-	g_free (priv->rfcomm_iface);
+	g_free (priv->connect_rfcomm_iface);
+	g_free (priv->dbus_path);
 	g_free (priv->name);
 	g_free (priv->bdaddr);
 
@@ -1202,17 +1341,34 @@ nm_device_bt_class_init (NMDeviceBtClass *klass)
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->can_auto_connect = can_auto_connect;
 	device_class->deactivate = deactivate;
+	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
 	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;
 	device_class->is_available = is_available;
-	device_class->component_added = component_added;
 	device_class->get_configured_mtu = nm_modem_get_configured_mtu;
 
 	device_class->state_changed = device_state_changed;
 
+	obj_properties[PROP_BT_BZ_MGR] =
+	     g_param_spec_pointer (NM_DEVICE_BT_BZ_MGR, "", "",
+	                           G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+	                           G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_BT_BDADDR] =
+	     g_param_spec_string (NM_DEVICE_BT_BDADDR, "", "",
+	                          NULL,
+	                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_BT_DBUS_PATH] =
+	     g_param_spec_string (NM_DEVICE_BT_DBUS_PATH, "", "",
+	                          NULL,
+	                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_BT_NAME] =
 	     g_param_spec_string (NM_DEVICE_BT_NAME, "", "",
 	                          NULL,
@@ -1225,12 +1381,6 @@ nm_device_bt_class_init (NMDeviceBtClass *klass)
 	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                        G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_BT_DEVICE] =
-	     g_param_spec_object (NM_DEVICE_BT_DEVICE, "", "",
-	                          NM_TYPE_BLUEZ_DEVICE,
-	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
-
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	signals[PPP_STATS] =
diff --git a/src/devices/bluetooth/nm-device-bt.h b/src/devices/bluetooth/nm-device-bt.h
index 6c8a5773..ecb5781b 100644
--- a/src/devices/bluetooth/nm-device-bt.h
+++ b/src/devices/bluetooth/nm-device-bt.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2009 Red Hat, Inc.
  */
 
@@ -21,7 +7,6 @@
 #define __NETWORKMANAGER_DEVICE_BT_H__
 
 #include "devices/nm-device.h"
-#include "nm-bluez-device.h"
 
 #define NM_TYPE_DEVICE_BT                   (nm_device_bt_get_type ())
 #define NM_DEVICE_BT(obj)                   (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_BT, NMDeviceBt))
@@ -30,9 +15,11 @@
 #define NM_IS_DEVICE_BT_CLASS(klass)        (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_BT))
 #define NM_DEVICE_BT_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_BT, NMDeviceBtClass))
 
-#define NM_DEVICE_BT_NAME         "name"
+#define NM_DEVICE_BT_BDADDR       "bt-bdaddr"
+#define NM_DEVICE_BT_BZ_MGR       "bt-bz-mgr"
 #define NM_DEVICE_BT_CAPABILITIES "bt-capabilities"
-#define NM_DEVICE_BT_DEVICE       "bt-device"
+#define NM_DEVICE_BT_DBUS_PATH    "bt-dbus-path"
+#define NM_DEVICE_BT_NAME         "bt-name"
 
 #define NM_DEVICE_BT_PPP_STATS    "ppp-stats"
 
@@ -41,13 +28,21 @@ typedef struct _NMDeviceBtClass NMDeviceBtClass;
 
 GType nm_device_bt_get_type (void);
 
-NMDevice *nm_device_bt_new (NMBluezDevice *bt_device,
-                            const char *udi,
-                            const char *bdaddr,
-                            const char *name,
-                            guint32 capabilities);
+struct _NMBluezManager;
 
-guint32 nm_device_bt_get_capabilities (NMDeviceBt *device);
+NMDeviceBt *nm_device_bt_new (struct _NMBluezManager *bz_mgr,
+                              const char *dbus_path,
+                              const char *bdaddr,
+                              const char *name,
+                              NMBluetoothCapabilities capabilities);
+
+gboolean _nm_device_bt_for_same_device (NMDeviceBt *device,
+                                        const char *dbus_path,
+                                        const char *bdaddr,
+                                        const char *name,
+                                        NMBluetoothCapabilities capabilities);
+
+NMBluetoothCapabilities nm_device_bt_get_capabilities (NMDeviceBt *device);
 
 struct _NMModem;
 
@@ -55,4 +50,11 @@ gboolean nm_device_bt_modem_added (NMDeviceBt *device,
                                    struct _NMModem *modem,
                                    const char *driver);
 
+void _nm_device_bt_notify_removed (NMDeviceBt *self);
+
+void _nm_device_bt_notify_set_name (NMDeviceBt *self, const char *name);
+
+void _nm_device_bt_notify_set_connected (NMDeviceBt *self,
+                                         gboolean connected);
+
 #endif /* __NETWORKMANAGER_DEVICE_BT_H__ */
diff --git a/src/devices/bluetooth/tests/nm-bt-test.c b/src/devices/bluetooth/tests/nm-bt-test.c
new file mode 100644
index 00000000..02cfd228
--- /dev/null
+++ b/src/devices/bluetooth/tests/nm-bt-test.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: LGPL-2.1+
+
+#include "nm-default.h"
+
+#include <glib-unix.h>
+
+#include "devices/bluetooth/nm-bluez5-dun.h"
+
+#include "nm-test-utils-core.h"
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN LOGD_BT
+#define _NMLOG(level, ...) \
+	nm_log ((level), _NMLOG_DOMAIN, \
+	        NULL, NULL, \
+	        "bt%s%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+	        NM_PRINT_FMT_QUOTED (gl.argv_cmd, "[", gl.argv_cmd, "]", "") \
+	        _NM_UTILS_MACRO_REST (__VA_ARGS__))
+
+/*****************************************************************************/
+
+struct {
+	int argc;
+	const char *const*argv;
+	const char *argv_cmd;
+	GMainLoop *loop;
+} gl;
+
+typedef struct _MainCmdInfo {
+	const char *name;
+	int (*main_func) (const struct _MainCmdInfo *main_cmd_info);
+} MainCmdInfo;
+
+/*****************************************************************************/
+
+#if WITH_BLUEZ5_DUN
+
+typedef struct {
+	NMBluez5DunContext *dun_context;
+	GCancellable *cancellable;
+	guint timeout_id;
+	guint sig_term_id;
+	guint sig_int_id;
+} DunConnectData;
+
+static void
+_dun_connect_cb (NMBluez5DunContext *context,
+                 const char *rfcomm_dev,
+                 GError *error,
+                 gpointer user_data)
+{
+	DunConnectData *dun_connect_data = user_data;
+
+	g_assert (dun_connect_data);
+	g_assert (!dun_connect_data->dun_context);
+	g_assert ((!!error) != (!!rfcomm_dev));
+
+	if (rfcomm_dev && !context) {
+		_LOGI ("dun-connect notifies path \"%s\". Wait longer...", rfcomm_dev);
+		return;
+	}
+
+	if (rfcomm_dev) {
+		g_assert (context);
+		_LOGI ("dun-connect completed with path \"%s\"", rfcomm_dev);
+	} else {
+		g_assert (!context);
+		_LOGI ("dun-connect failed with error: %s", error->message);
+	}
+
+	dun_connect_data->dun_context = context;
+
+	g_main_loop_quit (gl.loop);
+}
+
+static void
+_dun_notify_tty_hangup_cb (NMBluez5DunContext *context,
+                           gpointer user_data)
+{
+	_LOGI ("dun-connect: notified TTY hangup");
+}
+
+static gboolean
+_timeout_cb (gpointer user_data)
+{
+	DunConnectData *dun_connect_data = user_data;
+
+	_LOGI ("timeout");
+	dun_connect_data->timeout_id = 0;
+	if (dun_connect_data->cancellable)
+		g_cancellable_cancel (dun_connect_data->cancellable);
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_sig_xxx_cb (DunConnectData *dun_connect_data, int sigid)
+{
+	_LOGI ("signal %s received", sigid == SIGTERM ? "SIGTERM" : "SIGINT");
+	g_main_loop_quit (gl.loop);
+	return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+_sig_term_cb (gpointer user_data)
+{
+	return _sig_xxx_cb (user_data, SIGTERM);
+}
+
+static gboolean
+_sig_int_cb (gpointer user_data)
+{
+	return _sig_xxx_cb (user_data, SIGINT);
+}
+#endif
+
+static int
+do_dun_connect (const MainCmdInfo *main_cmd_info)
+{
+#if WITH_BLUEZ5_DUN
+	gs_unref_object GCancellable *cancellable = NULL;
+	gs_free_error GError *error = NULL;
+	const char *adapter;
+	const char *remote;
+	DunConnectData dun_connect_data = { };
+
+	if (gl.argc < 4) {
+		_LOGE ("missing arguments \"adapter\" and \"remote\"");
+		return -1;
+	}
+
+	adapter = gl.argv[2];
+	remote = gl.argv[3];
+
+	cancellable = g_cancellable_new ();
+	dun_connect_data.cancellable = cancellable;
+
+	if (!nm_bluez5_dun_connect (adapter,
+	                            remote,
+	                            cancellable,
+	                            _dun_connect_cb,
+	                            &dun_connect_data,
+	                            _dun_notify_tty_hangup_cb,
+	                            &dun_connect_data,
+	                            &error)) {
+		_LOGE ("connect failed to start: %s", error->message);
+		return -1;
+	}
+
+	dun_connect_data.timeout_id = g_timeout_add (60000, _timeout_cb, &dun_connect_data);
+
+	g_main_loop_run (gl.loop);
+
+	nm_clear_g_source (&dun_connect_data.timeout_id);
+
+	if (dun_connect_data.dun_context) {
+
+		dun_connect_data.sig_term_id = g_unix_signal_add (SIGTERM, _sig_term_cb, &dun_connect_data);
+		dun_connect_data.sig_int_id = g_unix_signal_add (SIGINT, _sig_int_cb, &dun_connect_data);
+
+		g_main_loop_run (gl.loop);
+
+		nm_clear_g_source (&dun_connect_data.sig_term_id);
+		nm_clear_g_source (&dun_connect_data.sig_int_id);
+
+		nm_bluez5_dun_disconnect (g_steal_pointer (&dun_connect_data.dun_context));
+	}
+
+	return 0;
+#else
+	_LOGE ("compiled without bluetooth DUN support");
+	return 1;
+#endif
+}
+
+/*****************************************************************************/
+
+NMTST_DEFINE ();
+
+int
+main (int argc, char **argv)
+{
+	static const MainCmdInfo main_cmd_infos[] = {
+		{ .name = "dun-connect", .main_func = do_dun_connect, },
+	};
+	int exit_code = 0;
+	guint i;
+
+	if (!g_getenv ("G_MESSAGES_DEBUG"))
+		g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+	nmtst_init_with_logging (&argc, &argv, "DEBUG", "ALL");
+
+	nm_logging_init (NULL, TRUE);
+
+	gl.argv = (const char *const*) argv;
+	gl.argc = argc;
+	gl.loop = g_main_loop_new (NULL, FALSE);
+
+	_LOGI ("bluetooth test util start");
+
+	gl.argv_cmd = argc >= 2 ? argv[1] : NULL;
+
+	for (i = 0; i < G_N_ELEMENTS (main_cmd_infos); i++) {
+		if (nm_streq0 (main_cmd_infos[i].name, gl.argv_cmd)) {
+			_LOGD ("start \"%s\"", gl.argv_cmd);
+			exit_code = main_cmd_infos[i].main_func (&main_cmd_infos[i]);
+			_LOGD ("completed with %d", exit_code);
+			break;
+		}
+	}
+	if (gl.argv_cmd && i >= G_N_ELEMENTS (main_cmd_infos)) {
+		nm_log_err (LOGD_BT, "invalid command \"%s\"", gl.argv_cmd);
+		exit_code = -1;
+	}
+
+	nm_clear_pointer (&gl.loop, g_main_loop_unref);
+
+	return exit_code;
+}
diff --git a/src/devices/nm-acd-manager.c b/src/devices/nm-acd-manager.c
index 036c4709..6e7a2a0f 100644
--- a/src/devices/nm-acd-manager.c
+++ b/src/devices/nm-acd-manager.c
@@ -1,16 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * Copyright (C) 2015-2018 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
diff --git a/src/devices/nm-acd-manager.h b/src/devices/nm-acd-manager.h
index 53c88e4b..4afe509d 100644
--- a/src/devices/nm-acd-manager.h
+++ b/src/devices/nm-acd-manager.h
@@ -1,16 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * Copyright (C) 2015-2018 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
 
 #ifndef __NM_ACD_MANAGER__
diff --git a/src/devices/nm-device-6lowpan.c b/src/devices/nm-device-6lowpan.c
index 7a279431..0289a360 100644
--- a/src/devices/nm-device-6lowpan.c
+++ b/src/devices/nm-device-6lowpan.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2018 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -230,20 +216,6 @@ update_connection (NMDevice *device, NMConnection *connection)
 	              NULL);
 }
 
-static NMActStageReturn
-act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *out_failure_reason)
-{
-	NMActStageReturn ret;
-
-	ret = NM_DEVICE_CLASS (nm_device_6lowpan_parent_class)->act_stage1_prepare (dev, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
-	if (!nm_device_hw_addr_set_cloned (dev, nm_device_get_applied_connection (dev), FALSE))
-		return NM_ACT_STAGE_RETURN_FAILURE;
-	return NM_ACT_STAGE_RETURN_SUCCESS;
-}
-
 /*****************************************************************************/
 
 static void
@@ -273,7 +245,7 @@ nm_device_6lowpan_class_init (NMDevice6LowpanClass *klass)
 	device_class->connection_type_check_compatible = NM_SETTING_6LOWPAN_SETTING_NAME;
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_6LOWPAN);
 
-	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE;
 	device_class->complete_connection = complete_connection;
 	device_class->create_and_realize = create_and_realize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
diff --git a/src/devices/nm-device-6lowpan.h b/src/devices/nm-device-6lowpan.h
index 86edf4af..7d427235 100644
--- a/src/devices/nm-device-6lowpan.h
+++ b/src/devices/nm-device-6lowpan.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2018 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_6LOWPAN_H__
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index 50494526..c6ecb2e8 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2011 - 2018 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2011 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -213,10 +199,10 @@ set_simple_option (NMDevice *device,
 	set_bond_attr (device, mode, opt, value);
 }
 
-static NMActStageReturn
-apply_bonding_config (NMDevice *device)
+static gboolean
+apply_bonding_config (NMDeviceBond *self)
 {
-	NMDeviceBond *self = NM_DEVICE_BOND (device);
+	NMDevice *device = NM_DEVICE (self);
 	NMSettingBond *s_bond;
 	int ifindex = nm_device_get_ifindex (device);
 	const char *mode_str, *value;
@@ -239,7 +225,7 @@ apply_bonding_config (NMDevice *device)
 
 	s_bond = nm_device_get_applied_setting (device, NM_TYPE_SETTING_BOND);
 
-	g_return_val_if_fail (s_bond, NM_ACT_STAGE_RETURN_FAILURE);
+	g_return_val_if_fail (s_bond, FALSE);
 
 	mode_str = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MODE);
 	if (!mode_str)
@@ -248,7 +234,7 @@ apply_bonding_config (NMDevice *device)
 	mode = _nm_setting_bond_mode_from_string (mode_str);
 	if (mode == NM_BOND_MODE_UNKNOWN) {
 		_LOGW (LOGD_BOND, "unknown bond mode '%s'", mode_str);
-		return NM_ACT_STAGE_RETURN_FAILURE;
+		return FALSE;
 	}
 
 	/* Set mode first, as some other options (e.g. arp_interval) are valid
@@ -334,24 +320,26 @@ apply_bonding_config (NMDevice *device)
 	else
 		set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA);
 
-	return NM_ACT_STAGE_RETURN_SUCCESS;
+	return TRUE;
 }
 
 static NMActStageReturn
-act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *out_failure_reason)
+act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
+	NMDeviceBond *self = NM_DEVICE_BOND (device);
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
 
-	ret = NM_DEVICE_CLASS (nm_device_bond_parent_class)->act_stage1_prepare (dev, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
 	/* Interface must be down to set bond options */
-	nm_device_take_down (dev, TRUE);
-	ret = apply_bonding_config (dev);
-	if (ret != NM_ACT_STAGE_RETURN_FAILURE)
-		ret = nm_device_hw_addr_set_cloned (dev, nm_device_get_applied_connection (dev), FALSE);
-	nm_device_bring_up (dev, TRUE, NULL);
+	nm_device_take_down (device, TRUE);
+	if (!apply_bonding_config (self))
+		ret = NM_ACT_STAGE_RETURN_FAILURE;
+	else {
+		if (!nm_device_hw_addr_set_cloned (device,
+		                                   nm_device_get_applied_connection (device),
+		                                   FALSE))
+			ret = NM_ACT_STAGE_RETURN_FAILURE;
+	}
+	nm_device_bring_up (device, TRUE, NULL);
 
 	return ret;
 }
@@ -416,10 +404,12 @@ release_slave (NMDevice *device,
 	int ifindex_slave;
 	int ifindex;
 
-	ifindex = nm_device_get_ifindex (device);
-	if (   ifindex <= 0
-	    || !nm_platform_link_get (nm_device_get_platform (device), ifindex))
-		configure = FALSE;
+	if (configure) {
+		ifindex = nm_device_get_ifindex (device);
+		if (   ifindex <= 0
+		    || !nm_platform_link_get (nm_device_get_platform (device), ifindex))
+			configure = FALSE;
+	}
 
 	ifindex_slave = nm_device_get_ip_ifindex (slave);
 
diff --git a/src/devices/nm-device-bond.h b/src/devices/nm-device-bond.h
index 9448950b..7964f2a2 100644
--- a/src/devices/nm-device-bond.h
+++ b/src/devices/nm-device-bond.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2012 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_BOND_H__
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index 91d824b3..72a8ce2b 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2011 - 2015 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2011 - 2015 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -36,7 +22,9 @@ _LOG_DECLARE_SELF(NMDeviceBridge);
 
 struct _NMDeviceBridge {
 	NMDevice parent;
+	GCancellable *bt_cancellable;
 	bool vlan_configured:1;
+	bool bt_registered:1;
 };
 
 struct _NMDeviceBridgeClass {
@@ -64,6 +52,7 @@ check_connection_available (NMDevice *device,
                             const char *specific_object,
                             GError **error)
 {
+	NMDeviceBridge *self = NM_DEVICE_BRIDGE (device);
 	NMSettingBluetooth *s_bt;
 
 	if (!NM_DEVICE_CLASS (nm_device_bridge_parent_class)->check_connection_available (device, connection, flags, specific_object, error))
@@ -80,13 +69,18 @@ check_connection_available (NMDevice *device,
 		}
 
 		bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt);
-		if (!nm_bt_vtable_network_server->is_available (nm_bt_vtable_network_server, bdaddr)) {
+		if (!nm_bt_vtable_network_server->is_available (nm_bt_vtable_network_server,
+		                                                bdaddr,
+		                                                  (   self->bt_cancellable
+		                                                   || self->bt_registered)
+		                                                ? device
+		                                                : NULL)) {
 			if (bdaddr)
 				nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-				                    "not suitable NAP device \"%s\" available", bdaddr);
+				                    "no suitable NAP device \"%s\" available", bdaddr);
 			else
 				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-				                            "not suitable NAP device available");
+				                            "no suitable NAP device available");
 			return FALSE;
 		}
 	}
@@ -225,7 +219,7 @@ commit_option (NMDevice *device, NMSetting *setting, const Option *option, gbool
 	GParamSpec *pspec;
 	GValue val = G_VALUE_INIT;
 	guint32 uval = 0;
-	gs_free char *value = NULL;
+	char value[100];
 
 	g_assert (setting);
 
@@ -258,10 +252,10 @@ commit_option (NMDevice *device, NMSetting *setting, const Option *option, gbool
 		if (option->user_hz_compensate)
 			uval *= 100;
 	} else
-		g_assert_not_reached ();
+		nm_assert_not_reached ();
 	g_value_unset (&val);
 
-	value = g_strdup_printf ("%u", uval);
+	nm_sprintf_buf (value, "%u", uval);
 	if (slave)
 		nm_platform_sysctl_slave_set_option (nm_device_get_platform (device), ifindex, option->sysname, value);
 	else
@@ -497,27 +491,16 @@ bridge_set_vlan_options (NMDevice *device, NMSettingBridge *s_bridge)
 static NMActStageReturn
 act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-	NMActStageReturn ret;
 	NMConnection *connection;
 	NMSetting *s_bridge;
 	const Option *option;
 
-	NM_DEVICE_BRIDGE (device)->vlan_configured = FALSE;
-
-	ret = NM_DEVICE_CLASS (nm_device_bridge_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
 	connection = nm_device_get_applied_connection (device);
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
 	s_bridge = (NMSetting *) nm_connection_get_setting_bridge (connection);
 	g_return_val_if_fail (s_bridge, NM_ACT_STAGE_RETURN_FAILURE);
 
-	if (!nm_device_hw_addr_set_cloned (device, connection, FALSE)) {
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
-		return NM_ACT_STAGE_RETURN_FAILURE;
-	}
-
 	for (option = master_options; option->name; option++)
 		commit_option (device, s_bridge, option, FALSE);
 
@@ -529,9 +512,53 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
+static void
+_bt_register_bridge_cb (GError *error,
+                        gpointer user_data)
+{
+	NMDeviceBridge *self;
+
+	if (nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	self = user_data;
+
+	g_clear_object (&self->bt_cancellable);
+
+	if (error) {
+		_LOGD (LOGD_DEVICE, "bluetooth NAP server failed to register bridge: %s", error->message);
+		nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_BT_FAILED);
+		return;
+	}
+
+	nm_device_activate_schedule_stage3_ip_config_start (NM_DEVICE (self));
+}
+
+void
+_nm_device_bridge_notify_unregister_bt_nap (NMDevice *device,
+                                            const char *reason)
+{
+	NMDeviceBridge *self = NM_DEVICE_BRIDGE (device);
+
+	_LOGD (LOGD_DEVICE, "bluetooth NAP server unregistered from bridge: %s%s",
+	       reason,
+	       self->bt_registered ? "" : " (was no longer registered)");
+
+	nm_clear_g_cancellable (&self->bt_cancellable);
+
+	if (self->bt_registered) {
+		self->bt_registered = FALSE;
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_BT_FAILED);
+	}
+}
+
 static NMActStageReturn
 act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
+	NMDeviceBridge *self = NM_DEVICE_BRIDGE (device);
 	NMConnection *connection;
 	NMSettingBluetooth *s_bt;
 
@@ -539,14 +566,32 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	s_bt = _nm_connection_get_setting_bluetooth_for_nap (connection);
 	if (s_bt) {
-		if (   !nm_bt_vtable_network_server
-		    || !nm_bt_vtable_network_server->register_bridge (nm_bt_vtable_network_server,
-		                                                      nm_setting_bluetooth_get_bdaddr (s_bt),
-		                                                      device)) {
-			/* The HCI we could use is no longer present. */
-			*out_failure_reason = NM_DEVICE_STATE_REASON_REMOVED;
+		gs_free_error GError *error = NULL;
+
+		if (!nm_bt_vtable_network_server) {
+			_LOGD (LOGD_DEVICE, "bluetooth NAP server failed because bluetooth plugin not available");
+			*out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED;
 			return NM_ACT_STAGE_RETURN_FAILURE;
 		}
+
+		if (self->bt_cancellable)
+			return NM_ACT_STAGE_RETURN_POSTPONE;
+
+		self->bt_cancellable = g_cancellable_new ();
+		if (!nm_bt_vtable_network_server->register_bridge (nm_bt_vtable_network_server,
+		                                                   nm_setting_bluetooth_get_bdaddr (s_bt),
+		                                                   device,
+		                                                   self->bt_cancellable,
+		                                                   _bt_register_bridge_cb,
+		                                                   device,
+		                                                   &error)) {
+			_LOGD (LOGD_DEVICE, "bluetooth NAP server failed to register bridge: %s", error->message);
+			*out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED;
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		}
+
+		self->bt_registered = TRUE;
+		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
 	return NM_ACT_STAGE_RETURN_SUCCESS;
@@ -555,9 +600,17 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 static void
 deactivate (NMDevice *device)
 {
-	if (nm_bt_vtable_network_server) {
-		/* always call unregister. It does nothing if the device
-		 * isn't registered as a hotspot bridge. */
+	NMDeviceBridge *self = NM_DEVICE_BRIDGE (device);
+
+	_LOGD (LOGD_DEVICE, "deactivate bridge%s",
+	       self->bt_registered ? " (registered as NAP bluetooth device)" : "");
+
+	self->vlan_configured = FALSE;
+
+	nm_clear_g_cancellable (&self->bt_cancellable);
+
+	if (self->bt_registered) {
+		self->bt_registered = FALSE;
 		nm_bt_vtable_network_server->unregister_bridge (nm_bt_vtable_network_server,
 		                                                device);
 	}
@@ -628,10 +681,12 @@ release_slave (NMDevice *device,
 	int ifindex_slave;
 	int ifindex;
 
-	ifindex = nm_device_get_ifindex (device);
-	if (   ifindex <= 0
-	    || !nm_platform_link_get (nm_device_get_platform (device), ifindex))
-		configure = FALSE;
+	if (configure) {
+		ifindex = nm_device_get_ifindex (device);
+		if (   ifindex <= 0
+		    || !nm_platform_link_get (nm_device_get_platform (device), ifindex))
+			configure = FALSE;
+	}
 
 	ifindex_slave = nm_device_get_ip_ifindex (slave);
 
@@ -758,6 +813,7 @@ nm_device_bridge_class_init (NMDeviceBridgeClass *klass)
 	device_class->master_update_slave_connection = master_update_slave_connection;
 
 	device_class->create_and_realize = create_and_realize;
+	device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE;
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
 	device_class->deactivate = deactivate;
diff --git a/src/devices/nm-device-bridge.h b/src/devices/nm-device-bridge.h
index bc5ed04c..fb614d40 100644
--- a/src/devices/nm-device-bridge.h
+++ b/src/devices/nm-device-bridge.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2012 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_BRIDGE_H__
@@ -36,4 +22,7 @@ GType nm_device_bridge_get_type (void);
 
 extern const NMBtVTableNetworkServer *nm_bt_vtable_network_server;
 
+void _nm_device_bridge_notify_unregister_bt_nap (NMDevice *device,
+                                                 const char *reason);
+
 #endif /* __NETWORKMANAGER_DEVICE_BRIDGE_H__ */
diff --git a/src/devices/nm-device-dummy.c b/src/devices/nm-device-dummy.c
index 9c0c3035..465a293b 100644
--- a/src/devices/nm-device-dummy.c
+++ b/src/devices/nm-device-dummy.c
@@ -1,14 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * Copyright 2017 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -116,21 +108,6 @@ create_and_realize (NMDevice *device,
 	return TRUE;
 }
 
-static NMActStageReturn
-act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMActStageReturn ret;
-
-	ret = NM_DEVICE_CLASS (nm_device_dummy_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
-	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
-		return NM_ACT_STAGE_RETURN_FAILURE;
-
-	return NM_ACT_STAGE_RETURN_SUCCESS;
-}
-
 /*****************************************************************************/
 
 static void
@@ -167,7 +144,7 @@ nm_device_dummy_class_init (NMDeviceDummyClass *klass)
 	device_class->create_and_realize = create_and_realize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->update_connection = update_connection;
-	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE;
 	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
 }
 
diff --git a/src/devices/nm-device-dummy.h b/src/devices/nm-device-dummy.h
index a380d366..e95be72d 100644
--- a/src/devices/nm-device-dummy.h
+++ b/src/devices/nm-device-dummy.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2017 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_DUMMY_H__
diff --git a/src/devices/nm-device-ethernet-utils.c b/src/devices/nm-device-ethernet-utils.c
index 1d7a060e..79673201 100644
--- a/src/devices/nm-device-ethernet-utils.c
+++ b/src/devices/nm-device-ethernet-utils.c
@@ -1,18 +1,6 @@
-/* This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * (C) Copyright 2011 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
  */
 
 #include "nm-default.h"
diff --git a/src/devices/nm-device-ethernet-utils.h b/src/devices/nm-device-ethernet-utils.h
index 6355c4ec..0e16f4b8 100644
--- a/src/devices/nm-device-ethernet-utils.h
+++ b/src/devices/nm-device-ethernet-utils.h
@@ -1,18 +1,6 @@
-/* This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * (C) Copyright 2011 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_ETHERNET_UTILS_H__
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index b2b05883..6b80c4ed 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2005 - 2014 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
@@ -853,14 +839,19 @@ link_negotiation_set (NMDevice *device)
 		autoneg = nm_setting_wired_get_auto_negotiate (s_wired);
 		speed = nm_setting_wired_get_speed (s_wired);
 		duplex = link_duplex_to_platform (nm_setting_wired_get_duplex (s_wired));
-		if (!autoneg && !speed && !duplex) {
+		if (   !autoneg
+		    && !speed
+		    && !duplex) {
 			_LOGD (LOGD_DEVICE, "set-link: ignore link negotiation");
 			return;
 		}
 	}
 
-	if (!nm_platform_ethtool_get_link_settings (nm_device_get_platform (device), nm_device_get_ifindex (device),
-	                                            &link_autoneg, &link_speed, &link_duplex)) {
+	if (!nm_platform_ethtool_get_link_settings (nm_device_get_platform (device),
+	                                            nm_device_get_ifindex (device),
+	                                            &link_autoneg,
+	                                            &link_speed,
+	                                            &link_duplex)) {
 		_LOGW (LOGD_DEVICE, "set-link: unable to retrieve link negotiation");
 		return;
 	}
@@ -873,16 +864,18 @@ link_negotiation_set (NMDevice *device)
 		return;
 	}
 
-	if (autoneg && !speed && !duplex)
+	if (   autoneg
+	    && !speed
+	    && !duplex)
 		_LOGD (LOGD_DEVICE, "set-link: configure auto-negotiation");
 	else {
 		_LOGD (LOGD_DEVICE, "set-link: configure %snegotiation (%u Mbit%s - %s duplex%s)",
 		       autoneg ? "auto-" : "static ",
 		       speed ?: link_speed,
 		       speed ? "" : "*",
-		       duplex
-		         ? nm_platform_link_duplex_type_to_string (duplex)
-		         : nm_platform_link_duplex_type_to_string (link_duplex),
+		         duplex
+		       ? nm_platform_link_duplex_type_to_string (duplex)
+		       : nm_platform_link_duplex_type_to_string (link_duplex),
 		       duplex ? "" : "*");
 	}
 
@@ -903,9 +896,10 @@ pppoe_reconnect_delay (gpointer user_data)
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 
 	priv->pppoe_wait_id = 0;
+	priv->last_pppoe_time = 0;
 	_LOGI (LOGD_DEVICE, "PPPoE reconnect delay complete, resuming connection...");
-	nm_device_activate_schedule_stage2_device_config (NM_DEVICE (self));
-	return FALSE;
+	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+	return G_SOURCE_REMOVE;
 }
 
 static NMActStageReturn
@@ -913,35 +907,33 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
-	NMActStageReturn ret;
-
-	ret = NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
 
 	link_negotiation_set (device);
 
-	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
-		return NM_ACT_STAGE_RETURN_FAILURE;
-
 	/* If we're re-activating a PPPoE connection a short while after
 	 * a previous PPPoE connection was torn down, wait a bit to allow the
 	 * remote side to handle the disconnection.  Otherwise the peer may
 	 * get confused and fail to negotiate the new connection. (rh #1023503)
+	 *
+	 * FIXME(shutdown): when exiting, we also need to wait before quiting,
+	 * at least for additional NM_SHUTDOWN_TIMEOUT_MS seconds because
+	 * otherwise after restart the device won't work for the first seconds.
 	 */
-	if (priv->last_pppoe_time) {
+	if (priv->last_pppoe_time != 0) {
 		gint32 delay = nm_utils_get_monotonic_timestamp_s () - priv->last_pppoe_time;
 
 		if (   delay < PPPOE_RECONNECT_DELAY
 		    && nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPPOE)) {
-			_LOGI (LOGD_DEVICE, "delaying PPPoE reconnect for %d seconds to ensure peer is ready...",
-			       delay);
-			g_assert (!priv->pppoe_wait_id);
-			priv->pppoe_wait_id = g_timeout_add_seconds (delay,
-			                                             pppoe_reconnect_delay,
-			                                             self);
+			if (priv->pppoe_wait_id == 0) {
+				_LOGI (LOGD_DEVICE, "delaying PPPoE reconnect for %d seconds to ensure peer is ready...",
+				       delay);
+				priv->pppoe_wait_id = g_timeout_add_seconds (delay,
+				                                             pppoe_reconnect_delay,
+				                                             self);
+			}
 			return NM_ACT_STAGE_RETURN_POSTPONE;
 		}
+		nm_clear_g_source (&priv->pppoe_wait_id);
 		priv->last_pppoe_time = 0;
 	}
 
@@ -1743,6 +1735,7 @@ static void
 reapply_connection (NMDevice *device, NMConnection *con_old, NMConnection *con_new)
 {
 	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
+	NMDeviceState state = nm_device_get_state (device);
 
 	NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->reapply_connection (device,
 	                                                                       con_old,
@@ -1750,8 +1743,10 @@ reapply_connection (NMDevice *device, NMConnection *con_old, NMConnection *con_n
 
 	_LOGD (LOGD_DEVICE, "reapplying wired settings");
 
-	link_negotiation_set (device);
-	wake_on_lan_enable (device);
+	if (state >= NM_DEVICE_STATE_PREPARE)
+		link_negotiation_set (device);
+	if (state >= NM_DEVICE_STATE_CONFIG)
+		wake_on_lan_enable (device);
 }
 
 static void
@@ -1864,6 +1859,7 @@ nm_device_ethernet_class_init (NMDeviceEthernetClass *klass)
 	device_class->new_default_connection = new_default_connection;
 
 	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE;
 	device_class->act_stage2_config = act_stage2_config;
 	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->get_configured_mtu = get_configured_mtu;
diff --git a/src/devices/nm-device-ethernet.h b/src/devices/nm-device-ethernet.h
index d7de519c..08962232 100644
--- a/src/devices/nm-device-ethernet.h
+++ b/src/devices/nm-device-ethernet.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2005 - 2010 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c
index c0ec1d2e..232e69ef 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/devices/nm-device-factory.c
@@ -1,19 +1,5 @@
-/* NetworkManager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2014 - 2018 Red Hat, Inc.
  */
 
@@ -36,7 +22,6 @@
 
 enum {
 	DEVICE_ADDED,
-	COMPONENT_ADDED,
 	LAST_SIGNAL
 };
 
@@ -46,17 +31,6 @@ G_DEFINE_ABSTRACT_TYPE (NMDeviceFactory, nm_device_factory, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
-gboolean
-nm_device_factory_emit_component_added (NMDeviceFactory *factory, GObject *component)
-{
-	gboolean consumed = FALSE;
-
-	g_return_val_if_fail (NM_IS_DEVICE_FACTORY (factory), FALSE);
-
-	g_signal_emit (factory, signals[COMPONENT_ADDED], 0, component, &consumed);
-	return consumed;
-}
-
 static void
 nm_device_factory_get_supported_types (NMDeviceFactory *factory,
                                        const NMLinkType **out_link_types,
@@ -195,16 +169,8 @@ nm_device_factory_class_init (NMDeviceFactoryClass *klass)
 	signals[DEVICE_ADDED] = g_signal_new (NM_DEVICE_FACTORY_DEVICE_ADDED,
 	                                      G_OBJECT_CLASS_TYPE (object_class),
 	                                      G_SIGNAL_RUN_FIRST,
-	                                      G_STRUCT_OFFSET (NMDeviceFactoryClass, device_added),
-	                                      NULL, NULL, NULL,
+	                                      0, NULL, NULL, NULL,
 	                                      G_TYPE_NONE, 1, NM_TYPE_DEVICE);
-
-	signals[COMPONENT_ADDED] = g_signal_new (NM_DEVICE_FACTORY_COMPONENT_ADDED,
-	                                         G_OBJECT_CLASS_TYPE (object_class),
-	                                         G_SIGNAL_RUN_LAST,
-	                                         G_STRUCT_OFFSET (NMDeviceFactoryClass, component_added),
-	                                         g_signal_accumulator_true_handled, NULL, NULL,
-	                                         G_TYPE_BOOLEAN, 1, G_TYPE_OBJECT);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-factory.h b/src/devices/nm-device-factory.h
index 7fe371ad..7a323d49 100644
--- a/src/devices/nm-device-factory.h
+++ b/src/devices/nm-device-factory.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2007 - 2014 Red Hat, Inc.
  */
 
@@ -36,7 +22,6 @@
 #define NM_IS_DEVICE_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEVICE_FACTORY))
 #define NM_DEVICE_FACTORY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEVICE_FACTORY, NMDeviceFactoryClass))
 
-#define NM_DEVICE_FACTORY_COMPONENT_ADDED "component-added"
 #define NM_DEVICE_FACTORY_DEVICE_ADDED    "device-added"
 
 typedef struct {
@@ -131,36 +116,6 @@ typedef struct {
 	                               NMConnection *connection,
 	                               gboolean *out_ignore);
 
-	/* Signals */
-
-	/**
-	 * device_added:
-	 * @factory: the #NMDeviceFactory
-	 * @device: the new #NMDevice subclass
-	 *
-	 * The factory emits this signal if it finds a new device by itself.
-	 */
-	void       (*device_added)    (NMDeviceFactory *factory, NMDevice *device);
-
-	/**
-	 * component_added:
-	 * @factory: the #NMDeviceFactory
-	 * @component: a new component which existing devices may wish to claim
-	 *
-	 * The factory emits this signal when an appearance of some component
-	 * native to it could be interesting to some of the already existing devices.
-	 * The devices then indicate if they took interest in claiming the component.
-	 *
-	 * For example, the WWAN factory may indicate that a new modem is available,
-	 * which an existing Bluetooth device may wish to claim. It emits a signal
-	 * passing the modem instance around to see if any device claims it.
-	 * If no device claims the component, the plugin is allowed to create a new
-	 * #NMDevice instance for that component and emit the "device-added" signal.
-	 *
-	 * Returns: %TRUE if the component was claimed by a device, %FALSE if not
-	 */
-	gboolean   (*component_added) (NMDeviceFactory *factory, GObject *component);
-
 } NMDeviceFactoryClass;
 
 GType      nm_device_factory_get_type    (void);
@@ -202,10 +157,6 @@ NMDevice * nm_device_factory_create_device (NMDeviceFactory *factory,
                                             gboolean *out_ignore,
                                             GError **error);
 
-/* For use by implementations */
-gboolean   nm_device_factory_emit_component_added (NMDeviceFactory *factory,
-                                                   GObject *component);
-
 #define NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(...) \
 	{ static NMLinkType const _link_types_declared[] = { __VA_ARGS__, NM_LINK_TYPE_NONE }; _link_types = _link_types_declared; }
 #define NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(...) \
diff --git a/src/devices/nm-device-generic.c b/src/devices/nm-device-generic.c
index 39232acf..5f9e13c3 100644
--- a/src/devices/nm-device-generic.c
+++ b/src/devices/nm-device-generic.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2013 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
  */
 
 #include "nm-default.h"
diff --git a/src/devices/nm-device-generic.h b/src/devices/nm-device-generic.h
index 9c59851a..5dd90a3a 100644
--- a/src/devices/nm-device-generic.h
+++ b/src/devices/nm-device-generic.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2013 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_GENERIC_H__
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index e39329a9..9f79d9bb 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2011 - 2018 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2011 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -76,16 +62,11 @@ static NMActStageReturn
 act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	nm_auto_close int dirfd = -1;
-	NMActStageReturn ret;
 	NMSettingInfiniband *s_infiniband;
 	char ifname_verified[IFNAMSIZ];
 	const char *transport_mode;
 	gboolean ok;
 
-	ret = NM_DEVICE_CLASS (nm_device_infiniband_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
 	s_infiniband = nm_device_get_applied_setting (device, NM_TYPE_SETTING_INFINIBAND);
 
 	g_return_val_if_fail (s_infiniband, NM_ACT_STAGE_RETURN_FAILURE);
@@ -94,7 +75,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	dirfd = nm_platform_sysctl_open_netdir (nm_device_get_platform (device), nm_device_get_ifindex (device), ifname_verified);
 	if (dirfd < 0) {
-		if (!strcmp (transport_mode, "datagram"))
+		if (nm_streq (transport_mode, "datagram"))
 			return NM_ACT_STAGE_RETURN_SUCCESS;
 		else {
 			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_INFINIBAND_MODE);
@@ -210,6 +191,32 @@ update_connection (NMDevice *device, NMConnection *connection)
 }
 
 static gboolean
+can_reapply_change (NMDevice *device,
+                    const char *setting_name,
+                    NMSetting *s_old,
+                    NMSetting *s_new,
+                    GHashTable *diffs,
+                    GError **error)
+{
+	NMDeviceClass *device_class;
+
+	if (nm_streq (setting_name, NM_SETTING_INFINIBAND_SETTING_NAME)) {
+		return nm_device_hash_check_invalid_keys (diffs,
+		                                          NM_SETTING_INFINIBAND_SETTING_NAME,
+		                                          error,
+		                                          NM_SETTING_INFINIBAND_MTU); /* reapplied with IP config */
+	}
+
+	device_class = NM_DEVICE_CLASS (nm_device_infiniband_parent_class);
+	return device_class->can_reapply_change (device,
+	                                         setting_name,
+	                                         s_old,
+	                                         s_new,
+	                                         diffs,
+	                                         error);
+}
+
+static gboolean
 create_and_realize (NMDevice *device,
                     NMConnection *connection,
                     NMDevice *parent,
@@ -361,6 +368,7 @@ nm_device_infiniband_class_init (NMDeviceInfinibandClass *klass)
 	device_class->connection_type_check_compatible = NM_SETTING_INFINIBAND_SETTING_NAME;
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_INFINIBAND);
 
+	device_class->can_reapply_change = can_reapply_change;
 	device_class->create_and_realize = create_and_realize;
 	device_class->unrealize = unrealize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
diff --git a/src/devices/nm-device-infiniband.h b/src/devices/nm-device-infiniband.h
index 55d0aadc..6f46ef50 100644
--- a/src/devices/nm-device-infiniband.h
+++ b/src/devices/nm-device-infiniband.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2011 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_INFINIBAND_H__
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index f070dd5f..0becb5e5 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2015 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -967,21 +953,6 @@ set_property (GObject *object, guint prop_id,
 	}
 }
 
-static NMActStageReturn
-act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMActStageReturn ret;
-
-	ret = NM_DEVICE_CLASS (nm_device_ip_tunnel_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
-	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
-		return NM_ACT_STAGE_RETURN_FAILURE;
-
-	return NM_ACT_STAGE_RETURN_SUCCESS;
-}
-
 /*****************************************************************************/
 
 static void
@@ -1068,7 +1039,7 @@ nm_device_ip_tunnel_class_init (NMDeviceIPTunnelClass *klass)
 	                                                        NM_LINK_TYPE_IPIP,
 	                                                        NM_LINK_TYPE_SIT);
 
-	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE;
 	device_class->link_changed = link_changed;
 	device_class->can_reapply_change = can_reapply_change;
 	device_class->complete_connection = complete_connection;
diff --git a/src/devices/nm-device-ip-tunnel.h b/src/devices/nm-device-ip-tunnel.h
index e38d36eb..a8f30711 100644
--- a/src/devices/nm-device-ip-tunnel.h
+++ b/src/devices/nm-device-ip-tunnel.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2015 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_IP_TUNNEL_H__
diff --git a/src/devices/nm-device-logging.h b/src/devices/nm-device-logging.h
index c45a0552..2c56e526 100644
--- a/src/devices/nm-device-logging.h
+++ b/src/devices/nm-device-logging.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
diff --git a/src/devices/nm-device-macsec.c b/src/devices/nm-device-macsec.c
index 13798f5e..c9592a49 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/devices/nm-device-macsec.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2017 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -324,8 +310,10 @@ macsec_secrets_cb (NMActRequest *req,
 		nm_device_state_changed (device,
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
-	} else
-		nm_device_activate_schedule_stage1_device_prepare (device);
+		return;
+	}
+
+	nm_device_activate_schedule_stage1_device_prepare (device);
 }
 
 static void
diff --git a/src/devices/nm-device-macsec.h b/src/devices/nm-device-macsec.h
index e6919d4c..38a7dbae 100644
--- a/src/devices/nm-device-macsec.h
+++ b/src/devices/nm-device-macsec.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2017 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #ifndef __NM_DEVICE_MACSEC_H__
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index 964e0f61..3633f02d 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2013 - 2015 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 - 2015 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -430,21 +416,6 @@ update_connection (NMDevice *device, NMConnection *connection)
 	              NULL);
 }
 
-static NMActStageReturn
-act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMActStageReturn ret;
-
-	ret = NM_DEVICE_CLASS (nm_device_macvlan_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
-	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
-		return NM_ACT_STAGE_RETURN_FAILURE;
-
-	return NM_ACT_STAGE_RETURN_SUCCESS;
-}
-
 /*****************************************************************************/
 
 static void
@@ -536,7 +507,7 @@ nm_device_macvlan_class_init (NMDeviceMacvlanClass *klass)
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_MACVLAN, NM_LINK_TYPE_MACVTAP);
 	device_class->mtu_parent_delta = 0;
 
-	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->complete_connection = complete_connection;
 	device_class->create_and_realize = create_and_realize;
diff --git a/src/devices/nm-device-macvlan.h b/src/devices/nm-device-macvlan.h
index 9e76f5c9..8fcf97cd 100644
--- a/src/devices/nm-device-macvlan.h
+++ b/src/devices/nm-device-macvlan.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2013 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_MACVLAN_H__
diff --git a/src/devices/nm-device-ppp.c b/src/devices/nm-device-ppp.c
index f2e68f73..32403ff6 100644
--- a/src/devices/nm-device-ppp.c
+++ b/src/devices/nm-device-ppp.c
@@ -1,13 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
@@ -88,7 +80,7 @@ ppp_ifindex_set (NMPPPManager *ppp_manager,
 	}
 
 	if (old_name)
-		nm_manager_remove_device (nm_manager_get (), old_name, NM_DEVICE_TYPE_PPP);
+		nm_manager_remove_device (NM_MANAGER_GET, old_name, NM_DEVICE_TYPE_PPP);
 
 	nm_device_activate_schedule_stage3_ip_config_start (device);
 }
diff --git a/src/devices/nm-device-ppp.h b/src/devices/nm-device-ppp.h
index 097f4b97..5a169b37 100644
--- a/src/devices/nm-device-ppp.h
+++ b/src/devices/nm-device-ppp.h
@@ -1,13 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 83feb130..e87733ef 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2007 - 2008 Novell, Inc.
  * Copyright (C) 2007 - 2011 Red Hat, Inc.
  */
@@ -26,6 +12,12 @@
 /* This file should only be used by subclasses of NMDevice */
 
 typedef enum {
+	NM_DEVICE_STAGE_STATE_INIT      = 0,
+	NM_DEVICE_STAGE_STATE_PENDING   = 1,
+	NM_DEVICE_STAGE_STATE_COMPLETED = 2,
+} NMDeviceStageState;
+
+typedef enum {
 	NM_DEVICE_IP_STATE_NONE,
 	NM_DEVICE_IP_STATE_WAIT,
 	NM_DEVICE_IP_STATE_CONF,
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index afe83f50..77b46a59 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2013 - 2015 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 - 2015 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -351,18 +337,17 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceTun *self = NM_DEVICE_TUN (device);
 	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
-	NMActStageReturn ret;
 
-	ret = NM_DEVICE_CLASS (nm_device_tun_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
-	/* Nothing to do for TUN devices */
-	if (priv->props.type == IFF_TUN)
-		return NM_ACT_STAGE_RETURN_SUCCESS;
-
-	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
-		return NM_ACT_STAGE_RETURN_FAILURE;
+	if (priv->props.type == IFF_TUN) {
+		/* Nothing to do for TUN devices */
+	} else {
+		if (!nm_device_hw_addr_set_cloned (device,
+		                                   nm_device_get_applied_connection (device),
+		                                   FALSE)) {
+			*out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED;
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		}
+	}
 
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
diff --git a/src/devices/nm-device-tun.h b/src/devices/nm-device-tun.h
index f665b942..2f87d3bc 100644
--- a/src/devices/nm-device-tun.h
+++ b/src/devices/nm-device-tun.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2013 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_TUN_H__
diff --git a/src/devices/nm-device-veth.c b/src/devices/nm-device-veth.c
index 0d6425d4..5c26a036 100644
--- a/src/devices/nm-device-veth.c
+++ b/src/devices/nm-device-veth.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2013 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
  */
 
 #include "nm-default.h"
diff --git a/src/devices/nm-device-veth.h b/src/devices/nm-device-veth.h
index 07217ff9..15da6ab8 100644
--- a/src/devices/nm-device-veth.h
+++ b/src/devices/nm-device-veth.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2013 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_VETH_H__
diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c
index f8f6a597..b6efeb81 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/devices/nm-device-vlan.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2011 - 2012 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2011 - 2012 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -477,14 +463,6 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDevice *parent_device;
 	NMSettingVlan *s_vlan;
-	NMActStageReturn ret;
-
-	ret = NM_DEVICE_CLASS (nm_device_vlan_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
-	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
-		return NM_ACT_STAGE_RETURN_FAILURE;
 
 	/* Change MAC address to parent's one if needed */
 	parent_device = nm_device_parent_get_device (device);
@@ -497,7 +475,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	if (s_vlan) {
 		gs_free NMVlanQosMapping *ingress_map = NULL;
 		gs_free NMVlanQosMapping *egress_map = NULL;
-		guint n_ingress_map = 0, n_egress_map = 0;
+		guint n_ingress_map = 0;
+		guint n_egress_map = 0;
 
 		_nm_setting_vlan_get_priorities (s_vlan,
 		                                 NM_VLAN_INGRESS_MAP,
@@ -520,7 +499,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		                              n_egress_map);
 	}
 
-	return ret;
+	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
 /*****************************************************************************/
@@ -584,6 +563,7 @@ nm_device_vlan_class_init (NMDeviceVlanClass *klass)
 	device_class->link_changed = link_changed;
 	device_class->unrealize_notify = unrealize_notify;
 	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE;
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->get_configured_mtu = nm_device_get_configured_mtu_wired_parent;
 	device_class->is_available = is_available;
diff --git a/src/devices/nm-device-vlan.h b/src/devices/nm-device-vlan.h
index c463db58..a5d50a4a 100644
--- a/src/devices/nm-device-vlan.h
+++ b/src/devices/nm-device-vlan.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2012 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_VLAN_H__
diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c
index 1a700ba4..29be6854 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/devices/nm-device-vxlan.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2013 - 2015 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013 - 2015 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -481,21 +467,6 @@ update_connection (NMDevice *device, NMConnection *connection)
 	}
 }
 
-static NMActStageReturn
-act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMActStageReturn ret;
-
-	ret = NM_DEVICE_CLASS (nm_device_vxlan_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
-	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
-		return NM_ACT_STAGE_RETURN_FAILURE;
-
-	return NM_ACT_STAGE_RETURN_SUCCESS;
-}
-
 /*****************************************************************************/
 
 static void
@@ -620,7 +591,7 @@ nm_device_vxlan_class_init (NMDeviceVxlanClass *klass)
 	device_class->complete_connection = complete_connection;
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->update_connection = update_connection;
-	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE;
 	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
 
 	obj_properties[PROP_ID] =
diff --git a/src/devices/nm-device-vxlan.h b/src/devices/nm-device-vxlan.h
index dd14d910..a36bdf49 100644
--- a/src/devices/nm-device-vxlan.h
+++ b/src/devices/nm-device-vxlan.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2013, 2014 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2013, 2014 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_VXLAN_H__
diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c
index 5d640b62..c916fa46 100644
--- a/src/devices/nm-device-wireguard.c
+++ b/src/devices/nm-device-wireguard.c
@@ -1,20 +1,6 @@
+// SPDX-License-Identifier: LGPL-2.1+
 /*
- * 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 2018 Javier Arteaga <jarteaga@jbeta.is>
+ * Copyright (C) 2018 Javier Arteaga <jarteaga@jbeta.is>
  */
 
 #include "nm-default.h"
@@ -1276,8 +1262,10 @@ _secrets_cb (NMActRequest *req,
 		nm_device_state_changed (device,
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
-	} else
-		nm_device_activate_schedule_stage1_device_prepare (device);
+		return;
+	}
+
+	nm_device_activate_schedule_stage1_device_prepare (device);
 }
 
 static void
@@ -1518,17 +1506,6 @@ link_config_delayed_resolver_cb (gpointer user_data)
 }
 
 static NMActStageReturn
-act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (device);
-
-	priv->auto_default_route_initialized = FALSE;
-	priv->auto_default_route_priority_initialized = FALSE;
-
-	return NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->act_stage1_prepare (device, out_failure_reason);
-}
-
-static NMActStageReturn
 act_stage2_config (NMDevice *device,
                    NMDeviceStateReason *out_failure_reason)
 {
@@ -1810,23 +1787,27 @@ reapply_connection (NMDevice *device,
 	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
 	gs_unref_object NMIPConfig *ip4_config = NULL;
 	gs_unref_object NMIPConfig *ip6_config = NULL;
-
-	priv->auto_default_route_refresh = TRUE;
-
-	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);
+	NMDeviceState state = nm_device_get_state (device);
 
 	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);
+	if (state >= NM_DEVICE_STATE_CONFIG) {
+		priv->auto_default_route_refresh = TRUE;
+		link_config (NM_DEVICE_WIREGUARD (device),
+		             "reapply",
+		             LINK_CONFIG_MODE_REAPPLY,
+		             NULL);
+	}
+
+	if (state >= NM_DEVICE_STATE_IP_CONFIG) {
+		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);
+	}
 }
 
 /*****************************************************************************/
@@ -1968,7 +1949,6 @@ nm_device_wireguard_class_init (NMDeviceWireGuardClass *klass)
 	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->act_stage1_prepare = act_stage1_prepare;
 	device_class->state_changed = device_state_changed;
 	device_class->create_and_realize = create_and_realize;
 	device_class->act_stage2_config = act_stage2_config;
diff --git a/src/devices/nm-device-wireguard.h b/src/devices/nm-device-wireguard.h
index 3ad41f9b..67318042 100644
--- a/src/devices/nm-device-wireguard.h
+++ b/src/devices/nm-device-wireguard.h
@@ -1,20 +1,6 @@
+// SPDX-License-Identifier: LGPL-2.1+
 /*
- * 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 2018 Javier Arteaga <jarteaga@jbeta.is>
+ * Copyright (C) 2018 Javier Arteaga <jarteaga@jbeta.is>
  */
 
 #ifndef __NM_DEVICE_WIREGUARD_H__
diff --git a/src/devices/nm-device-wpan.c b/src/devices/nm-device-wpan.c
index 88234412..b947aba6 100644
--- a/src/devices/nm-device-wpan.c
+++ b/src/devices/nm-device-wpan.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2018 Lubomir Rintel <lkundrak@v3.sk>
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk>
  */
 
 #include "nm-default.h"
@@ -131,12 +117,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMDevice *lowpan_device = NULL;
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 
-	ret = NM_DEVICE_CLASS (nm_device_wpan_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
 	platform = nm_device_get_platform (device);
-	g_return_val_if_fail (platform, NM_ACT_STAGE_RETURN_FAILURE);
+	nm_assert (NM_IS_PLATFORM (platform));
 
 	ifindex = nm_device_get_ifindex (device);
 
@@ -147,7 +129,11 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	g_return_val_if_fail (s_wpan, NM_ACT_STAGE_RETURN_FAILURE);
 
 	hwaddr = nm_platform_link_get_address (platform, ifindex, &hwaddr_len);
-	g_return_val_if_fail (hwaddr, NM_ACT_STAGE_RETURN_FAILURE);
+
+	if (!hwaddr) {
+		*out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED;
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
 
 	/* As of kernel 4.16, the 6LoWPAN devices layered on top of WPANs
 	 * need to be DOWN as well as the WPAN device itself in order to
@@ -156,8 +142,9 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	                                                NM_LINK_TYPE_6LOWPAN,
 	                                                hwaddr,
 	                                                hwaddr_len);
-	if (lowpan_plink && NM_FLAGS_HAS (lowpan_plink->n_ifi_flags, IFF_UP)) {
-		lowpan_device = nm_manager_get_device_by_ifindex (nm_manager_get (),
+	if (   lowpan_plink
+	    && NM_FLAGS_HAS (lowpan_plink->n_ifi_flags, IFF_UP)) {
+		lowpan_device = nm_manager_get_device_by_ifindex (NM_MANAGER_GET,
 		                                                  lowpan_plink->ifindex);
 	}
 
@@ -192,6 +179,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	}
 
 	ret = NM_ACT_STAGE_RETURN_SUCCESS;
+
 out:
 	nm_device_bring_up (device, TRUE, NULL);
 
diff --git a/src/devices/nm-device-wpan.h b/src/devices/nm-device-wpan.h
index 33f24776..d86a9614 100644
--- a/src/devices/nm-device-wpan.h
+++ b/src/devices/nm-device-wpan.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2018 Lubomir Rintel <lkundrak@v3.sk>
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk>
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_WPAN_H__
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index b3d97af7..e7a4a059 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2005 - 2018 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
@@ -64,6 +50,7 @@
 #include "settings/nm-settings-connection.h"
 #include "settings/nm-settings.h"
 #include "nm-setting-ethtool.h"
+#include "nm-setting-user.h"
 #include "nm-auth-utils.h"
 #include "nm-keep-alive.h"
 #include "nm-netns.h"
@@ -102,11 +89,6 @@ _LOG_DECLARE_SELF (NMDevice);
 
 typedef void (*ActivationHandleFunc) (NMDevice *self);
 
-typedef struct {
-	ActivationHandleFunc func;
-	guint id;
-} ActivationHandleData;
-
 typedef enum {
 	CLEANUP_TYPE_KEEP,
 	CLEANUP_TYPE_REMOVED,
@@ -243,6 +225,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDevice,
 	PROP_RX_BYTES,
 	PROP_IP4_CONNECTIVITY,
 	PROP_IP6_CONNECTIVITY,
+	PROP_INTERFACE_FLAGS,
 );
 
 typedef struct _NMDevicePrivate {
@@ -333,8 +316,23 @@ typedef struct _NMDevicePrivate {
 	NMActRequest *  queued_act_request;
 	bool            queued_act_request_is_waiting_for_carrier:1;
 	NMDBusTrackObjPath act_request;
-	ActivationHandleData act_handle4; /* for layer2 and IPv4. */
-	ActivationHandleData act_handle6;
+
+	union {
+		struct {
+			guint activation_source_id_6;
+			guint activation_source_id_4; /* for layer2 and IPv4. */
+		};
+		guint activation_source_id_x[2];
+	};
+
+	union {
+		struct {
+			ActivationHandleFunc activation_source_func_6;
+			ActivationHandleFunc activation_source_func_4; /* for layer2 and IPv4. */
+		};
+		ActivationHandleFunc activation_source_func_x[2];
+	};
+
 	guint           recheck_assume_id;
 
 	struct {
@@ -394,7 +392,6 @@ typedef struct _NMDevicePrivate {
 	NMDeviceAutoconnectBlockedFlags autoconnect_blocked_flags:5;
 
 	bool            is_enslaved:1;
-	bool            master_ready_handled:1;
 
 	bool            ipv6ll_handle:1; /* TRUE if NM handles the device's IPv6LL address */
 	bool            ipv6ll_has:1;
@@ -403,6 +400,8 @@ typedef struct _NMDevicePrivate {
 
 	bool            concheck_rp_filter_checked:1;
 
+	NMDeviceStageState stage1_sriov_state:3;
+
 	/* Generic DHCP stuff */
 	char *          dhcp_anycast_address;
 
@@ -489,7 +488,7 @@ typedef struct _NMDevicePrivate {
 	/* Firewall */
 	FirewallState fw_state:4;
 	NMFirewallManager *fw_mgr;
-	NMFirewallManagerCallId fw_call;
+	NMFirewallManagerCallId *fw_call;
 
 	/* IPv4LL stuff */
 	sd_ipv4ll *    ipv4ll;
@@ -585,6 +584,7 @@ typedef struct _NMDevicePrivate {
 	} concheck_x[2];
 
 	guint check_delete_unrealized_id;
+	guint32 interface_flags;
 
 	struct {
 		SriovOp *pending;    /* SR-IOV operation currently running */
@@ -633,7 +633,6 @@ static void _carrier_wait_check_queued_act_request (NMDevice *self);
 static gint64 _get_carrier_wait_ms (NMDevice *self);
 
 static const char *_activation_func_to_string (ActivationHandleFunc func);
-static void activation_source_handle_cb (NMDevice *self, int addr_family);
 
 static void _set_state_full (NMDevice *self,
                              NMDeviceState state,
@@ -671,6 +670,10 @@ static void (*const activate_stage4_ip_config_timeout_x[2]) (NMDevice *self) = {
 	activate_stage4_ip_config_timeout_4,
 };
 
+static void sriov_op_cb (GError *error, gpointer user_data);
+
+static void activate_stage2_device_config (NMDevice *self);
+
 static void activate_stage5_ip_config_result_4 (NMDevice *self);
 static void activate_stage5_ip_config_result_6 (NMDevice *self);
 
@@ -1347,6 +1350,7 @@ _get_stable_id (NMDevice *self,
 		       NM_PRINT_FMT_QUOTED (stable_type == NM_UTILS_STABLE_TYPE_GENERATED, " from \"", generated, "\"", ""));
 	}
 
+	nm_assert (priv->current_stable_id);
 	*out_stable_type = priv->current_stable_id_type;
 	return priv->current_stable_id;
 }
@@ -1716,7 +1720,7 @@ _parent_set_ifindex (NMDevice *self,
 	}
 
 	if (parent_ifindex > 0) {
-		parent_device = nm_manager_get_device_by_ifindex (nm_manager_get (), parent_ifindex);
+		parent_device = nm_manager_get_device_by_ifindex (NM_MANAGER_GET, parent_ifindex);
 		if (parent_device == self)
 			parent_device = NULL;
 	} else
@@ -2209,7 +2213,7 @@ nm_device_get_route_metric (NMDevice *self,
 	if (route_metric >= 0)
 		goto out;
 
-	route_metric = nm_manager_device_route_metric_reserve (nm_manager_get (),
+	route_metric = nm_manager_device_route_metric_reserve (NM_MANAGER_GET,
 	                                                       nm_device_get_ip_ifindex (self),
 	                                                       nm_device_get_device_type (self));
 out:
@@ -3021,18 +3025,18 @@ concheck_cb (NMConnectivity *connectivity,
 
 	self_keep_alive = g_object_ref (self);
 
-	_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));
-
 	/* keep @self alive, while we invoke callbacks. */
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	nm_assert (!handle || c_list_contains (&priv->concheck_lst_head, &handle->concheck_lst));
+	nm_assert (handle && c_list_contains (&priv->concheck_lst_head, &handle->concheck_lst));
 
 	seq = handle->seq;
 
+	_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));
+
 	/* find out, if there are any periodic checks pending (either whether they
 	 * were scheduled before or after @handle. */
 	any_periodic_before = FALSE;
@@ -3600,12 +3604,26 @@ nm_device_set_carrier (NMDevice *self, gboolean carrier)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMDeviceState state = nm_device_get_state (self);
+	gboolean notify_flags = FALSE;
 
 	if (priv->carrier == carrier)
 		return;
 
+	if (NM_FLAGS_ALL (priv->capabilities,
+	                    NM_DEVICE_CAP_CARRIER_DETECT
+	                  | NM_DEVICE_CAP_NONSTANDARD_CARRIER)) {
+		if (carrier)
+			priv->interface_flags |= NM_DEVICE_INTERFACE_FLAG_CARRIER;
+		else
+			priv->interface_flags &= ~NM_DEVICE_INTERFACE_FLAG_CARRIER;
+		notify_flags = TRUE;
+	}
+
 	priv->carrier = carrier;
-	_notify (self, PROP_CARRIER);
+	if (notify_flags)
+		nm_gobject_notify_together (self, PROP_CARRIER, PROP_INTERFACE_FLAGS);
+	else
+		_notify (self, PROP_CARRIER);
 
 	if (priv->carrier) {
 		_LOGI (LOGD_DEVICE, "carrier: link connected");
@@ -3675,7 +3693,7 @@ device_recheck_slave_status (NMDevice *self, const NMPlatformLink *plink)
 	if (plink->master <= 0)
 		return;
 
-	master = nm_manager_get_device_by_ifindex (nm_manager_get (), plink->master);
+	master = nm_manager_get_device_by_ifindex (NM_MANAGER_GET, plink->master);
 	plink_master = nm_platform_link_get (nm_device_get_platform (self), plink->master);
 	plink_master_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink_master));
 
@@ -3802,6 +3820,33 @@ ndisc_set_router_config (NMNDisc *ndisc, NMDevice *self)
 	g_array_unref (dns_domains);
 }
 
+static void
+device_update_interface_flags (NMDevice *self, const NMPlatformLink *plink)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceInterfaceFlags flags = NM_DEVICE_INTERFACE_FLAG_NONE;
+
+	if (plink && NM_FLAGS_HAS (plink->n_ifi_flags, IFF_UP))
+		flags |= NM_DEVICE_INTERFACE_FLAG_UP;
+	if (plink && NM_FLAGS_HAS (plink->n_ifi_flags, IFF_LOWER_UP))
+		flags |= NM_DEVICE_INTERFACE_FLAG_LOWER_UP;
+
+	if (NM_FLAGS_ALL (priv->capabilities,
+	                    NM_DEVICE_CAP_CARRIER_DETECT
+	                  | NM_DEVICE_CAP_NONSTANDARD_CARRIER)) {
+		if (priv->carrier)
+			flags |= NM_DEVICE_INTERFACE_FLAG_CARRIER;
+	} else {
+		if (plink && NM_FLAGS_HAS (plink->n_ifi_flags, IFF_LOWER_UP))
+			flags |= NM_DEVICE_INTERFACE_FLAG_CARRIER;
+	}
+
+	if (flags != priv->interface_flags) {
+		priv->interface_flags = flags;
+		_notify (self, PROP_INTERFACE_FLAGS);
+	}
+}
+
 static gboolean
 device_link_changed (NMDevice *self)
 {
@@ -3889,6 +3934,8 @@ device_link_changed (NMDevice *self)
 	    && !nm_device_has_capability (self, NM_DEVICE_CAP_NONSTANDARD_CARRIER))
 		nm_device_set_carrier (self, pllink->connected);
 
+	device_update_interface_flags (self, pllink);
+
 	klass->link_changed (self, pllink);
 
 	/* Update DHCP, etc, if needed */
@@ -4241,9 +4288,11 @@ nm_device_update_from_platform_link (NMDevice *self, const NMPlatformLink *plink
 		_notify (self, PROP_IFINDEX);
 		NM_DEVICE_GET_CLASS (self)->link_changed (self, plink);
 	}
+
+	device_update_interface_flags (self, plink);
 }
 
-static void sriov_op_cb (GError *error, gpointer user_data);
+/*****************************************************************************/
 
 static void
 sriov_op_start (NMDevice *self, SriovOp *op)
@@ -4276,11 +4325,14 @@ sriov_op_cb (GError *error, gpointer user_data)
 
 	priv->sriov.pending = NULL;
 
+	g_clear_object (&op->cancellable);
+
 	if (op->callback)
 		op->callback (error, op->callback_data);
 
-	g_clear_object (&op->cancellable);
-	g_slice_free (SriovOp, op);
+	nm_assert (!priv->sriov.pending);
+
+	nm_g_slice_free (op);
 
 	if (priv->sriov.next) {
 		sriov_op_start (self,
@@ -4289,41 +4341,82 @@ sriov_op_cb (GError *error, gpointer user_data)
 }
 
 static void
-sriov_op_queue (NMDevice *self,
-                guint num_vfs,
-                NMTernary autoprobe,
-                NMPlatformAsyncCallback callback,
-                gpointer callback_data)
+sriov_op_queue_op (NMDevice *self,
+                   SriovOp *op)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	GError *error = NULL;
-	SriovOp *op;
-
-	op = g_slice_new0 (SriovOp);
-	op->num_vfs = num_vfs;
-	op->autoprobe = autoprobe;
-	op->callback = callback;
-	op->callback_data = callback_data;
 
 	if (priv->sriov.next) {
+		SriovOp *op_next = g_steal_pointer (&priv->sriov.next);
+
 		/* Cancel the next operation immediately */
-		if (priv->sriov.next->callback) {
+		if (op_next->callback) {
+			gs_free_error GError *error = NULL;
+
 			nm_utils_error_set_cancelled (&error, FALSE, NULL);
-			priv->sriov.next->callback (error, priv->sriov.next->callback_data);
-			g_clear_error (&error);
+			op_next->callback (error, op_next->callback_data);
 		}
-		g_slice_free (SriovOp, priv->sriov.next);
-		priv->sriov.next = NULL;
-	}
 
-	if (priv->sriov.pending) {
+		nm_g_slice_free (op_next);
+
+		if (!priv->sriov.pending) {
+			/* This (having "next" set but "pending" not) can only happen if we are
+			 * called from inside the callback again.
+			 *
+			 * That means we append the new request as "next" and return. Once
+			 * the callback returns, it will schedule the request. */
+			priv->sriov.next = op;
+			return;
+		}
+	} else if (priv->sriov.pending) {
 		priv->sriov.next = op;
 		g_cancellable_cancel (priv->sriov.pending->cancellable);
-	} else
+		return;
+	}
+
+	if (op)
 		sriov_op_start (self, op);
 }
 
 static void
+sriov_op_queue (NMDevice *self,
+                guint num_vfs,
+                NMTernary autoprobe,
+                NMPlatformAsyncCallback callback,
+                gpointer callback_data)
+{
+	SriovOp *op;
+
+	/* We usually never want to cancel an async write operation, unless it's superseded
+	 * by a newer operation (that resets the state). That is, because we need to ensure
+	 * that we never end up doing two concurrent writes (since we write on a background
+	 * thread, that would be unordered/racy).
+	 * Of course, since we queue requests only per-device, when devices get renamed we
+	 * might end up writing the same sysctl concurrently still. But that's really
+	 * unlikely, and don't rename after udev completes!
+	 *
+	 * The "next" operation is not yet even started. It can be replaced/canceled right away
+	 * when a newer request comes.
+	 * The "pending" operation is currently ongoing, and we may cancel it if
+	 * we have a follow-up operation (queued in "next"). Unless we have a such
+	 * a newer request, we cannot cancel it!
+	 *
+	 * FIXME(shutdown): However, during shutdown we don't have a follow-up write request to cancel
+	 * this operation and we have to give it at least some time to complete. The solution is that
+	 * we register a way to abort the last call during shutdown, and after NM_SHUTDOWN_TIMEOUT_MS
+	 * grace period we pull the plug and cancel it. */
+
+	op = g_slice_new (SriovOp);
+	*op = (SriovOp) {
+		.num_vfs       = num_vfs,
+		.autoprobe     = autoprobe,
+		.callback      = callback,
+		.callback_data = callback_data,
+	};
+	sriov_op_queue_op (self, op);
+}
+
+static void
 device_init_static_sriov_num_vfs (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -4746,42 +4839,24 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 	return TRUE;
 }
 
-/**
- * nm_device_notify_component_added():
- * @self: the #NMDevice
- * @component: the component being added by a plugin
- *
- * Called by the manager to notify the device that a new component has
- * been found.  The device implementation should return %TRUE if it
- * wishes to claim the component, or %FALSE if it cannot.
- *
- * Returns: %TRUE to claim the component, %FALSE if the component cannot be
- * claimed.
- */
-gboolean
-nm_device_notify_component_added (NMDevice *self, GObject *component)
+void
+nm_device_notify_availability_maybe_changed (NMDevice *self)
 {
-	NMDeviceClass *klass;
 	NMDevicePrivate *priv;
 
-	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_if_fail (NM_IS_DEVICE (self));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	klass = NM_DEVICE_GET_CLASS (self);
-
-	if (priv->state == NM_DEVICE_STATE_DISCONNECTED) {
-		/* A device could have stayed disconnected because it would
-		 * want to register with a network server that now become
-		 * available. */
-		nm_device_recheck_available_connections (self);
-		if (g_hash_table_size (priv->available_connections) > 0)
-			nm_device_emit_recheck_auto_activate (self);
-	}
 
-	if (klass->component_added)
-		return klass->component_added (self, component);
+	if (priv->state != NM_DEVICE_STATE_DISCONNECTED)
+		return;
 
-	return FALSE;
+	/* A device could have stayed disconnected because it would
+	 * want to register with a network server that now become
+	 * available. */
+	nm_device_recheck_available_connections (self);
+	if (g_hash_table_size (priv->available_connections) > 0)
+		nm_device_emit_recheck_auto_activate (self);
 }
 
 /**
@@ -5856,9 +5931,10 @@ check_connection_compatible (NMDevice *self, NMConnection *connection, GError **
 		return FALSE;
 	}
 
-	conn_iface = nm_manager_get_connection_iface (nm_manager_get (),
+	conn_iface = nm_manager_get_connection_iface (NM_MANAGER_GET,
 	                                              connection,
 	                                              NULL,
+	                                              NULL,
 	                                              &local);
 
 	/* We always need a interface name for virtual devices, but for
@@ -6103,119 +6179,102 @@ dnsmasq_state_changed_cb (NMDnsMasqManager *manager, guint32 status, gpointer us
 
 /*****************************************************************************/
 
-static gboolean
-activation_source_handle_cb4 (gpointer user_data)
-{
-	activation_source_handle_cb (user_data, AF_INET);
-	return G_SOURCE_REMOVE;
-}
-
-static gboolean
-activation_source_handle_cb6 (gpointer user_data)
-{
-	activation_source_handle_cb (user_data, AF_INET6);
-	return G_SOURCE_REMOVE;
-}
-
-static ActivationHandleData *
-activation_source_get_by_family (NMDevice *self,
-                                 int addr_family,
-                                 GSourceFunc *out_idle_func)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	switch (addr_family) {
-	case AF_INET6:
-		NM_SET_OUT (out_idle_func, activation_source_handle_cb6);
-		return &priv->act_handle6;
-	case AF_INET:
-		NM_SET_OUT (out_idle_func, activation_source_handle_cb4);
-		return &priv->act_handle4;
-	}
-	g_return_val_if_reached (NULL);
-}
-
 static void
 activation_source_clear (NMDevice *self,
                          int addr_family)
 {
-	ActivationHandleData *act_data;
-
-	act_data = activation_source_get_by_family (self, addr_family, NULL);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-	if (act_data->id) {
+	if (priv->activation_source_id_x[IS_IPv4] != 0) {
 		_LOGD (LOGD_DEVICE, "activation-stage: clear %s,v%c (id %u)",
-		       _activation_func_to_string (act_data->func),
+		       _activation_func_to_string (priv->activation_source_func_x[IS_IPv4]),
 		       nm_utils_addr_family_to_char (addr_family),
-		       act_data->id);
-		nm_clear_g_source (&act_data->id);
-		act_data->func = NULL;
+		       priv->activation_source_id_x[IS_IPv4]);
+		nm_clear_g_source (&priv->activation_source_id_x[IS_IPv4]);
+		priv->activation_source_func_x[IS_IPv4] = NULL;
 	}
 }
 
-static void
+static gboolean
 activation_source_handle_cb (NMDevice *self,
                              int addr_family)
 {
-	ActivationHandleData *act_data, a;
+	NMDevicePrivate *priv;
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
+	ActivationHandleFunc activation_source_func;
+	guint activation_source_id;
 
-	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_val_if_fail (NM_IS_DEVICE (self), G_SOURCE_REMOVE);
 
-	act_data = activation_source_get_by_family (self, addr_family, NULL);
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	g_return_if_fail (act_data->id);
-	g_return_if_fail (act_data->func);
+	activation_source_func = priv->activation_source_func_x[IS_IPv4];
+	activation_source_id = priv->activation_source_id_x[IS_IPv4];
 
-	a = *act_data;
+	g_return_val_if_fail (activation_source_id != 0, G_SOURCE_REMOVE);
+	nm_assert (activation_source_func);
 
-	act_data->func = NULL;
-	act_data->id = 0;
+	priv->activation_source_func_x[IS_IPv4] = NULL;
+	priv->activation_source_id_x[IS_IPv4] = 0;
 
 	_LOGD (LOGD_DEVICE, "activation-stage: invoke %s,v%c (id %u)",
-	       _activation_func_to_string (a.func),
+	       _activation_func_to_string (activation_source_func),
 	       nm_utils_addr_family_to_char (addr_family),
-	       a.id);
+	       activation_source_id);
 
-	a.func (self);
+	activation_source_func (self);
 
-	_LOGD (LOGD_DEVICE, "activation-stage: complete %s,v%c (id %u)",
-	       _activation_func_to_string (a.func),
+	_LOGT (LOGD_DEVICE, "activation-stage: complete %s,v%c (id %u)",
+	       _activation_func_to_string (activation_source_func),
 	       nm_utils_addr_family_to_char (addr_family),
-	       a.id);
+	       activation_source_id);
+
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+activation_source_handle_cb_4 (gpointer user_data)
+{
+	return activation_source_handle_cb (user_data, AF_INET);
+}
+
+static gboolean
+activation_source_handle_cb_6 (gpointer user_data)
+{
+	return activation_source_handle_cb (user_data, AF_INET6);
 }
 
 static void
 activation_source_schedule (NMDevice *self, ActivationHandleFunc func, int addr_family)
 {
-	ActivationHandleData *act_data;
-	GSourceFunc source_func = NULL;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 	guint new_id = 0;
 
-	act_data = activation_source_get_by_family (self, addr_family, &source_func);
-
-	if (act_data->id && act_data->func == func) {
-		/* Don't bother rescheduling the same function that's about to
-		 * run anyway.  Fixes issues with crappy wireless drivers sending
-		 * streams of associate events before NM has had a chance to process
-		 * the first one.
-		 */
-		_LOGD (LOGD_DEVICE, "activation-stage: already scheduled %s,v%c (id %u)",
+	if (   priv->activation_source_id_x[IS_IPv4] != 0
+	    && priv->activation_source_func_x[IS_IPv4] == func) {
+		/* Scheduling the same stage multiple times is fine. */
+		_LOGT (LOGD_DEVICE, "activation-stage: already scheduled %s,v%c (id %u)",
 		       _activation_func_to_string (func),
 		       nm_utils_addr_family_to_char (addr_family),
-		       act_data->id);
+		       priv->activation_source_id_x[IS_IPv4]);
 		return;
 	}
 
-	new_id = g_idle_add (source_func, self);
+	new_id = g_idle_add (  IS_IPv4
+	                     ? activation_source_handle_cb_4
+	                     : activation_source_handle_cb_6,
+	                     self);
 
-	if (act_data->id) {
-		_LOGW (LOGD_DEVICE, "activation-stage: schedule %s,v%c which replaces %s,v%c (id %u -> %u)",
+	if (priv->activation_source_id_x[IS_IPv4] != 0) {
+		_LOGD (LOGD_DEVICE, "activation-stage: schedule %s,v%c which replaces %s,v%c (id %u -> %u)",
 		       _activation_func_to_string (func),
 		       nm_utils_addr_family_to_char (addr_family),
-		       _activation_func_to_string (act_data->func),
+		       _activation_func_to_string (priv->activation_source_func_x[IS_IPv4]),
 		       nm_utils_addr_family_to_char (addr_family),
-		       act_data->id, new_id);
-		nm_clear_g_source (&act_data->id);
+		       priv->activation_source_id_x[IS_IPv4], new_id);
+		nm_clear_g_source (&priv->activation_source_id_x[IS_IPv4]);
 	} else {
 		_LOGD (LOGD_DEVICE, "activation-stage: schedule %s,v%c (id %u)",
 		       _activation_func_to_string (func),
@@ -6223,19 +6282,38 @@ activation_source_schedule (NMDevice *self, ActivationHandleFunc func, int addr_
 		       new_id);
 	}
 
-	act_data->func = func;
-	act_data->id = new_id;
+	priv->activation_source_func_x[IS_IPv4] = func;
+	priv->activation_source_id_x[IS_IPv4] = new_id;
 }
 
-static gboolean
-activation_source_is_scheduled (NMDevice *self,
-                                ActivationHandleFunc func,
-                                int addr_family)
+static void
+activation_source_invoke_sync (NMDevice *self, ActivationHandleFunc func, int addr_family)
 {
-	ActivationHandleData *act_data;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
 
-	act_data = activation_source_get_by_family (self, addr_family, NULL);
-	return act_data->func == func;
+	if (priv->activation_source_id_x[IS_IPv4] == 0) {
+		_LOGD (LOGD_DEVICE, "activation-stage: synchronously invoke %s,v%c",
+		       _activation_func_to_string (func),
+		       nm_utils_addr_family_to_char (addr_family));
+	} else if (priv->activation_source_func_x[IS_IPv4] == func) {
+		_LOGD (LOGD_DEVICE, "activation-stage: synchronously invoke %s,v%c which was already scheduled (id %u)",
+		       _activation_func_to_string (func),
+		       nm_utils_addr_family_to_char (addr_family),
+		       priv->activation_source_id_x[IS_IPv4]);
+	} else {
+		_LOGD (LOGD_DEVICE, "activation-stage: synchronously invoke %s,v%c which replaces %s,v%c (id %u)",
+		       _activation_func_to_string (func),
+		       nm_utils_addr_family_to_char (addr_family),
+		       _activation_func_to_string (priv->activation_source_func_x[IS_IPv4]),
+		       nm_utils_addr_family_to_char (addr_family),
+		       priv->activation_source_id_x[IS_IPv4]);
+	}
+
+	nm_clear_g_source (&priv->activation_source_id_x[IS_IPv4]);
+	priv->activation_source_func_x[IS_IPv4] = NULL;
+
+	func (self);
 }
 
 /*****************************************************************************/
@@ -6248,22 +6326,18 @@ master_ready (NMDevice *self,
 	NMActiveConnection *master_connection;
 	NMDevice *master;
 
-	g_return_if_fail (priv->state == NM_DEVICE_STATE_PREPARE);
-	g_return_if_fail (!priv->master_ready_handled);
-
 	/* Notify a master device that it has a new slave */
-	g_return_if_fail (nm_active_connection_get_master_ready (active));
-	master_connection = nm_active_connection_get_master (active);
+	nm_assert (nm_active_connection_get_master_ready (active));
 
-	priv->master_ready_handled = TRUE;
-	nm_clear_g_signal_handler (active, &priv->master_ready_id);
+	master_connection = nm_active_connection_get_master (active);
 
 	master = nm_active_connection_get_device (master_connection);
 
 	_LOGD (LOGD_DEVICE, "master connection ready; master device %s",
 	       nm_device_get_iface (master));
 
-	if (priv->master && priv->master != master)
+	if (   priv->master
+	    && priv->master != master)
 		nm_device_master_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
 
 	/* If the master didn't change, add-slave only rechecks whether to assume a connection. */
@@ -6277,8 +6351,12 @@ master_ready_cb (NMActiveConnection *active,
                  GParamSpec *pspec,
                  NMDevice *self)
 {
-	master_ready (self, active);
-	nm_device_activate_schedule_stage2_device_config (self);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	nm_assert (nm_active_connection_get_master_ready (active));
+
+	if (priv->state == NM_DEVICE_STATE_PREPARE)
+		nm_device_activate_schedule_stage1_device_prepare (self);
 }
 
 static void
@@ -6424,64 +6502,9 @@ sriov_params_cb (GError *error, gpointer data)
 		return;
 	}
 
-	nm_device_activate_schedule_stage2_device_config (self);
-}
-
-static NMActStageReturn
-act_stage1_prepare (NMDevice *self, NMDeviceStateReason *out_failure_reason)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMSettingSriov *s_sriov;
-	guint i, num;
+	priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_COMPLETED;
 
-	if (   priv->ifindex > 0
-	    && nm_device_has_capability (self, NM_DEVICE_CAP_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;
-		NMTernary autoprobe;
-		gpointer *data;
-
-		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,
-			                                                         NM_CON_DEFAULT ("sriov.autoprobe-drivers"),
-			                                                         self,
-			                                                         NM_TERNARY_FALSE,
-			                                                         NM_TERNARY_TRUE,
-			                                                         NM_TERNARY_TRUE);
-		}
-
-		num = nm_setting_sriov_get_num_vfs (s_sriov);
-		plat_vfs = g_new0 (NMPlatformVF *, num + 1);
-		for (i = 0; i < num; i++) {
-			vf = nm_setting_sriov_get_vf (s_sriov, i);
-			plat_vfs[i] = sriov_vf_config_to_platform (self, vf, &error);
-			if (!plat_vfs[i]) {
-				_LOGE (LOGD_DEVICE,
-				       "failed to apply SR-IOV VF '%s': %s",
-				       nm_utils_sriov_vf_to_str (vf, FALSE, NULL),
-				       error->message);
-				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
-				return NM_ACT_STAGE_RETURN_FAILURE;
-			}
-		}
-
-		/* When changing the number of VFs the kernel can block
-		 * for very long time in the write to sysfs, especially
-		 * if autoprobe-drivers is enabled. Do it asynchronously
-		 * to avoid blocking the entire NM process.
-		 */
-		data = nm_utils_user_data_pack (self, g_steal_pointer (&plat_vfs));
-		sriov_op_queue (self,
-		                nm_setting_sriov_get_total_vfs (s_sriov),
-		                autoprobe,
-		                sriov_params_cb,
-		                data);
-		return NM_ACT_STAGE_RETURN_POSTPONE;
-	}
-	return NM_ACT_STAGE_RETURN_SUCCESS;
+	nm_device_activate_schedule_stage1_device_prepare (self);
 }
 
 /*
@@ -6495,6 +6518,8 @@ activate_stage1_device_prepare (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
+	NMActiveConnection *active;
+	NMActiveConnection *master;
 
 	priv->v4_route_table_initialized = FALSE;
 	priv->v6_route_table_initialized = FALSE;
@@ -6509,21 +6534,117 @@ activate_stage1_device_prepare (NMDevice *self)
 
 	nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
 
+	if (priv->stage1_sriov_state != NM_DEVICE_STAGE_STATE_COMPLETED) {
+		NMSettingSriov *s_sriov;
+
+		if (nm_device_sys_iface_state_is_external_or_assume (self)) {
+			/* pass */
+		} else if (priv->stage1_sriov_state == NM_DEVICE_STAGE_STATE_PENDING)
+			return;
+		else if (   priv->ifindex > 0
+		         && nm_device_has_capability (self, NM_DEVICE_CAP_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;
+			NMTernary autoprobe;
+			guint i, num;
+
+			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,
+				                                                         NM_CON_DEFAULT ("sriov.autoprobe-drivers"),
+				                                                         self,
+				                                                         NM_TERNARY_FALSE,
+				                                                         NM_TERNARY_TRUE,
+				                                                         NM_TERNARY_TRUE);
+			}
+
+			num = nm_setting_sriov_get_num_vfs (s_sriov);
+			plat_vfs = g_new0 (NMPlatformVF *, num + 1);
+			for (i = 0; i < num; i++) {
+				vf = nm_setting_sriov_get_vf (s_sriov, i);
+				plat_vfs[i] = sriov_vf_config_to_platform (self, vf, &error);
+				if (!plat_vfs[i]) {
+					_LOGE (LOGD_DEVICE,
+					       "failed to apply SR-IOV VF '%s': %s",
+					       nm_utils_sriov_vf_to_str (vf, FALSE, NULL),
+					       error->message);
+					nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
+					return;
+				}
+			}
+
+			/* When changing the number of VFs the kernel can block
+			 * for very long time in the write to sysfs, especially
+			 * if autoprobe-drivers is enabled. Do it asynchronously
+			 * to avoid blocking the entire NM process.
+			 */
+			sriov_op_queue (self,
+			                nm_setting_sriov_get_total_vfs (s_sriov),
+			                autoprobe,
+			                sriov_params_cb,
+			                nm_utils_user_data_pack (self,
+			                                         g_steal_pointer (&plat_vfs)));
+			priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_PENDING;
+			return;
+		}
+		priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_COMPLETED;
+	}
+
 	/* 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;
+		NMDeviceClass *klass = NM_DEVICE_GET_CLASS (self);
+
+		if (klass->act_stage1_prepare_set_hwaddr_ethernet) {
+			if (!nm_device_hw_addr_set_cloned (self,
+			                                   nm_device_get_applied_connection (self),
+			                                   FALSE)) {
+				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+				return;
+			}
+		}
+
+		if (klass->act_stage1_prepare) {
+			NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
+
+			ret = klass->act_stage1_prepare (self, &failure_reason);
+			if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
+				return;
+			}
+			if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
+				return;
+
+			nm_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
+		}
+	}
 
-		ret = NM_DEVICE_GET_CLASS (self)->act_stage1_prepare (self, &failure_reason);
-		if (ret == NM_ACT_STAGE_RETURN_POSTPONE) {
+	active = NM_ACTIVE_CONNECTION (priv->act_request.obj);
+	master = nm_active_connection_get_master (active);
+	if (master) {
+		if (nm_active_connection_get_state (master) >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
+			_LOGD (LOGD_DEVICE, "master connection is deactivating");
+			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED);
 			return;
-		} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
-			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, failure_reason);
+		}
+		/* If the master connection is ready for slaves, attach ourselves */
+		if (!nm_active_connection_get_master_ready (active)) {
+			if (priv->master_ready_id == 0) {
+				_LOGD (LOGD_DEVICE, "waiting for master connection to become ready");
+				priv->master_ready_id = g_signal_connect (active,
+				                                          "notify::" NM_ACTIVE_CONNECTION_INT_MASTER_READY,
+				                                          (GCallback) master_ready_cb,
+				                                          self);
+			}
 			return;
 		}
-		g_return_if_fail (ret == NM_ACT_STAGE_RETURN_SUCCESS);
 	}
+	nm_clear_g_signal_handler (priv->act_request.obj, &priv->master_ready_id);
+	if (master)
+		master_ready (self, active);
 
-	nm_device_activate_schedule_stage2_device_config (self);
+	activation_source_invoke_sync (self, activate_stage2_device_config, AF_INET);
 }
 
 /*
@@ -6914,44 +7035,8 @@ activate_stage2_device_config (NMDevice *self)
 void
 nm_device_activate_schedule_stage2_device_config (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);
-
-	if (!priv->master_ready_handled) {
-		NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request.obj);
-		NMActiveConnection *master;
-
-		master = nm_active_connection_get_master (active);
-
-		if (!master) {
-			g_warn_if_fail (!priv->master_ready_id);
-			priv->master_ready_handled = TRUE;
-		} else {
-			/* If the master connection is ready for slaves, attach ourselves */
-			if (nm_active_connection_get_master_ready (active))
-				master_ready (self, active);
-			else if (nm_active_connection_get_state (master) >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
-				_LOGD (LOGD_DEVICE, "master connection is deactivating");
-				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED);
-			} else {
-				_LOGD (LOGD_DEVICE, "waiting for master connection to become ready");
-
-				if (priv->master_ready_id == 0) {
-					priv->master_ready_id = g_signal_connect (active,
-					                                          "notify::" NM_ACTIVE_CONNECTION_INT_MASTER_READY,
-					                                          (GCallback) master_ready_cb,
-					                                          self);
-				}
-				/* Postpone */
-				return;
-			}
-		}
-	}
-
 	activation_source_schedule (self, activate_stage2_device_config, AF_INET);
 }
 
@@ -7403,8 +7488,6 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 		/* Stop any ongoing DHCP transaction on this device */
 		nm_clear_g_signal_handler (priv->dhcp4.client, &priv->dhcp4.state_sigid);
 
-		nm_device_remove_pending_action (self, NM_PENDING_ACTION_DHCP4, FALSE);
-
 		if (   cleanup_type == CLEANUP_TYPE_DECONFIGURE
 		    || cleanup_type == CLEANUP_TYPE_REMOVED)
 			nm_dhcp_client_stop (priv->dhcp4.client, release);
@@ -7552,7 +7635,7 @@ ip_config_merge_and_apply (NMDevice *self,
 		nm_ip_config_merge (composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0);
 
 	if (priv->ext_ip_config_x[IS_IPv4])
-		nm_ip_config_merge (composite, priv->ext_ip_config_x[IS_IPv4], NM_IP_CONFIG_MERGE_DEFAULT, 0);
+		nm_ip_config_merge (composite, priv->ext_ip_config_x[IS_IPv4], NM_IP_CONFIG_MERGE_EXTERNAL, 0);
 
 	/* Merge WWAN config *last* to ensure modem-given settings overwrite
 	 * any external stuff set by pppd or other scripts.
@@ -7658,8 +7741,6 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 	                           NULL,
 	                           NULL, NULL, NULL);
 
-	nm_device_remove_pending_action (self, NM_PENDING_ACTION_DHCP4, FALSE);
-
 	return TRUE;
 }
 
@@ -7688,7 +7769,13 @@ dhcp4_fail (NMDevice *self, NMDhcpState dhcp_state)
 	       _ip_state_to_string (priv->ip_state_4),
 	       priv->dhcp4.was_active);
 
-	/* Keep client running if there are static addresses configured
+	/* The client is always left running after a failure. */
+
+	/* Nothing to do if we failed before... */
+	if (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL)
+		goto clear_config;
+
+	/* ... and also if there are static addresses configured
 	 * on the interface.
 	 */
 	if (   priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
@@ -7704,14 +7791,12 @@ dhcp4_fail (NMDevice *self, NMDhcpState dhcp_state)
 	 */
 	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_ip_config_timeout (self, AF_INET);
 		return;
 	}
 
 	/* In any other case (expired lease, assumed connection, etc.),
-	 * start a grace period in which we keep the client running,
-	 * hoping that it will regain a lease.
+	 * wait for some time before failing the IP method.
 	 */
 	if (!priv->dhcp4.grace_id) {
 		priv->dhcp4.grace_id = g_timeout_add_seconds (DHCP_GRACE_PERIOD_SEC,
@@ -7869,6 +7954,191 @@ get_dhcp_timeout (NMDevice *self, int addr_family)
 	return timeout ?: NM_DHCP_TIMEOUT_DEFAULT;
 }
 
+/**
+ * dhcp_get_iaid:
+ * @self: the #NMDevice
+ * @addr_family: the address family
+ * @connection: the connection
+ * @out_is_explicit: on return, %TRUE if the user set a valid IAID in
+ *   the connection or in global configuration; %FALSE if the connection
+ *   property was empty and no valid global configuration was provided.
+ *
+ * Returns: a IAID value for this device and the given connection.
+ */
+static guint32
+dhcp_get_iaid (NMDevice *self,
+               int addr_family,
+               NMConnection *connection,
+               gboolean *out_is_explicit)
+{
+	NMSettingIPConfig *s_ip;
+	const char *iaid_str;
+	gs_free char *iaid_str_free = NULL;
+	guint32 iaid;
+	const char *iface;
+	const char *fail_reason;
+	gboolean is_explicit = TRUE;
+
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
+	iaid_str = nm_setting_ip_config_get_dhcp_iaid (s_ip);
+	if (!iaid_str) {
+		iaid_str_free = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+		                                                         addr_family == AF_INET
+		                                                       ? NM_CON_DEFAULT ("ipv4.dhcp-iaid")
+		                                                       : NM_CON_DEFAULT ("ipv6.dhcp-iaid"),
+		                                                       self);
+		iaid_str = iaid_str_free;
+		if (!iaid_str) {
+			iaid_str = NM_IAID_IFNAME;
+			is_explicit = FALSE;
+		} else if (!_nm_utils_iaid_verify (iaid_str, NULL)) {
+			_LOGW (LOGD_DEVICE, "invalid global default '%s' for ipv%c.dhcp-iaid",
+			       iaid_str,
+			       nm_utils_addr_family_to_char (addr_family));
+			iaid_str = NM_IAID_IFNAME;
+			is_explicit = FALSE;
+		}
+	}
+
+	if (nm_streq0 (iaid_str, NM_IAID_MAC)) {
+		const NMPlatformLink *pllink;
+
+		pllink = nm_platform_link_get (nm_device_get_platform (self),
+		                               nm_device_get_ip_ifindex (self));
+		if (!pllink || pllink->l_address.len < 4) {
+			fail_reason = "invalid link-layer address";
+			goto out_fail;
+		}
+
+		/* @iaid is in native endianness. Use unaligned_read_be32()
+		 * so that the IAID for a given MAC address is the same on
+		 * BE and LE machines. */
+		iaid = unaligned_read_be32 (&pllink->l_address.data[pllink->l_address.len - 4]);
+		goto out_good;
+	} else if (nm_streq0 (iaid_str, NM_IAID_PERM_MAC)) {
+		guint8 hwaddr_buf[NM_UTILS_HWADDR_LEN_MAX];
+		const char *hwaddr_str;
+		gsize hwaddr_len;
+
+		hwaddr_str = nm_device_get_permanent_hw_address (self);
+		if (!hwaddr_str) {
+			fail_reason = "no permanent link-layer address";
+			goto out_fail;
+		}
+
+		if (!_nm_utils_hwaddr_aton (hwaddr_str, hwaddr_buf, sizeof (hwaddr_buf), &hwaddr_len))
+			g_return_val_if_reached (0);
+
+		if (hwaddr_len < 4) {
+			fail_reason = "invalid link-layer address";
+			goto out_fail;
+		}
+
+		iaid = unaligned_read_be32 (&hwaddr_buf[hwaddr_len - 4]);
+		goto out_good;
+	}  else if (nm_streq (iaid_str, "stable")) {
+		nm_auto_free_checksum GChecksum *sum = NULL;
+		guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA1];
+		NMUtilsStableType stable_type;
+		const char *stable_id;
+		guint32 salted_header;
+		const guint8 *host_id;
+		gsize host_id_len;
+
+		stable_id = _get_stable_id (self, connection, &stable_type);
+		salted_header = htonl (53390459 + stable_type);
+		nm_utils_host_id_get (&host_id, &host_id_len);
+		iface = nm_device_get_ip_iface (self);
+
+		sum = g_checksum_new (G_CHECKSUM_SHA1);
+		g_checksum_update (sum, (const guchar *) &salted_header, sizeof (salted_header));
+		g_checksum_update (sum, (const guchar *) stable_id, strlen (stable_id) + 1);
+		g_checksum_update (sum, (const guchar *) iface, strlen (iface) + 1);
+		g_checksum_update (sum, (const guchar *) host_id, host_id_len);
+		nm_utils_checksum_get_digest (sum, digest);
+
+		iaid = unaligned_read_be32 (digest);
+		goto out_good;
+	} else if ((iaid = _nm_utils_ascii_str_to_int64 (iaid_str, 10, 0, G_MAXUINT32, -1)) != -1) {
+		goto out_good;
+	} else {
+		iface = nm_device_get_ip_iface (self);
+		iaid = nm_utils_create_dhcp_iaid (TRUE,
+		                                  (const guint8 *) iface,
+		                                  strlen (iface));
+		goto out_good;
+	}
+
+out_fail:
+	nm_assert (fail_reason);
+	_LOGW (  addr_family == AF_INET
+	       ? (LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4)
+	       : (LOGD_DEVICE | LOGD_DHCP6 | LOGD_IP6),
+	       "ipv%c.dhcp-iaid: failure to generate IAID: %s. Using interface-name based IAID",
+	       nm_utils_addr_family_to_char (addr_family), fail_reason);
+	is_explicit = FALSE;
+	iface = nm_device_get_ip_iface (self);
+	iaid = nm_utils_create_dhcp_iaid (TRUE,
+	                                  (const guint8 *) iface,
+	                                  strlen (iface));
+out_good:
+	_LOGD (  addr_family == AF_INET
+	       ? (LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4)
+	       : (LOGD_DEVICE | LOGD_DHCP6 | LOGD_IP6),
+	       "ipv%c.dhcp-iaid: using %u (0x%08x) IAID (str: '%s', explicit %d)",
+	       nm_utils_addr_family_to_char (addr_family), iaid, iaid,
+	       iaid_str, is_explicit);
+	NM_SET_OUT (out_is_explicit, is_explicit);
+	return iaid;
+}
+
+static NMDhcpHostnameFlags
+get_dhcp_hostname_flags (NMDevice *self, int addr_family)
+{
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip;
+	NMDhcpHostnameFlags flags;
+	gs_free_error GError *error = NULL;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_DHCP_HOSTNAME_FLAG_NONE);
+
+	connection = nm_device_get_applied_connection (self);
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
+	g_return_val_if_fail (s_ip, NM_DHCP_HOSTNAME_FLAG_NONE);
+
+	if (!nm_setting_ip_config_get_dhcp_send_hostname (s_ip))
+		return NM_DHCP_HOSTNAME_FLAG_NONE;
+
+	flags = nm_setting_ip_config_get_dhcp_hostname_flags (s_ip);
+	if (flags != NM_DHCP_HOSTNAME_FLAG_NONE)
+		return flags;
+
+	flags = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                     addr_family == AF_INET
+	                                                       ? NM_CON_DEFAULT ("ipv4.dhcp-hostname-flags")
+	                                                       : NM_CON_DEFAULT ("ipv6.dhcp-hostname-flags"),
+	                                                     self,
+	                                                     0, NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS,
+	                                                     0);
+
+	if (!_nm_utils_validate_dhcp_hostname_flags (flags, addr_family, &error)) {
+		_LOGW (LOGD_DEVICE, "invalid global default value 0x%x for ipv%d.%s: %s",
+		       (guint) flags,
+		       addr_family == AF_INET ? 4 : 6,
+		       NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS,
+		       error->message);
+		flags = NM_DHCP_HOSTNAME_FLAG_NONE;
+	}
+
+	if (flags != NM_DHCP_HOSTNAME_FLAG_NONE)
+		return flags;
+
+	if (addr_family == AF_INET)
+		return NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP4;
+	else
+		return NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP6;
+}
+
 static GBytes *
 dhcp4_get_client_id (NMDevice *self,
                      NMConnection *connection,
@@ -7945,8 +8215,9 @@ dhcp4_get_client_id (NMDevice *self,
 	}
 
 	if (nm_streq (client_id, "duid")) {
-		result = nm_utils_dhcp_client_id_systemd_node_specific (TRUE,
-		                                                        nm_device_get_ip_iface (self));
+		guint32 iaid = dhcp_get_iaid (self, AF_INET, connection, NULL);
+
+		result = nm_utils_dhcp_client_id_systemd_node_specific (iaid);
 		goto out_good;
 	}
 
@@ -7960,11 +8231,7 @@ dhcp4_get_client_id (NMDevice *self,
 		gsize host_id_len;
 
 		stable_id = _get_stable_id (self, connection, &stable_type);
-		if (!stable_id)
-			g_return_val_if_reached (NULL);
-
 		salted_header = htonl (2011610591 + stable_type);
-
 		nm_utils_host_id_get (&host_id, &host_id_len);
 
 		sum = g_checksum_new (G_CHECKSUM_SHA1);
@@ -8044,12 +8311,12 @@ dhcp4_start (NMDevice *self)
 	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip4),
 	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip4),
 	                                                nm_setting_ip4_config_get_dhcp_fqdn (NM_SETTING_IP4_CONFIG (s_ip4)),
+	                                                get_dhcp_hostname_flags (self, AF_INET),
 	                                                client_id,
 	                                                get_dhcp_timeout (self, AF_INET),
 	                                                priv->dhcp_anycast_address,
 	                                                NULL,
 	                                                &error);
-
 	if (!priv->dhcp4.client) {
 		_LOGW (LOGD_DHCP4, "failure to start DHCP: %s", error->message);
 		g_clear_error (&error);
@@ -8061,8 +8328,6 @@ dhcp4_start (NMDevice *self)
 	                                            G_CALLBACK (dhcp4_state_changed),
 	                                            self);
 
-	nm_device_add_pending_action (self, NM_PENDING_ACTION_DHCP4, TRUE);
-
 	if (nm_device_sys_iface_state_is_external_or_assume (self))
 		priv->dhcp4.was_active = TRUE;
 
@@ -8267,8 +8532,6 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 		g_clear_object (&priv->dhcp6.client);
 	}
 
-	nm_device_remove_pending_action (self, NM_PENDING_ACTION_DHCP6, FALSE);
-
 	if (priv->dhcp6.config) {
 		nm_dbus_object_clear_and_unexport (&priv->dhcp6.config);
 		_notify (self, PROP_DHCP6_CONFIG);
@@ -8302,8 +8565,6 @@ dhcp6_lease_change (NMDevice *self)
 	                           NULL,
 	                           NULL, NULL, NULL);
 
-	nm_device_remove_pending_action (self, NM_PENDING_ACTION_DHCP6, FALSE);
-
 	return TRUE;
 }
 
@@ -8333,10 +8594,16 @@ dhcp6_fail (NMDevice *self, NMDhcpState dhcp_state)
 	       _ip_state_to_string (priv->ip_state_6),
 	       priv->dhcp6.was_active);
 
+	/* The client is always left running after a failure. */
+
+	/* Nothing to do if we failed before... */
+	if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
+		goto clear_config;
+
 	is_dhcp_managed = (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED);
 
 	if (is_dhcp_managed) {
-		/* Keep client running if there are static addresses configured
+		/* ... and also if there are static addresses configured
 		 * on the interface.
 		 */
 		if (   priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE
@@ -8352,14 +8619,12 @@ dhcp6_fail (NMDevice *self, NMDhcpState dhcp_state)
 		 */
 		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_ip_config_timeout (self, AF_INET6);
 			return;
 		}
 
 		/* In any other case (expired lease, assumed connection, etc.),
-		 * start a grace period in which we keep the client running,
-		 * hoping that it will regain a lease.
+		 * wait for some time before failing the IP method.
 		 */
 		if (!priv->dhcp6.grace_id) {
 			priv->dhcp6.grace_id = g_timeout_add_seconds (DHCP_GRACE_PERIOD_SEC,
@@ -8690,8 +8955,6 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
 		} digest;
 
 		stable_id = _get_stable_id (self, connection, &stable_type);
-		if (!stable_id)
-			g_return_val_if_reached (NULL);
 
 		if (NM_IN_STRSET (duid, "stable-ll", "stable-llt")) {
 			/* for stable LL/LLT DUIDs, we still need a hardware address to detect
@@ -8820,6 +9083,8 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	gboolean enforce_duid = FALSE;
 	const NMPlatformLink *pllink;
 	GError *error = NULL;
+	guint32 iaid;
+	gboolean iaid_explicit;
 
 	const NMPlatformIP6Address *ll_addr = NULL;
 
@@ -8844,6 +9109,8 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 		bcast_hwaddr = nmp_link_address_get_as_bytes (&pllink->l_broadcast);
 	}
 
+	iaid = dhcp_get_iaid (self, AF_INET6, connection, &iaid_explicit);
+
 	duid = dhcp6_get_duid (self, connection, hwaddr, &enforce_duid);
 	priv->dhcp6.client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (),
 	                                                nm_device_get_multi_index (self),
@@ -8857,8 +9124,11 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	                                                nm_device_get_route_metric (self, AF_INET6),
 	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip6),
 	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip6),
+	                                                get_dhcp_hostname_flags (self, AF_INET6),
 	                                                duid,
 	                                                enforce_duid,
+	                                                iaid,
+	                                                iaid_explicit,
 	                                                get_dhcp_timeout (self, AF_INET6),
 	                                                priv->dhcp_anycast_address,
 	                                                (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
@@ -8893,7 +9163,6 @@ dhcp6_start (NMDevice *self, gboolean wait_for_ll)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
-	NMSettingIPConfig *s_ip6;
 
 	nm_dbus_object_clear_and_unexport (&priv->dhcp6.config);
 	priv->dhcp6.config = nm_dhcp6_config_new ();
@@ -8903,11 +9172,7 @@ dhcp6_start (NMDevice *self, gboolean wait_for_ll)
 	g_clear_pointer (&priv->dhcp6.event_id, g_free);
 
 	connection = nm_device_get_applied_connection (self);
-	g_assert (connection);
-	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	if (!nm_setting_ip_config_get_may_fail (s_ip6) ||
-	    !strcmp (nm_setting_ip_config_get_method (s_ip6), NM_SETTING_IP6_CONFIG_METHOD_DHCP))
-		nm_device_add_pending_action (self, NM_PENDING_ACTION_DHCP6, TRUE);
+	g_return_val_if_fail (connection, FALSE);
 
 	if (wait_for_ll) {
 		/* ensure link local is ready... */
@@ -9134,13 +9399,12 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 		const char *stable_id;
 
 		stable_id = _get_stable_id (self, connection, &stable_type);
-		if (   !stable_id
-		    || !nm_utils_ipv6_addr_set_stable_privacy (stable_type,
-		                                               &lladdr,
-		                                               nm_device_get_iface (self),
-		                                               stable_id,
-		                                               priv->linklocal6_dad_counter++,
-		                                               &error)) {
+		if (!nm_utils_ipv6_addr_set_stable_privacy (stable_type,
+		                                            &lladdr,
+		                                            nm_device_get_iface (self),
+		                                            stable_id,
+		                                            priv->linklocal6_dad_counter++,
+		                                            &error)) {
 			_LOGW (LOGD_IP6, "linklocal6: failed to generate an address: %s", error->message);
 			g_clear_error (&error);
 			linklocal6_failed (self);
@@ -9794,7 +10058,6 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 	g_assert (s_ip6);
 
 	stable_id = _get_stable_id (self, connection, &stable_type);
-	g_assert (stable_id);
 	priv->ndisc = nm_lndp_ndisc_new (nm_device_get_platform (self),
 	                                 nm_device_get_ip_ifindex (self),
 	                                 nm_device_get_ip_iface (self),
@@ -9817,9 +10080,6 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 		                 "IPv6 private addresses. This feature is not available");
 	}
 
-	if (!nm_setting_ip_config_get_may_fail (nm_connection_get_setting_ip6_config (connection)))
-		nm_device_add_pending_action (self, NM_PENDING_ACTION_AUTOCONF6, TRUE);
-
 	/* ensure link local is ready... */
 	if (!linklocal6_start (self)) {
 		/* wait for the LL address to show up */
@@ -9840,8 +10100,6 @@ addrconf6_cleanup (NMDevice *self)
 	nm_clear_g_signal_handler (priv->ndisc, &priv->ndisc_changed_id);
 	nm_clear_g_signal_handler (priv->ndisc, &priv->ndisc_timeout_id);
 
-	nm_device_remove_pending_action (self, NM_PENDING_ACTION_AUTOCONF6, FALSE);
-
 	applied_config_clear (&priv->ac_ip6_config);
 	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
 	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
@@ -10002,7 +10260,7 @@ _ip6_privacy_get (NMDevice *self)
 		return ip6_privacy;
 
 	if (!nm_device_get_ip_ifindex (self))
-		return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;;
+		return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
 
 	/* 3.) No valid default-value configured. Fallback to reading sysctl.
 	 *
@@ -10376,7 +10634,7 @@ activate_stage3_ip_config_start (NMDevice *self)
 
 static void
 fw_change_zone_cb (NMFirewallManager *firewall_manager,
-                   NMFirewallManagerCallId call_id,
+                   NMFirewallManagerCallId *call_id,
                    GError *error,
                    gpointer user_data)
 {
@@ -10389,6 +10647,7 @@ fw_change_zone_cb (NMFirewallManager *firewall_manager,
 
 	if (priv->fw_call != call_id)
 		g_return_if_reached ();
+
 	priv->fw_call = NULL;
 
 	if (nm_utils_error_is_cancelled (error, FALSE))
@@ -10668,7 +10927,7 @@ start_sharing (NMDevice *self, NMIP4Config *config, GError **error)
 		 * 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 ()),
+		announce_android_metered = NM_IN_SET (nm_manager_get_metered (NM_MANAGER_GET),
 		                                      NM_METERED_YES,
 		                                      NM_METERED_GUESS_YES);
 		break;
@@ -10841,8 +11100,6 @@ activate_stage5_ip_config_result_4 (NMDevice *self)
 	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, NM_DEVICE_IP_STATE_DONE);
 	check_ip_state (self, FALSE, TRUE);
@@ -11004,8 +11261,6 @@ activate_stage5_ip_config_result_6 (NMDevice *self)
 				return;
 			}
 		}
-		nm_device_remove_pending_action (self, NM_PENDING_ACTION_DHCP6, FALSE);
-		nm_device_remove_pending_action (self, NM_PENDING_ACTION_AUTOCONF6, FALSE);
 
 		/* Start IPv6 forwarding if we need it */
 		method = nm_device_get_effective_ip_config_method (self, AF_INET6);
@@ -11360,7 +11615,8 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 			}
 		}
 
-		if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
+		if (   nm_device_get_ip_ifindex (self) > 0
+		    && !ip_config_merge_and_apply (self, AF_INET, TRUE))
 			_LOGW (LOGD_IP4, "Failed to reapply IPv4 configuration");
 	}
 }
@@ -11433,7 +11689,8 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 			}
 		}
 
-		if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
+		if (   nm_device_get_ip_ifindex (self) > 0
+		    && !ip_config_merge_and_apply (self, AF_INET6, TRUE))
 			_LOGW (LOGD_IP4, "Failed to reapply IPv6 configuration");
 	}
 }
@@ -11492,10 +11749,19 @@ can_reapply_change (NMDevice *self,
 		                                          NM_SETTING_CONNECTION_MDNS,
 		                                          NM_SETTING_CONNECTION_LLMNR);
 	} else if (NM_IN_STRSET (setting_name,
+	                         NM_SETTING_USER_SETTING_NAME,
 	                         NM_SETTING_PROXY_SETTING_NAME,
 	                         NM_SETTING_IP4_CONFIG_SETTING_NAME,
 	                         NM_SETTING_IP6_CONFIG_SETTING_NAME)) {
 		return TRUE;
+	} else if (   nm_streq (setting_name, NM_SETTING_WIRED_SETTING_NAME)
+	           && NM_IN_SET (NM_DEVICE_GET_CLASS (self)->get_configured_mtu,
+	                         nm_device_get_configured_mtu_wired_parent,
+	                         nm_device_get_configured_mtu_for_wired)) {
+		return nm_device_hash_check_invalid_keys (diffs,
+		                                          NM_SETTING_WIRED_SETTING_NAME,
+		                                          error,
+		                                          NM_SETTING_WIRED_MTU);
 	} else {
 		g_set_error (error,
 		             NM_DEVICE_ERROR,
@@ -11541,7 +11807,8 @@ check_and_reapply_connection (NMDevice *self,
 	NMSettingIPConfig *s_ip6_old, *s_ip6_new;
 	GHashTableIter iter;
 
-	if (priv->state != NM_DEVICE_STATE_ACTIVATED) {
+	if (   priv->state < NM_DEVICE_STATE_PREPARE
+	    || priv->state > NM_DEVICE_STATE_ACTIVATED) {
 		g_set_error_literal (error,
 		                     NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_NOT_ACTIVE,
@@ -11654,24 +11921,31 @@ check_and_reapply_connection (NMDevice *self,
 	 *************************************************************************/
 	klass->reapply_connection (self, con_old, con_new);
 
-	nm_device_update_firewall_zone (self);
-	nm_device_update_metered (self);
-	lldp_init (self, FALSE);
+	if (priv->state >= NM_DEVICE_STATE_CONFIG)
+		lldp_init (self, FALSE);
 
-	s_ip4_old = nm_connection_get_setting_ip4_config (con_old);
-	s_ip4_new = nm_connection_get_setting_ip4_config (con_new);
-	s_ip6_old = nm_connection_get_setting_ip6_config (con_old);
-	s_ip6_new = nm_connection_get_setting_ip6_config (con_new);
+	if (priv->state >= NM_DEVICE_STATE_IP_CONFIG) {
+		s_ip4_old = nm_connection_get_setting_ip4_config (con_old);
+		s_ip4_new = nm_connection_get_setting_ip4_config (con_new);
+		s_ip6_old = nm_connection_get_setting_ip6_config (con_old);
+		s_ip6_new = nm_connection_get_setting_ip6_config (con_new);
 
-	/* Allow reapply of MTU */
-	priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
+		/* Allow reapply of MTU */
+		priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
 
-	nm_device_reactivate_ip4_config (self, s_ip4_old, s_ip4_new);
-	nm_device_reactivate_ip6_config (self, s_ip6_old, s_ip6_new);
+		nm_device_reactivate_ip4_config (self, s_ip4_old, s_ip4_new);
+		nm_device_reactivate_ip6_config (self, s_ip6_old, s_ip6_new);
 
-	_routing_rules_sync (self, NM_TERNARY_TRUE);
+		_routing_rules_sync (self, NM_TERNARY_TRUE);
 
-	reactivate_proxy_config (self);
+		reactivate_proxy_config (self);
+	}
+
+	if (priv->state >= NM_DEVICE_STATE_IP_CHECK)
+		nm_device_update_firewall_zone (self);
+
+	if (priv->state >= NM_DEVICE_STATE_ACTIVATED)
+		nm_device_update_metered (self);
 
 	return TRUE;
 }
@@ -11768,7 +12042,8 @@ impl_device_reapply (NMDBusObject *obj,
 		return;
 	}
 
-	if (priv->state != NM_DEVICE_STATE_ACTIVATED) {
+	if (   priv->state < NM_DEVICE_STATE_PREPARE
+	    || priv->state > NM_DEVICE_STATE_ACTIVATED) {
 		error = g_error_new_literal (NM_DEVICE_ERROR,
 		                             NM_DEVICE_ERROR_NOT_ACTIVE,
 		                             "Device is not activated");
@@ -12353,7 +12628,7 @@ nm_device_is_activating (NMDevice *self)
 	 * handler is actually run.  If there's an activation handler scheduled
 	 * we're activating anyway.
 	 */
-	return priv->act_handle4.id ? TRUE : FALSE;
+	return priv->activation_source_id_4 != 0;
 }
 
 NMProxyConfig *
@@ -13335,9 +13610,8 @@ queued_ip_config_change (NMDevice *self, int addr_family)
 	 * it changing IP configurations before they are applied. Postpone the
 	 * update in such case.
 	 */
-	if (activation_source_is_scheduled (self,
-	                                    activate_stage5_ip_config_result_x[IS_IPv4],
-	                                    addr_family))
+	if (   priv->activation_source_id_x[IS_IPv4] != 0
+	    && priv->activation_source_func_x[IS_IPv4] == activate_stage5_ip_config_result_x[IS_IPv4])
 		return G_SOURCE_CONTINUE;
 
 	priv->queued_ip_config_id_x[IS_IPv4] = 0;
@@ -14115,6 +14389,11 @@ nm_device_update_metered (NMDevice *self)
 		}
 	}
 
+	if (   value == NM_METERED_INVALID
+	    && NM_DEVICE_GET_CLASS (self)->get_guessed_metered
+	    && NM_DEVICE_GET_CLASS (self)->get_guessed_metered (self))
+		value = NM_METERED_GUESS_YES;
+
 	/* Try to guess a value using the metered flag in IP configuration */
 	if (value == NM_METERED_INVALID) {
 		if (   priv->ip_config_4
@@ -14676,8 +14955,10 @@ _cleanup_generic_pre (NMDevice *self, CleanupType cleanup_type)
 
 	_cancel_activation (self);
 
+	priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_INIT;
+
 	if (cleanup_type != CLEANUP_TYPE_KEEP) {
-		nm_manager_device_route_metric_clear (nm_manager_get (),
+		nm_manager_device_route_metric_clear (NM_MANAGER_GET,
 		                                      nm_device_get_ip_ifindex (self));
 	}
 
@@ -14754,10 +15035,7 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 
 	if (priv->act_request.obj) {
 		nm_active_connection_set_default (NM_ACTIVE_CONNECTION (priv->act_request.obj), AF_INET, FALSE);
-
-		priv->master_ready_handled = FALSE;
 		nm_clear_g_signal_handler (priv->act_request.obj, &priv->master_ready_id);
-
 		act_request_set (self, NULL);
 	}
 
@@ -14949,7 +15227,7 @@ nm_device_spawn_iface_helper (NMDevice *self)
 	g_ptr_array_add (argv, g_strdup (nm_connection_get_uuid (connection)));
 
 	stable_id = _get_stable_id (self, connection, &stable_type);
-	if (stable_id && stable_type != NM_UTILS_STABLE_TYPE_UUID) {
+	if (stable_type != NM_UTILS_STABLE_TYPE_UUID) {
 		g_ptr_array_add (argv, g_strdup ("--stable-id"));
 		g_ptr_array_add (argv, g_strdup_printf ("%d %s", (int) stable_type, stable_id));
 	}
@@ -15112,9 +15390,9 @@ deactivate_ready (NMDevice *self, NMDeviceStateReason reason)
 	if (priv->dispatcher.call_id)
 		return;
 
-	if (priv->sriov.pending)
+	if (   priv->sriov.pending
+	    || priv->sriov.next)
 		return;
-	nm_assert (!priv->sriov.next);
 
 	nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
 }
@@ -15208,6 +15486,13 @@ _set_state_full (NMDevice *self,
 
 	old_state = priv->state;
 
+	if (   state == NM_DEVICE_STATE_FAILED
+	    && nm_device_sys_iface_state_is_external_or_assume (self)) {
+		/* Avoid tearing down assumed connection, assume it's connected */
+		state = NM_DEVICE_STATE_ACTIVATED;
+		reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED;
+	}
+
 	/* Do nothing if state isn't changing, but as a special case allow
 	 * re-setting UNAVAILABLE if the device is missing firmware so that we
 	 * can retry device initialization.
@@ -15459,14 +15744,6 @@ _set_state_full (NMDevice *self,
 		 */
 		_cancel_activation (self);
 
-		if (nm_device_sys_iface_state_is_external_or_assume (self)) {
-			/* Avoid tearing down assumed connection, assume it's connected */
-			nm_device_queue_state (self,
-			                       NM_DEVICE_STATE_ACTIVATED,
-			                       NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
-			break;
-		}
-
 		sett_conn = nm_device_get_settings_connection (self);
 		_LOGW (LOGD_DEVICE | LOGD_WIFI,
 		       "Activation: failed for connection '%s'",
@@ -16237,12 +16514,10 @@ _hw_addr_get_cloned (NMDevice *self, NMConnection *connection, gboolean is_wifi,
 		}
 
 		stable_id = _get_stable_id (self, connection, &stable_type);
-		if (stable_id) {
-			hw_addr_generated = nm_utils_hw_addr_gen_stable_eth (stable_type, stable_id,
-			                                                     nm_device_get_ip_iface (self),
-			                                                     nm_device_get_initial_hw_address (self),
-			                                                     _get_generate_mac_address_mask_setting (self, connection, is_wifi, &generate_mac_address_mask_tmp));
-		}
+		hw_addr_generated = nm_utils_hw_addr_gen_stable_eth (stable_type, stable_id,
+		                                                     nm_device_get_ip_iface (self),
+		                                                     nm_device_get_initial_hw_address (self),
+		                                                     _get_generate_mac_address_mask_setting (self, connection, is_wifi, &generate_mac_address_mask_tmp));
 		if (!hw_addr_generated) {
 			g_set_error (error,
 			             NM_DEVICE_ERROR,
@@ -16698,14 +16973,15 @@ dispose (GObject *object)
 
 	_cleanup_generic_pre (self, CLEANUP_TYPE_KEEP);
 
-	g_warn_if_fail (c_list_is_empty (&priv->slaves));
-	g_assert (priv->master_ready_id == 0);
+	nm_assert (c_list_is_empty (&priv->slaves));
 
 	/* Let the kernel manage IPv6LL again */
 	set_nm_ipv6ll (self, FALSE);
 
 	_cleanup_generic_post (self, CLEANUP_TYPE_KEEP);
 
+	nm_assert (priv->master_ready_id == 0);
+
 	g_hash_table_remove_all (priv->ip6_saved_properties);
 
 	nm_clear_g_source (&priv->recheck_assume_id);
@@ -17093,6 +17369,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_IP6_CONNECTIVITY:
 		g_value_set_uint (value, priv->concheck_x[0].state);
 		break;
+	case PROP_INTERFACE_FLAGS:
+		g_value_set_uint (value, priv->interface_flags);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -17181,6 +17460,7 @@ static const NMDBusInterfaceInfoExtended interface_info_device = {
 			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),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE       ("InterfaceFlags",       "u",      NM_DEVICE_INTERFACE_FLAGS),
 		),
 	),
 };
@@ -17221,7 +17501,6 @@ nm_device_class_init (NMDeviceClass *klass)
 	klass->link_changed = link_changed;
 
 	klass->is_available = is_available;
-	klass->act_stage1_prepare = act_stage1_prepare;
 	klass->act_stage2_config = act_stage2_config;
 	klass->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	klass->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
@@ -17409,14 +17688,6 @@ nm_device_class_init (NMDeviceClass *klass)
 	                          FALSE,
 	                          G_PARAM_READABLE |
 	                          G_PARAM_STATIC_STRINGS);
-
-	/**
-	 * NMDevice:metered:
-	 *
-	 * Whether the connection is metered.
-	 *
-	 * Since: 1.2
-	 **/
 	obj_properties[PROP_METERED] =
 	    g_param_spec_uint (NM_DEVICE_METERED, "", "",
 	                       0, G_MAXUINT32, NM_METERED_UNKNOWN,
@@ -17465,6 +17736,13 @@ nm_device_class_init (NMDeviceClass *klass)
 	                        NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
 	                        G_PARAM_READABLE |
 	                        G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_INTERFACE_FLAGS] =
+	    g_param_spec_uint (NM_DEVICE_INTERFACE_FLAGS, "", "",
+	                       0,
+	                       G_MAXUINT32,
+	                       0,
+	                       G_PARAM_READABLE |
+	                       G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 412a57a0..518c66ca 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2005 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
@@ -67,9 +53,6 @@ nm_device_state_reason_check (NMDeviceStateReason reason)
 }
 
 #define NM_PENDING_ACTION_AUTOACTIVATE              "autoactivate"
-#define NM_PENDING_ACTION_DHCP4                     "dhcp4"
-#define NM_PENDING_ACTION_DHCP6                     "dhcp6"
-#define NM_PENDING_ACTION_AUTOCONF6                 "autoconf6"
 #define NM_PENDING_ACTION_RECHECK_AVAILABLE         "recheck-available"
 #define NM_PENDING_ACTION_CARRIER_WAIT              "carrier-wait"
 #define NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT    "waiting-for-supplicant"
@@ -149,6 +132,7 @@ nm_device_state_reason_check (NMDeviceStateReason reason)
 
 #define NM_DEVICE_IP4_CONNECTIVITY           "ip4-connectivity"
 #define NM_DEVICE_IP6_CONNECTIVITY           "ip6-connectivity"
+#define NM_DEVICE_INTERFACE_FLAGS            "interface-flags"
 
 #define NM_TYPE_DEVICE            (nm_device_get_type ())
 #define NM_DEVICE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE, NMDevice))
@@ -435,23 +419,6 @@ typedef struct _NMDeviceClass {
 	                                           int new_ifindex,
 	                                           NMDevice *new_parent);
 
-	/**
-	 * component_added:
-	 * @self: the #NMDevice
-	 * @component: the component (device, modem, etc) which was added
-	 *
-	 * Notifies @self that a new component that a device might be interested
-	 * in was detected by some device factory. It may include an object of
-	 * %GObject subclass to help the devices decide whether it claims that
-	 * particular object itself and the emitting factory should not.
-	 *
-	 * Returns: %TRUE if the component was claimed exclusively and no further
-	 * devices should be notified of the new component.  %FALSE to indicate
-	 * that the component was not exclusively claimed and other devices should
-	 * be notified.
-	 */
-	gboolean        (* component_added) (NMDevice *self, GObject *component);
-
 	gboolean        (* owns_iface) (NMDevice *self, const char *iface);
 
 	NMConnection *  (* new_default_connection) (NMDevice *self);
@@ -472,10 +439,15 @@ typedef struct _NMDeviceClass {
 	guint32         (* get_dhcp_timeout) (NMDevice *self,
 	                                      int addr_family);
 
+	gboolean        (* get_guessed_metered) (NMDevice *self);
+
 	/* 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;
+
+	bool act_stage1_prepare_set_hwaddr_ethernet:1;
+
 } NMDeviceClass;
 
 typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device,
@@ -825,7 +797,7 @@ gboolean   nm_device_check_connection_available (NMDevice *device,
                                                  const char *specific_object,
                                                  GError **error);
 
-gboolean nm_device_notify_component_added (NMDevice *device, GObject *component);
+void nm_device_notify_availability_maybe_changed (NMDevice *self);
 
 gboolean nm_device_owns_iface (NMDevice *device, const char *iface);
 
@@ -885,12 +857,22 @@ void nm_device_check_connectivity_cancel (NMDeviceConnectivityHandle *handle);
 NMConnectivityState nm_device_get_connectivity_state (NMDevice *self, int addr_family);
 
 typedef struct _NMBtVTableNetworkServer NMBtVTableNetworkServer;
+
+typedef void (*NMBtVTableRegisterCallback) (GError *error,
+                                            gpointer user_data);
+
 struct _NMBtVTableNetworkServer {
 	gboolean (*is_available) (const NMBtVTableNetworkServer *vtable,
-	                          const char *addr);
+	                          const char *addr,
+	                          NMDevice *device_accept_busy);
+
 	gboolean (*register_bridge) (const NMBtVTableNetworkServer *vtable,
 	                             const char *addr,
-	                             NMDevice *device);
+	                             NMDevice *device,
+	                             GCancellable *cancellable,
+	                             NMBtVTableRegisterCallback callback,
+	                             gpointer callback_user_data,
+	                             GError **error);
 	gboolean (*unregister_bridge) (const NMBtVTableNetworkServer *vtable,
 	                               NMDevice *device);
 };
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index 469ccce1..4c9e7705 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
diff --git a/src/devices/nm-lldp-listener.h b/src/devices/nm-lldp-listener.h
index c44ca59f..92e1d00f 100644
--- a/src/devices/nm-lldp-listener.h
+++ b/src/devices/nm-lldp-listener.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
diff --git a/src/devices/ovs/meson.build b/src/devices/ovs/meson.build
index 834b27b0..27e1b4d0 100644
--- a/src/devices/ovs/meson.build
+++ b/src/devices/ovs/meson.build
@@ -7,14 +7,15 @@ sources = files(
 )
 
 deps = [
+  daemon_nm_default_dep,
   jansson_dep,
-  nm_dep,
 ]
 
 libnm_device_plugin_ovs = shared_module(
   'nm-device-plugin-ovs',
   sources: sources,
   dependencies: deps,
+  c_args: daemon_c_flags,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
@@ -28,10 +29,3 @@ test(
   check_exports,
   args: [libnm_device_plugin_ovs.full_path(), linker_script_devices],
 )
-
-# FIXME: check_so_symbols replacement
-'''
-check-local-devices-ovs: src/devices/ovs/libnm-device-plugin-ovs.la
-  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/ovs/.libs/libnm-device-plugin-ovs.so "$(srcdir)/linker-script-devices.ver"
-  $(call check_so_symbols,$(builddir)/src/devices/ovs/.libs/libnm-device-plugin-ovs.so)
-'''
diff --git a/src/devices/ovs/nm-device-ovs-bridge.c b/src/devices/ovs/nm-device-ovs-bridge.c
index be707e7a..59096803 100644
--- a/src/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/devices/ovs/nm-device-ovs-bridge.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2017 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
diff --git a/src/devices/ovs/nm-device-ovs-bridge.h b/src/devices/ovs/nm-device-ovs-bridge.h
index 631b4754..07a1fee7 100644
--- a/src/devices/ovs/nm-device-ovs-bridge.h
+++ b/src/devices/ovs/nm-device-ovs-bridge.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2017 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index 83de3c3d..726e9901 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2017 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -112,7 +98,9 @@ link_changed (NMDevice *device,
 {
 	NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE (device);
 
-	if (pllink && priv->waiting_for_interface) {
+	if (   pllink
+	    && priv->waiting_for_interface
+	    && nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
 		priv->waiting_for_interface = FALSE;
 		nm_device_bring_up (device, TRUE, NULL);
 		nm_device_activate_schedule_stage3_ip_config_start (device);
@@ -156,6 +144,15 @@ can_unmanaged_external_down (NMDevice *self)
 	return FALSE;
 }
 
+static void
+deactivate (NMDevice *device)
+{
+	NMDeviceOvsInterface *self = NM_DEVICE_OVS_INTERFACE (device);
+	NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE (self);
+
+	priv->waiting_for_interface = FALSE;
+}
+
 /*****************************************************************************/
 
 static void
@@ -185,6 +182,7 @@ nm_device_ovs_interface_class_init (NMDeviceOvsInterfaceClass *klass)
 	device_class->connection_type_check_compatible = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_OPENVSWITCH);
 
+	device_class->deactivate = deactivate;
 	device_class->get_type_description = get_type_description;
 	device_class->create_and_realize = create_and_realize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
diff --git a/src/devices/ovs/nm-device-ovs-interface.h b/src/devices/ovs/nm-device-ovs-interface.h
index a748e206..e31dac8b 100644
--- a/src/devices/ovs/nm-device-ovs-interface.h
+++ b/src/devices/ovs/nm-device-ovs-interface.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2017 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__
diff --git a/src/devices/ovs/nm-device-ovs-port.c b/src/devices/ovs/nm-device-ovs-port.c
index 39e64b3f..0955e8a9 100644
--- a/src/devices/ovs/nm-device-ovs-port.c
+++ b/src/devices/ovs/nm-device-ovs-port.c
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2017 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
diff --git a/src/devices/ovs/nm-device-ovs-port.h b/src/devices/ovs/nm-device-ovs-port.h
index 5ccf1ec1..7edbf02f 100644
--- a/src/devices/ovs/nm-device-ovs-port.h
+++ b/src/devices/ovs/nm-device-ovs-port.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2017 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_OVS_PORT_H__
diff --git a/src/devices/ovs/nm-ovs-factory.c b/src/devices/ovs/nm-ovs-factory.c
index fdf07bd3..d6dd13eb 100644
--- a/src/devices/ovs/nm-ovs-factory.c
+++ b/src/devices/ovs/nm-ovs-factory.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
@@ -83,7 +69,7 @@ new_device_from_type (const char *name, NMDeviceType device_type)
 	const char *type_desc;
 	NMLinkType link_type = NM_LINK_TYPE_NONE;
 
-	if (nm_manager_get_device (nm_manager_get (), name, device_type))
+	if (nm_manager_get_device (NM_MANAGER_GET, name, device_type))
 		return NULL;
 
 	if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE) {
@@ -130,7 +116,7 @@ ovsdb_device_removed (NMOvsdb *ovsdb, const char *name, NMDeviceType device_type
 	NMDevice *device;
 	NMDeviceState device_state;
 
-	device = nm_manager_get_device (nm_manager_get (), name, device_type);
+	device = nm_manager_get_device (NM_MANAGER_GET, name, device_type);
 	if (!device)
 		return;
 
@@ -158,7 +144,7 @@ ovsdb_interface_failed (NMOvsdb *ovsdb,
 
 	_LOGI (name, connection_uuid, "ovs interface \"%s\" (%s) failed: %s", name, connection_uuid, error);
 
-	device = nm_manager_get_device (nm_manager_get (), name, NM_DEVICE_TYPE_OVS_INTERFACE);
+	device = nm_manager_get_device (NM_MANAGER_GET, name, NM_DEVICE_TYPE_OVS_INTERFACE);
 	if (!device)
 		return;
 
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index c0e68db3..ec4f5c74 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2017 Red Hat, Inc.
  */
 
diff --git a/src/devices/ovs/nm-ovsdb.h b/src/devices/ovs/nm-ovsdb.h
index e7f9d719..59f46206 100644
--- a/src/devices/ovs/nm-ovsdb.h
+++ b/src/devices/ovs/nm-ovsdb.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2017 Red Hat, Inc.
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_OVSDB_H__
diff --git a/src/devices/team/meson.build b/src/devices/team/meson.build
index 3f755012..0e63183b 100644
--- a/src/devices/team/meson.build
+++ b/src/devices/team/meson.build
@@ -4,15 +4,16 @@ sources = files(
 )
 
 deps = [
+  daemon_nm_default_dep,
   jansson_dep,
   libteamdctl_dep,
-  nm_dep,
 ]
 
 libnm_device_plugin_team = shared_module(
   'nm-device-plugin-team',
   sources: sources,
   dependencies: deps,
+  c_args: daemon_c_flags,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
@@ -26,10 +27,3 @@ test(
   check_exports,
   args: [libnm_device_plugin_team.full_path(), linker_script_devices],
 )
-
-# FIXME: check_so_symbols replacement
-'''
-check-local-devices-team: src/devices/team/libnm-device-plugin-team.la
-  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/team/.libs/libnm-device-plugin-team.so "$(srcdir)/linker-script-devices.ver"
-  $(call check_so_symbols,$(builddir)/src/devices/team/.libs/libnm-device-plugin-team.so)
-'''
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index 4661a840..fb9c9c69 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2013 Jiri Pirko <jiri@resnulli.us>
  * Copyright (C) 2018 Red Hat, Inc.
  */
@@ -49,14 +35,14 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceTeam,
 
 typedef struct {
 	struct teamdctl *tdc;
+	char *config;
 	GPid teamd_pid;
 	guint teamd_process_watch;
 	guint teamd_timeout;
 	guint teamd_read_timeout;
 	guint teamd_dbus_watch;
-	char *config;
-	gboolean kill_in_progress;
-	NMConnection *connection;
+	bool kill_in_progress:1;
+	NMDeviceStageState stage1_state:3;
 } NMDeviceTeamPrivate;
 
 struct _NMDeviceTeam {
@@ -70,11 +56,11 @@ struct _NMDeviceTeamClass {
 
 G_DEFINE_TYPE (NMDeviceTeam, nm_device_team, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_TEAM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceTeam, NM_IS_DEVICE_TEAM)
+#define NM_DEVICE_TEAM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceTeam, NM_IS_DEVICE_TEAM, NMDevice)
 
 /*****************************************************************************/
 
-static gboolean teamd_start (NMDevice *device, NMConnection *connection);
+static gboolean teamd_start (NMDeviceTeam *self);
 
 /*****************************************************************************/
 
@@ -141,9 +127,8 @@ _get_config (NMDeviceTeam *self)
 }
 
 static gboolean
-teamd_read_config (NMDevice *device)
+teamd_read_config (NMDeviceTeam *self)
 {
-	NMDeviceTeam *self = NM_DEVICE_TEAM (device);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
 	const char *config = NULL;
 	int err;
@@ -170,11 +155,11 @@ teamd_read_config (NMDevice *device)
 static gboolean
 teamd_read_timeout_cb (gpointer user_data)
 {
-	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE ((NMDeviceTeam *) user_data);
+	NMDeviceTeam *self = user_data;
+	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
 
-	teamd_read_config ((NMDevice *) user_data);
 	priv->teamd_read_timeout = 0;
-
+	teamd_read_config (self);
 	return G_SOURCE_REMOVE;
 }
 
@@ -192,8 +177,9 @@ update_connection (NMDevice *device, NMConnection *connection)
 	}
 
 	/* Read the configuration only if not already set */
-	if (!priv->config && ensure_teamd_connection (device))
-		teamd_read_config (device);
+	if (   !priv->config
+	    && ensure_teamd_connection (device))
+		teamd_read_config (self);
 
 	/* Restore previous tdc state */
 	if (priv->tdc && !tdc) {
@@ -273,31 +259,32 @@ master_update_slave_connection (NMDevice *self,
 }
 
 /*****************************************************************************/
+
 static void
 teamd_kill_cb (pid_t pid, gboolean success, int child_status, void *user_data)
 {
-	NMDevice *device = NM_DEVICE (user_data);
-	NMDeviceTeam *self = (NMDeviceTeam *) device;
+	gs_unref_object NMDeviceTeam *self = user_data;
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
 
 	priv->kill_in_progress = FALSE;
 
-	if (priv->connection) {
-		_LOGT (LOGD_TEAM, "kill terminated, starting teamd...");
-		if (!teamd_start (device, priv->connection)) {
-			nm_device_state_changed (device,
-			                         NM_DEVICE_STATE_FAILED,
-			                         NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
-		}
-		g_clear_object (&priv->connection);
+	if (nm_device_get_state (NM_DEVICE (self)) != NM_DEVICE_STATE_PREPARE) {
+		_LOGT (LOGD_TEAM, "kill terminated");
+		return;
+	}
+
+	_LOGT (LOGD_TEAM, "kill terminated, starting teamd...");
+	if (!teamd_start (self)) {
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
 	}
-	g_object_unref (device);
 }
 
 static void
-teamd_cleanup (NMDevice *device, gboolean free_tdc)
+teamd_cleanup (NMDeviceTeam *self, gboolean free_tdc)
 {
-	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE ((NMDeviceTeam *) device);
+	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
 
 	nm_clear_g_source (&priv->teamd_process_watch);
 	nm_clear_g_source (&priv->teamd_timeout);
@@ -305,15 +292,18 @@ teamd_cleanup (NMDevice *device, gboolean free_tdc)
 
 	if (priv->teamd_pid > 0) {
 		priv->kill_in_progress = TRUE;
-		nm_utils_kill_child_async (priv->teamd_pid, SIGTERM,
-		                           LOGD_TEAM, "teamd",
+		nm_utils_kill_child_async (priv->teamd_pid,
+		                           SIGTERM,
+		                           LOGD_TEAM,
+		                           "teamd",
 		                           2000,
 		                           teamd_kill_cb,
-		                           g_object_ref (device));
+		                           g_object_ref (self));
 		priv->teamd_pid = 0;
 	}
 
-	if (priv->tdc && free_tdc) {
+	if (   priv->tdc
+	    && free_tdc) {
 		teamdctl_disconnect (priv->tdc);
 		teamdctl_free (priv->tdc);
 		priv->tdc = NULL;
@@ -333,7 +323,7 @@ teamd_timeout_cb (gpointer user_data)
 	if (priv->teamd_pid && !priv->tdc) {
 		/* Timed out launching our own teamd process */
 		_LOGW (LOGD_TEAM, "teamd timed out");
-		teamd_cleanup (device, TRUE);
+		teamd_cleanup (self, TRUE);
 
 		g_warn_if_fail (nm_device_is_activating (device));
 		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
@@ -341,7 +331,7 @@ teamd_timeout_cb (gpointer user_data)
 		/* Read again the configuration after the timeout since it might
 		 * have changed.
 		 */
-		if (!teamd_read_config (device)) {
+		if (!teamd_read_config (self)) {
 			_LOGW (LOGD_TEAM, "failed to read teamd configuration");
 			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
 		}
@@ -388,7 +378,7 @@ teamd_dbus_appeared (GDBusConnection *connection,
 		if (ret) {
 			g_variant_get (ret, "(u)", &pid);
 			if (pid != priv->teamd_pid)
-				teamd_cleanup (device, FALSE);
+				teamd_cleanup (self, FALSE);
 		} else {
 			_LOGW (LOGD_TEAM, "failed to determine D-Bus name owner");
 			/* If we can't determine the bus name owner, don't kill our
@@ -403,16 +393,21 @@ teamd_dbus_appeared (GDBusConnection *connection,
 	 * device activation.
 	 */
 	success = ensure_teamd_connection (device);
-	if (nm_device_get_state (device) == NM_DEVICE_STATE_PREPARE) {
-		if (success)
-			success = teamd_read_config (device);
-		if (success)
-			nm_device_activate_schedule_stage2_device_config (device);
-		else if (!nm_device_sys_iface_state_is_external_or_assume (device)) {
-			teamd_cleanup (device, TRUE);
-			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
-		}
+
+	if (   nm_device_get_state (device) != NM_DEVICE_STATE_PREPARE
+	    || priv->stage1_state != NM_DEVICE_STAGE_STATE_PENDING)
+		return;
+
+	if (success)
+		success = teamd_read_config (self);
+
+	if (!success) {
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
+		return;
 	}
+
+	priv->stage1_state = NM_DEVICE_STAGE_STATE_COMPLETED;
+	nm_device_activate_schedule_stage1_device_prepare (device);
 }
 
 static void
@@ -437,15 +432,16 @@ teamd_dbus_vanished (GDBusConnection *dbus_connection,
 	}
 
 	_LOGI (LOGD_TEAM, "teamd vanished from D-Bus");
-	teamd_cleanup (device, TRUE);
+	teamd_cleanup (self, TRUE);
 
 	/* Attempt to respawn teamd */
-	if (state >= NM_DEVICE_STATE_PREPARE && state <= NM_DEVICE_STATE_ACTIVATED) {
-		NMConnection *connection = nm_device_get_applied_connection (device);
-
-		g_assert (connection);
-		if (!teamd_start (device, connection))
-			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
+	if (   state >= NM_DEVICE_STATE_PREPARE
+	    && state <= NM_DEVICE_STATE_ACTIVATED) {
+		if (!teamd_start (self)) {
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
+		}
 	}
 }
 
@@ -470,7 +466,7 @@ teamd_process_watch_cb (GPid pid, int status, gpointer user_data)
 	    (state >= NM_DEVICE_STATE_PREPARE) &&
 	    (state <= NM_DEVICE_STATE_ACTIVATED)) {
 		_LOGW (LOGD_TEAM, "teamd process %lld quit unexpectedly; failing activation", (long long) pid);
-		teamd_cleanup (device, TRUE);
+		teamd_cleanup (self, TRUE);
 		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
 	}
 }
@@ -525,11 +521,11 @@ teamd_kill (NMDeviceTeam *self, const char *teamd_binary, GError **error)
 }
 
 static gboolean
-teamd_start (NMDevice *device, NMConnection *connection)
+teamd_start (NMDeviceTeam *self)
 {
-	NMDeviceTeam *self = NM_DEVICE_TEAM (device);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
-	const char *iface = nm_device_get_ip_iface (device);
+	const char *iface = nm_device_get_ip_iface (NM_DEVICE (self));
+	NMConnection *connection;
 	gs_unref_ptrarray GPtrArray *argv = NULL;
 	gs_free_error GError *error = NULL;
 	gs_free char *tmp_str = NULL;
@@ -540,8 +536,13 @@ teamd_start (NMDevice *device, NMConnection *connection)
 	gs_free char *cloned_mac = NULL;
 	gs_free const char **envp = NULL;
 
+	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
 	s_team = nm_connection_get_setting_team (connection);
-	g_return_val_if_fail (s_team, FALSE);
+	if (!s_team)
+		g_return_val_if_reached (FALSE);
+
+	nm_assert (iface);
 
 	teamd_binary = nm_utils_find_helper ("teamd", NULL, NULL);
 	if (!teamd_binary) {
@@ -553,7 +554,7 @@ teamd_start (NMDevice *device, NMConnection *connection)
 		g_warn_if_reached ();
 		if (!priv->teamd_pid)
 			teamd_kill (self, teamd_binary, NULL);
-		teamd_cleanup (device, TRUE);
+		teamd_cleanup (self, TRUE);
 	}
 
 	/* Start teamd now */
@@ -568,7 +569,7 @@ teamd_start (NMDevice *device, NMConnection *connection)
 	g_ptr_array_add (argv, (gpointer) iface);
 
 	config = nm_setting_team_get_config (s_team);
-	if (!nm_device_hw_addr_get_cloned (device, connection, FALSE, &cloned_mac, NULL, &error)) {
+	if (!nm_device_hw_addr_get_cloned (NM_DEVICE (self), connection, FALSE, &cloned_mac, NULL, &error)) {
 		_LOGW (LOGD_DEVICE, "set-hw-addr: %s", error->message);
 		return FALSE;
 	}
@@ -615,18 +616,18 @@ teamd_start (NMDevice *device, NMConnection *connection)
 	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);
+		teamd_cleanup (self, TRUE);
 		return FALSE;
 	}
 
 	/* Start a timeout for teamd to appear at D-Bus */
 	if (!priv->teamd_timeout)
-		priv->teamd_timeout = g_timeout_add_seconds (5, teamd_timeout_cb, device);
+		priv->teamd_timeout = g_timeout_add_seconds (5, teamd_timeout_cb, self);
 
 	/* Monitor the child process so we know when it dies */
 	priv->teamd_process_watch = g_child_watch_add (priv->teamd_pid,
 	                                               teamd_process_watch_cb,
-	                                               device);
+	                                               self);
 
 	_LOGI (LOGD_TEAM, "Activation: (team) started teamd [pid %u]...", (guint) priv->teamd_pid);
 	return TRUE;
@@ -637,21 +638,21 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceTeam *self = NM_DEVICE_TEAM (device);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
 	gs_free_error GError *error = NULL;
 	NMSettingTeam *s_team;
-	NMConnection *connection;
 	const char *cfg;
 
-	ret = NM_DEVICE_CLASS (nm_device_team_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
+	s_team = nm_device_get_applied_setting (device, NM_TYPE_SETTING_TEAM);
+	if (!s_team)
+		g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE);
 
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+	if (priv->stage1_state == NM_DEVICE_STAGE_STATE_PENDING)
+		return NM_ACT_STAGE_RETURN_POSTPONE;
 
-	s_team = nm_connection_get_setting_team (connection);
-	g_return_val_if_fail (s_team, NM_ACT_STAGE_RETURN_FAILURE);
+	if (priv->stage1_state == NM_DEVICE_STAGE_STATE_COMPLETED)
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+
+	priv->stage1_state = NM_DEVICE_STAGE_STATE_PENDING;
 
 	if (priv->tdc) {
 		/* If the existing teamd config is the same as we're about to use,
@@ -660,7 +661,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		 * have a PID, then we must fail.
 		 */
 		cfg = teamdctl_config_get_raw (priv->tdc);
-		if (cfg && nm_streq0 (cfg,  nm_setting_team_get_config (s_team))) {
+		if (   cfg
+		    && nm_streq0 (cfg,  nm_setting_team_get_config (s_team))) {
 			_LOGD (LOGD_TEAM, "using existing matching teamd config");
 			return NM_ACT_STAGE_RETURN_SUCCESS;
 		}
@@ -675,17 +677,18 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		}
 
 		_LOGD (LOGD_TEAM, "existing teamd config mismatch; respawning...");
-		teamd_cleanup (device, TRUE);
+		teamd_cleanup (self, TRUE);
 	}
 
 	if (priv->kill_in_progress) {
 		_LOGT (LOGD_TEAM, "kill in progress, wait before starting teamd");
-		priv->connection = g_object_ref (connection);
 		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
-	return teamd_start (device, connection) ?
-		NM_ACT_STAGE_RETURN_POSTPONE : NM_ACT_STAGE_RETURN_FAILURE;
+	if (!teamd_start (self))
+		return NM_ACT_STAGE_RETURN_FAILURE;
+
+	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
 static void
@@ -694,16 +697,19 @@ deactivate (NMDevice *device)
 	NMDeviceTeam *self = NM_DEVICE_TEAM (device);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
 
+	priv->stage1_state = NM_DEVICE_STAGE_STATE_INIT;
+
 	if (nm_device_sys_iface_state_is_external (device))
 		return;
 
-	if (priv->teamd_pid || priv->tdc)
+	if (   priv->teamd_pid
+	    || priv->tdc)
 		_LOGI (LOGD_TEAM, "deactivation: stopping teamd...");
 
 	if (!priv->teamd_pid)
 		teamd_kill (self, NULL, NULL);
-	teamd_cleanup (device, TRUE);
-	g_clear_object (&priv->connection);
+
+	teamd_cleanup (self, TRUE);
 }
 
 static gboolean
@@ -773,27 +779,27 @@ release_slave (NMDevice *device,
 {
 	NMDeviceTeam *self = NM_DEVICE_TEAM (device);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
-	gboolean success;
+	gboolean do_release, success;
+	NMSettingTeamPort *s_port;
 	int ifindex_slave;
 	int ifindex;
 
-	ifindex = nm_device_get_ifindex (device);
-	if (   ifindex <= 0
-	    || !nm_platform_link_get (nm_device_get_platform (device), ifindex))
-		configure = FALSE;
+	do_release = configure;
+	if (do_release) {
+		ifindex = nm_device_get_ifindex (device);
+		if (   ifindex <= 0
+		    || !nm_platform_link_get (nm_device_get_platform (device), ifindex))
+			do_release = FALSE;
+	}
 
 	ifindex_slave = nm_device_get_ip_ifindex (slave);
 
 	if (ifindex_slave <= 0) {
 		_LOGD (LOGD_TEAM, "team port %s is already released", nm_device_get_ip_iface (slave));
-		return;
-	}
-
-	if (configure) {
+	} else if (do_release) {
 		success = nm_platform_link_release (nm_device_get_platform (device),
 		                                    nm_device_get_ip_ifindex (device),
 		                                    ifindex_slave);
-
 		if (success)
 			_LOGI (LOGD_TEAM, "released team port %s", nm_device_get_ip_iface (slave));
 		else
@@ -814,6 +820,13 @@ release_slave (NMDevice *device,
 		                                                  self);
 	} else
 		_LOGI (LOGD_TEAM, "team port %s was released", nm_device_get_ip_iface (slave));
+
+	/* Delete any port configuration we previously set */
+	if (   configure
+	    && priv->tdc
+	    && (s_port = nm_device_get_applied_setting (slave, NM_TYPE_SETTING_TEAM_PORT))
+	    && (nm_setting_team_port_get_config (s_port)))
+		teamdctl_port_config_update_raw (priv->tdc, nm_device_get_ip_iface (slave), "{}");
 }
 
 static gboolean
@@ -869,7 +882,7 @@ static void
 constructed (GObject *object)
 {
 	NMDevice *device = NM_DEVICE (object);
-	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE ((NMDeviceTeam *) device);
+	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (device);
 	char *tmp_str = NULL;
 
 	G_OBJECT_CLASS (nm_device_team_parent_class)->constructed (object);
@@ -901,15 +914,15 @@ nm_device_team_new (const char *iface)
 static void
 dispose (GObject *object)
 {
-	NMDevice *device = NM_DEVICE (object);
-	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE ((NMDeviceTeam *) device);
+	NMDeviceTeam *self = NM_DEVICE_TEAM (object);
+	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
 
 	if (priv->teamd_dbus_watch) {
 		g_bus_unwatch_name (priv->teamd_dbus_watch);
 		priv->teamd_dbus_watch = 0;
 	}
 
-	teamd_cleanup (device, TRUE);
+	teamd_cleanup (self, TRUE);
 	g_clear_pointer (&priv->config, g_free);
 
 	G_OBJECT_CLASS (nm_device_team_parent_class)->dispose (object);
diff --git a/src/devices/team/nm-device-team.h b/src/devices/team/nm-device-team.h
index 0c0b5101..71312c07 100644
--- a/src/devices/team/nm-device-team.h
+++ b/src/devices/team/nm-device-team.h
@@ -1,20 +1,6 @@
-/* NetworkManager -- Network link manager
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2013 Jiri Pirko <jiri@resnulli.us>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_TEAM_H__
diff --git a/src/devices/team/nm-team-factory.c b/src/devices/team/nm-team-factory.c
index 21a85701..3768217b 100644
--- a/src/devices/team/nm-team-factory.c
+++ b/src/devices/team/nm-team-factory.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
@@ -58,7 +44,7 @@ NM_DEVICE_FACTORY_DECLARE_TYPES (
 G_MODULE_EXPORT NMDeviceFactory *
 nm_device_factory_create (GError **error)
 {
-	nm_manager_set_capability (nm_manager_get (), NM_CAPABILITY_TEAM);
+	nm_manager_set_capability (NM_MANAGER_GET, NM_CAPABILITY_TEAM);
 	return (NMDeviceFactory *) g_object_new (NM_TYPE_TEAM_FACTORY, NULL);
 }
 
diff --git a/src/devices/tests/meson.build b/src/devices/tests/meson.build
index 4b4c5c7e..bb53d1ee 100644
--- a/src/devices/tests/meson.build
+++ b/src/devices/tests/meson.build
@@ -7,8 +7,10 @@ foreach test_unit: test_units
   exe = executable(
     test_unit,
     test_unit + '.c',
-    dependencies: test_nm_dep,
+    dependencies: libnetwork_manager_test_dep,
+    c_args: test_c_flags,
   )
+
   test(
     'devices/' + test_unit,
     test_script,
diff --git a/src/devices/tests/test-acd.c b/src/devices/tests/test-acd.c
index 42accfaf..da5e4f2d 100644
--- a/src/devices/tests/test-acd.c
+++ b/src/devices/tests/test-acd.c
@@ -1,19 +1,5 @@
-/* nm-platform.c - Handle runtime kernel networking configuration
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
diff --git a/src/devices/tests/test-lldp.c b/src/devices/tests/test-lldp.c
index ad157b7e..bd71c9e3 100644
--- a/src/devices/tests/test-lldp.c
+++ b/src/devices/tests/test-lldp.c
@@ -1,19 +1,5 @@
-/* nm-platform.c - Handle runtime kernel networking configuration
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2015 Red Hat, Inc.
  */
 
diff --git a/src/devices/wifi/meson.build b/src/devices/wifi/meson.build
index 4dfbe4c8..6566f201 100644
--- a/src/devices/wifi/meson.build
+++ b/src/devices/wifi/meson.build
@@ -19,14 +19,11 @@ if enable_iwd
   )
 endif
 
-deps = [
-  nm_dep,
-]
-
 libnm_device_plugin_wifi = shared_module(
   'nm-device-plugin-wifi',
   sources: sources,
-  dependencies: deps,
+  dependencies: daemon_nm_default_dep,
+  c_args: daemon_c_flags,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
@@ -41,13 +38,20 @@ test(
   args: [libnm_device_plugin_wifi.full_path(), linker_script_devices],
 )
 
-# FIXME: check_so_symbols replacement
-'''
-check-local-devices-wifi: src/devices/wifi/libnm-device-plugin-wifi.la
-  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wifi/.libs/libnm-device-plugin-wifi.so "$(srcdir)/linker-script-devices.ver"
-  $(call check_so_symbols,$(builddir)/src/devices/wifi/.libs/libnm-device-plugin-wifi.so)
-'''
-
 if enable_tests
-  subdir('tests')
+  test_unit = 'test-devices-wifi'
+
+  exe = executable(
+    test_unit,
+    ['tests/' + test_unit + '.c'] + common_sources,
+    dependencies: libnetwork_manager_test_dep,
+    c_args: test_c_flags,
+  )
+
+  test(
+    test_unit,
+    test_script,
+    args: test_args + [exe.full_path()],
+    timeout: default_test_timeout,
+  )
 endif
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index 0e6759e1..6b587e3f 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2017 Intel Corporation
  */
 
@@ -1714,18 +1700,14 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
-	NMActStageReturn ret;
 	NMWifiAP *ap = NULL;
+	gs_unref_object NMWifiAP *ap_fake = NULL;
 	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);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
 	req = nm_device_get_act_request (device);
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
 
@@ -1741,7 +1723,9 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		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;
+	ap =   ap_path
+	     ? nm_wifi_ap_lookup_for_device (NM_DEVICE (self), ap_path)
+	     : NULL;
 	if (ap) {
 		set_current_ap (self, ap, TRUE);
 		return NM_ACT_STAGE_RETURN_SUCCESS;
@@ -1767,19 +1751,19 @@ add_new:
 	 * 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);
+	ap_fake = nm_wifi_ap_new_fake_from_connection (connection);
+	if (!ap_fake)
+		g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE);
 
-	if (nm_wifi_ap_is_hotspot (ap))
-		nm_wifi_ap_set_address (ap, nm_device_get_hw_address (device));
+	if (nm_wifi_ap_is_hotspot (ap_fake))
+		nm_wifi_ap_set_address (ap_fake, nm_device_get_hw_address (device));
 
 	g_object_freeze_notify (G_OBJECT (self));
-	ap_add_remove (self, TRUE, ap, FALSE);
+	ap_add_remove (self, TRUE, ap_fake, FALSE);
 	g_object_thaw_notify (G_OBJECT (self));
-	set_current_ap (self, ap, FALSE);
+	set_current_ap (self, ap_fake, FALSE);
 	nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
-	                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
-	g_object_unref (ap);
+	                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap_fake)));
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
diff --git a/src/devices/wifi/nm-device-iwd.h b/src/devices/wifi/nm-device-iwd.h
index aab45b7b..586e02f4 100644
--- a/src/devices/wifi/nm-device-iwd.h
+++ b/src/devices/wifi/nm-device-iwd.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2017 Intel Corporation
  */
 
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index aa50c420..c19ec766 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -1,26 +1,11 @@
-/* NetworkManager -- Network link manager
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Dan Williams <dcbw@redhat.com>
  * Sjoerd Simons <sjoerd.simons@collabora.co.uk>
  * Daniel Drake <dsd@laptop.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * (C) Copyright 2005 - 2014 Red Hat, Inc.
- * (C) Copyright 2008 Collabora Ltd.
- * (C) Copyright 2009 One Laptop per Child
+ * Copyright (C) 2005 - 2014 Red Hat, Inc.
+ * Copyright (C) 2008 Collabora Ltd.
+ * Copyright (C) 2009 One Laptop per Child
  */
 
 #include "nm-default.h"
@@ -58,7 +43,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceOlpcMesh,
 typedef struct {
 	NMDevice *companion;
 	NMManager *manager;
-	gboolean  stage1_waiting;
+	bool stage1_waiting:1;
 } NMDeviceOlpcMeshPrivate;
 
 struct _NMDeviceOlpcMesh {
@@ -145,13 +130,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (device);
 	NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self);
-	NMActStageReturn ret;
 	gboolean scanning;
 
-	ret = NM_DEVICE_CLASS (nm_device_olpc_mesh_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
 	/* disconnect companion device, if it is connected */
 	if (nm_device_get_act_request (NM_DEVICE (priv->companion))) {
 		_LOGI (LOGD_OLPC, "disconnecting companion device %s",
@@ -171,6 +151,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
+	priv->stage1_waiting = FALSE;
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
@@ -273,10 +254,9 @@ companion_notify_cb (NMDeviceWifi *companion, GParamSpec *pspec, gpointer user_d
 		return;
 
 	g_object_get (companion, NM_DEVICE_WIFI_SCANNING, &scanning, NULL);
-
 	if (!scanning) {
 		priv->stage1_waiting = FALSE;
-		nm_device_activate_schedule_stage2_device_config (NM_DEVICE (self));
+		nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
 	}
 }
 
@@ -469,7 +449,7 @@ constructed (GObject *object)
 
 	G_OBJECT_CLASS (nm_device_olpc_mesh_parent_class)->constructed (object);
 
-	priv->manager = g_object_ref (nm_manager_get ());
+	priv->manager = g_object_ref (NM_MANAGER_GET);
 
 	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED, G_CALLBACK (device_added_cb), self);
 	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_REMOVED, G_CALLBACK (device_removed_cb), self);
diff --git a/src/devices/wifi/nm-device-olpc-mesh.h b/src/devices/wifi/nm-device-olpc-mesh.h
index 619fc46a..e6e76d6f 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.h
+++ b/src/devices/wifi/nm-device-olpc-mesh.h
@@ -1,26 +1,11 @@
-/* NetworkManager -- Network link manager
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Dan Williams <dcbw@redhat.com>
  * Sjoerd Simons <sjoerd.simons@collabora.co.uk>
  * Daniel Drake <dsd@laptop.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * (C) Copyright 2005 Red Hat, Inc.
- * (C) Copyright 2008 Collabora Ltd.
- * (C) Copyright 2009 One Laptop per Child
+ * Copyright (C) 2005 Red Hat, Inc.
+ * Copyright (C) 2008 Collabora Ltd.
+ * Copyright (C) 2009 One Laptop per Child
  */
 
 #ifndef __NETWORKMANAGER_DEVICE_OLPC_MESH_H__
diff --git a/src/devices/wifi/nm-device-wifi-p2p.c b/src/devices/wifi/nm-device-wifi-p2p.c
index 649f36d0..34ff70fa 100644
--- a/src/devices/wifi/nm-device-wifi-p2p.c
+++ b/src/devices/wifi/nm-device-wifi-p2p.c
@@ -1,21 +1,6 @@
-/* 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.
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -63,6 +48,7 @@ typedef struct {
 
 	CList peers_lst_head;
 
+	guint find_peer_timeout_id;
 	guint sup_timeout_id;
 	guint peer_dump_id;
 	guint peer_missing_id;
@@ -349,7 +335,7 @@ supplicant_find_timeout_cb (gpointer user_data)
 	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
 	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
 
-	priv->sup_timeout_id = 0;
+	priv->find_peer_timeout_id = 0;
 
 	nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
 
@@ -368,27 +354,16 @@ 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);
+	connection = nm_device_get_applied_connection (NM_DEVICE (self));
 	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));
@@ -397,33 +372,19 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	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);
+		if (priv->find_peer_timeout_id == 0) {
+			priv->find_peer_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
  *
@@ -461,7 +422,8 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMWifiP2PPeer *peer;
 	GBytes *wfd_ies;
 
-	nm_clear_g_source (&priv->sup_timeout_id);
+	if (nm_clear_g_source (&priv->find_peer_timeout_id))
+		nm_assert_not_reached ();
 
 	if (!priv->mgmt_iface) {
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
@@ -493,9 +455,11 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	                                     "pbc", NULL);
 
 	/* Set up a timeout on the connect attempt */
-	priv->sup_timeout_id = g_timeout_add_seconds (45,
-	                                              supplicant_connection_timeout_cb,
-	                                              self);
+	if (priv->sup_timeout_id == 0) {
+		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;
@@ -550,17 +514,19 @@ peer_add_remove (NMDeviceWifiP2P *self,
 	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) {
+		if (priv->find_peer_timeout_id != 0) {
 			NMConnection *connection;
 
+			nm_assert (nm_device_get_state (device) == NM_DEVICE_STATE_PREPARE);
+
 			connection = nm_device_get_applied_connection (device);
-			g_assert (connection);
+			nm_assert (NM_IS_CONNECTION (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);
+				nm_clear_g_source (&priv->find_peer_timeout_id);
+				nm_device_activate_schedule_stage1_device_prepare (device);
 			}
 		}
 
@@ -620,8 +586,17 @@ deactivate (NMDevice *device)
 {
 	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
 	int ifindex = nm_device_get_ip_ifindex (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->find_peer_timeout_id);
+	nm_clear_g_source (&priv->sup_timeout_id);
+	nm_clear_g_source (&priv->peer_missing_id);
 
-	cleanup_p2p_connect_attempt (self, TRUE);
+	if (priv->mgmt_iface)
+		nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (priv->group_iface)
+		nm_supplicant_interface_p2p_disconnect (priv->group_iface);
 
 	/* Clear any critical protocol notification in the Wi-Fi stack */
 	if (ifindex > 0)
@@ -922,6 +897,7 @@ supplicant_interfaces_release (NMDeviceWifiP2P *self, gboolean set_is_waiting)
 		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->find_peer_timeout_id);
 		nm_clear_g_source (&priv->sup_timeout_id);
 	}
 
diff --git a/src/devices/wifi/nm-device-wifi-p2p.h b/src/devices/wifi/nm-device-wifi-p2p.h
index a13eef15..df25ec65 100644
--- a/src/devices/wifi/nm-device-wifi-p2p.h
+++ b/src/devices/wifi/nm-device-wifi-p2p.h
@@ -1,21 +1,6 @@
-/* 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.
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2018 Red Hat, Inc.
  */
 
 #ifndef __NM_DEVICE_WIFI_P2P_H__
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index f690100b..65ba2bcc 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2005 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
@@ -411,7 +397,9 @@ set_current_ap (NMDeviceWifi *self, NMWifiAP *new_ap, gboolean recheck_available
 		NM80211Mode mode = nm_wifi_ap_get_mode (old_ap);
 
 		/* Remove any AP from the internal list if it was created by NM or isn't known to the supplicant */
-		if (mode == NM_802_11_MODE_ADHOC || mode == NM_802_11_MODE_AP || nm_wifi_ap_get_fake (old_ap))
+		if (   NM_IN_SET (mode, NM_802_11_MODE_ADHOC,
+		                        NM_802_11_MODE_AP)
+		    || nm_wifi_ap_get_fake (old_ap))
 			ap_add_remove (self, FALSE, old_ap, recheck_available_connections);
 		g_object_unref (old_ap);
 	}
@@ -645,36 +633,6 @@ deactivate_reset_hw_addr (NMDevice *device)
 }
 
 static gboolean
-is_adhoc_wpa (NMConnection *connection)
-{
-	NMSettingWireless *s_wifi;
-	NMSettingWirelessSecurity *s_wsec;
-	const char *mode, *key_mgmt;
-
-	/* The kernel doesn't support Ad-Hoc WPA connections well at this time,
-	 * and turns them into open networks.  It's been this way since at least
-	 * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks.
-	 */
-
-	s_wifi = nm_connection_get_setting_wireless (connection);
-	g_return_val_if_fail (s_wifi != NULL, FALSE);
-
-	mode = nm_setting_wireless_get_mode (s_wifi);
-	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) != 0)
-		return FALSE;
-
-	s_wsec = nm_connection_get_setting_wireless_security (connection);
-	if (!s_wsec)
-		return FALSE;
-
-	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec);
-	if (g_strcmp0 (key_mgmt, "wpa-none") != 0)
-		return FALSE;
-
-	return TRUE;
-}
-
-static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
@@ -720,12 +678,6 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 		return FALSE;
 	}
 
-	if (is_adhoc_wpa (connection)) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "Ad-Hoc WPA networks are not supported");
-		return FALSE;
-	}
-
 	/* Early exit if supplicant or device doesn't support requested mode */
 	mode = nm_setting_wireless_get_mode (s_wireless);
 	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0) {
@@ -953,19 +905,6 @@ complete_connection (NMDevice *device,
 			return FALSE;
 	}
 
-	/* The kernel doesn't support Ad-Hoc WPA connections well at this time,
-	 * and turns them into open networks.  It's been this way since at least
-	 * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks.
-	 */
-	if (is_adhoc_wpa (connection)) {
-		g_set_error_literal (error,
-		                     NM_CONNECTION_ERROR,
-		                     NM_CONNECTION_ERROR_INVALID_SETTING,
-		                     _("WPA Ad-Hoc disabled due to kernel bugs"));
-		g_prefix_error (error, "%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME);
-		return FALSE;
-	}
-
 	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
 	nm_utils_complete_generic (nm_device_get_platform (device),
 	                           connection,
@@ -1278,7 +1217,8 @@ scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 	/* Don't scan when a an AP or Ad-Hoc connection is active as it will
 	 * disrupt connected clients or peers.
 	 */
-	if (priv->mode == NM_802_11_MODE_ADHOC || priv->mode == NM_802_11_MODE_AP)
+	if (NM_IN_SET (priv->mode, NM_802_11_MODE_ADHOC,
+	                           NM_802_11_MODE_AP))
 		return TRUE;
 
 	switch (nm_device_get_state (NM_DEVICE (self))) {
@@ -1781,8 +1721,10 @@ wifi_secrets_cb (NMActRequest *req,
 		nm_device_state_changed (device,
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
-	} else
-		nm_device_activate_schedule_stage1_device_prepare (device);
+		return;
+	}
+
+	nm_device_activate_schedule_stage1_device_prepare (device);
 }
 
 static void
@@ -1801,10 +1743,11 @@ supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
                                      NMDeviceWifi *self)
 {
 	NMActRequest *req;
-	GVariant *val, *secrets = NULL;
+	gs_unref_variant GVariant *val_key = NULL;
+	gs_unref_variant GVariant *secrets = NULL;
+	gs_free_error GError *error = NULL;
 	const char *array;
 	gsize psk_len = 0;
-	GError *error = NULL;
 
 	if (nm_device_get_state (NM_DEVICE (self)) != NM_DEVICE_STATE_NEED_AUTH) {
 		_LOGI (LOGD_DEVICE | LOGD_WIFI, "WPS: The connection can't be updated with credentials");
@@ -1816,11 +1759,11 @@ supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
 	req = nm_device_get_act_request (NM_DEVICE (self));
 	g_return_if_fail (NM_IS_ACT_REQUEST (req));
 
-	val = g_variant_lookup_value (credentials, "Key", G_VARIANT_TYPE_BYTESTRING);
-	if (val) {
+	val_key = g_variant_lookup_value (credentials, "Key", G_VARIANT_TYPE_BYTESTRING);
+	if (val_key) {
 		char psk[64];
 
-		array = g_variant_get_fixed_array (val, &psk_len, 1);
+		array = g_variant_get_fixed_array (val_key, &psk_len, 1);
 		if (psk_len >= 8 && psk_len <= 63) {
 			memcpy (psk, array, psk_len);
 			psk[psk_len] = '\0';
@@ -1833,22 +1776,22 @@ supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
 		}
 		if (!secrets)
 			_LOGW (LOGD_DEVICE | LOGD_WIFI, "WPS: ignore invalid PSK");
-		g_variant_unref (val);
-	}
-	if (secrets) {
-		if (nm_settings_connection_new_secrets (nm_act_request_get_settings_connection (req),
-		                                        nm_act_request_get_applied_connection (req),
-		                                        NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-		                                        secrets,
-		                                        &error)) {
-			wifi_secrets_cancel (self);
-			nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
-		} else {
-			_LOGW (LOGD_DEVICE | LOGD_WIFI, "WPS: Could not update the connection with credentials: %s", error->message);
-			g_error_free (error);
-		}
-		g_variant_unref (secrets);
 	}
+
+	if (!secrets)
+		return;
+
+	if (!nm_settings_connection_new_secrets (nm_act_request_get_settings_connection (req),
+	                                         nm_act_request_get_applied_connection (req),
+	                                         NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+	                                         secrets,
+	                                         &error)) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI, "WPS: Could not update the connection with credentials: %s", error->message);
+		return;
+	}
+
+	wifi_secrets_cancel (self);
+	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
 }
 
 static gboolean
@@ -2458,9 +2401,9 @@ supplicant_connection_timeout_cb (gpointer user_data)
 	connection = nm_act_request_get_applied_connection (req);
 	g_assert (connection);
 
-	if (   priv->mode == NM_802_11_MODE_ADHOC
-	    || priv->mode == NM_802_11_MODE_MESH
-	    || priv->mode == NM_802_11_MODE_AP) {
+	if (NM_IN_SET (priv->mode, NM_802_11_MODE_ADHOC,
+	                           NM_802_11_MODE_MESH,
+	                           NM_802_11_MODE_AP)) {
 		/* In Ad-Hoc and AP modes there's nothing to check the encryption key
 		 * (if any), so supplicant timeouts here are almost certainly the wifi
 		 * driver being really stupid.
@@ -2666,18 +2609,14 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	NMActStageReturn ret;
 	NMWifiAP *ap = NULL;
+	gs_unref_object NMWifiAP *ap_fake = NULL;
 	NMActRequest *req;
 	NMConnection *connection;
 	NMSettingWireless *s_wireless;
 	const char *mode;
 	const char *ap_path;
 
-	ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
 	req = nm_device_get_act_request (NM_DEVICE (self));
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
 
@@ -2703,62 +2642,49 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		priv->mode = NM_802_11_MODE_MESH;
 	_notify (self, PROP_MODE);
 
-	/* The kernel doesn't support Ad-Hoc WPA connections well at this time,
-	 * and turns them into open networks.  It's been this way since at least
-	 * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks.
-	 */
-	if (is_adhoc_wpa (connection)) {
-		_LOGW (LOGD_WIFI, "Ad-Hoc WPA disabled due to kernel bugs");
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
-		return NM_ACT_STAGE_RETURN_FAILURE;
-	}
-
 	/* expire the temporary MAC address used during scanning */
 	priv->hw_addr_scan_expire = 0;
 
 	/* Set spoof MAC to the interface */
-	if (!nm_device_hw_addr_set_cloned (device, connection, TRUE))
+	if (!nm_device_hw_addr_set_cloned (device, connection, TRUE)) {
+		*out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED;
 		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
 
 	/* AP and Mesh modes never use a specific object or existing scanned AP */
-	if (priv->mode != NM_802_11_MODE_AP && priv->mode != NM_802_11_MODE_MESH) {
+	if (!NM_IN_SET (priv->mode, NM_802_11_MODE_AP,
+	                            NM_802_11_MODE_MESH)) {
 		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)
-			goto done;
-
-		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+		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_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
-		                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
-		goto done;
-	}
+	if (!ap) {
+		/* If the user is trying to connect to an AP that NM doesn't yet know about
+		 * (hidden network or something), starting a Hotspot or joining a Mesh,
+		 * create a fake APfrom 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 (Hotspot).
+		 */
+		ap_fake = nm_wifi_ap_new_fake_from_connection (connection);
+		if (!ap_fake)
+			g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE);
 
-	/* If the user is trying to connect to an AP that NM doesn't yet know about
-	 * (hidden network or something), starting a Hotspot or joining a Mesh,
-	 * create a fake APfrom 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 (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_fake))
+			nm_wifi_ap_set_address (ap_fake, nm_device_get_hw_address (device));
 
-	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_fake, TRUE);
+		g_object_thaw_notify (G_OBJECT (self));
+		ap = ap_fake;
+	}
 
-	g_object_freeze_notify (G_OBJECT (self));
-	ap_add_remove (self, TRUE, ap, TRUE);
-	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;
-
-done:
-	set_current_ap (self, ap, TRUE);
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
@@ -2893,8 +2819,8 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	 * if the user didn't specify one and we didn't find an AP that matched
 	 * the connection, just pick a frequency the device supports.
 	 */
-	if (   ap_mode == NM_802_11_MODE_ADHOC
-	    || ap_mode == NM_802_11_MODE_MESH
+	if (   NM_IN_SET (ap_mode, NM_802_11_MODE_ADHOC,
+	                           NM_802_11_MODE_MESH)
 	    || nm_wifi_ap_is_hotspot (ap))
 		ensure_hotspot_frequency (self, s_wireless, ap);
 
@@ -3242,6 +3168,15 @@ set_enabled (NMDevice *device, gboolean enabled)
 }
 
 static gboolean
+get_guessed_metered (NMDevice *device)
+{
+	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+	return priv->current_ap && nm_wifi_ap_get_metered (priv->current_ap);
+}
+
+static gboolean
 can_reapply_change (NMDevice *device,
                     const char *setting_name,
                     NMSetting *s_old,
@@ -3274,6 +3209,7 @@ static void
 reapply_connection (NMDevice *device, NMConnection *con_old, NMConnection *con_new)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceState state = nm_device_get_state (device);
 
 	NM_DEVICE_CLASS (nm_device_wifi_parent_class)->reapply_connection (device,
 	                                                                   con_old,
@@ -3281,7 +3217,8 @@ reapply_connection (NMDevice *device, NMConnection *con_old, NMConnection *con_n
 
 	_LOGD (LOGD_DEVICE, "reapplying wireless settings");
 
-	if (!wake_on_wlan_enable (self))
+	if (   state >= NM_DEVICE_STATE_CONFIG
+	    && !wake_on_wlan_enable (self))
 		_LOGW (LOGD_DEVICE | LOGD_WIFI, "Cannot configure WoWLAN.");
 }
 
@@ -3451,6 +3388,7 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	device_class->check_connection_available = check_connection_available;
 	device_class->complete_connection = complete_connection;
 	device_class->get_enabled = get_enabled;
+	device_class->get_guessed_metered = get_guessed_metered;
 	device_class->set_enabled = set_enabled;
 
 	device_class->act_stage1_prepare = act_stage1_prepare;
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index 82f62be6..aaf47143 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2005 - 2016 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index c79f6cc6..470cb1c9 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2017 Intel Corporation
  */
 
@@ -912,7 +898,7 @@ nm_iwd_manager_init (NMIwdManager *self)
 {
 	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
 
-	priv->manager = g_object_ref (nm_manager_get ());
+	priv->manager = g_object_ref (NM_MANAGER_GET);
 	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED,
 	                  G_CALLBACK (device_added), self);
 
diff --git a/src/devices/wifi/nm-iwd-manager.h b/src/devices/wifi/nm-iwd-manager.h
index b410e4ce..c50963fe 100644
--- a/src/devices/wifi/nm-iwd-manager.h
+++ b/src/devices/wifi/nm-iwd-manager.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2017 Intel Corporation
  */
 
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index c7ab7f04..ee7dc236 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2004 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
@@ -67,10 +53,12 @@ struct _NMWifiAPPrivate {
 	NM80211ApSecurityFlags wpa_flags;  /* WPA-related flags */
 	NM80211ApSecurityFlags rsn_flags;  /* RSN (WPA2) -related flags */
 
+	bool               metered:1;
+
 	/* Non-scanned attributes */
-	bool                fake:1;       /* Whether or not the AP is from a scan */
-	bool                hotspot:1;    /* Whether the AP is a local device's hotspot network */
-	gint32              last_seen;    /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
+	bool               fake:1;       /* Whether or not the AP is from a scan */
+	bool               hotspot:1;    /* Whether the AP is a local device's hotspot network */
+	gint32             last_seen;    /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
 };
 
 typedef struct _NMWifiAPPrivate NMWifiAPPrivate;
@@ -406,6 +394,12 @@ nm_wifi_ap_set_last_seen (NMWifiAP *ap, gint32 last_seen)
 	return FALSE;
 }
 
+gboolean
+nm_wifi_ap_get_metered (const NMWifiAP *self)
+{
+	return NM_WIFI_AP_GET_PRIVATE (self)->metered;
+}
+
 /*****************************************************************************/
 
 static NM80211ApSecurityFlags
@@ -731,44 +725,50 @@ get_max_rate_vht (const guint8 *bytes, guint len, guint32 *out_maxrate)
 /* Management Frame Information Element IDs, ieee80211_eid */
 #define WLAN_EID_HT_CAPABILITY       45
 #define WLAN_EID_VHT_CAPABILITY     191
+#define WLAN_EID_VENDOR_SPECIFIC    221
 
-static guint32
-get_max_rate (const guint8 *bytes, gsize len)
+static void
+parse_ies (const guint8 *bytes, gsize len, guint32 *out_max_rate, gboolean *out_metered)
 {
 	guint8 id, elem_len;
-	guint32 max_rate = 0;
+	guint32 m;
 
-	while (len) {
-		guint32 m;
+	*out_max_rate = 0;
+	*out_metered = FALSE;
 
+	while (len) {
 		if (len < 2)
-			return 0;
+			break;
 
 		id = *bytes++;
 		elem_len = *bytes++;
 		len -= 2;
 
 		if (elem_len > len)
-			return 0;
+			break;
 
 		switch (id) {
 		case WLAN_EID_HT_CAPABILITY:
-			if (!get_max_rate_ht (bytes, elem_len, &m))
-				return 0;
-			max_rate = NM_MAX (max_rate, m);
+			if (get_max_rate_ht (bytes, elem_len, &m))
+				*out_max_rate = NM_MAX (*out_max_rate, m);
 			break;
 		case WLAN_EID_VHT_CAPABILITY:
-			if (!get_max_rate_vht (bytes, elem_len, &m))
-				return 0;
-			max_rate = NM_MAX (max_rate, m);
+			if (get_max_rate_vht (bytes, elem_len, &m))
+				*out_max_rate = NM_MAX (*out_max_rate, m);
+			break;
+		case WLAN_EID_VENDOR_SPECIFIC:
+			if (   len == 8
+			    && bytes[0] == 0x00            /* OUI: Microsoft */
+			    && bytes[1] == 0x50
+			    && bytes[2] == 0xf2
+			    && bytes[3] == 0x11)           /* OUI type: Network cost */
+				*out_metered = (bytes[7] > 1); /* Cost level > 1 */
 			break;
 		}
 
 		len -= elem_len;
 		bytes += elem_len;
 	}
-
-	return max_rate;
 }
 
 /*****************************************************************************/
@@ -788,7 +788,8 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 	gint16 i16;
 	guint16 u16;
 	gboolean changed = FALSE;
-	guint32 max_rate;
+	gboolean metered;
+	guint32 max_rate, rate;
 
 	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
 	g_return_val_if_fail (properties, FALSE);
@@ -869,9 +870,12 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 	v = g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
 	if (v) {
 		bytes = g_variant_get_fixed_array (v, &len, 1);
-		max_rate = NM_MAX (max_rate, get_max_rate (bytes, len));
+		parse_ies (bytes, len, &rate, &metered);
+		max_rate = NM_MAX (max_rate, rate);
 		g_variant_unref (v);
+		priv->metered = metered;
 	}
+
 	if (max_rate)
 		changed |= nm_wifi_ap_set_max_bitrate (ap, max_rate / 1000);
 
@@ -1002,7 +1006,7 @@ nm_wifi_ap_to_string (const NMWifiAP *self,
 		export_path = "/";
 
 	g_snprintf (str_buf, buf_len,
-	            "%17s %-35s [ %c %3u %3u%% %c W:%04X R:%04X ] %3us sup:%s [nm:%s]",
+	            "%17s %-35s [ %c %3u %3u%% %c%c W:%04X R:%04X ] %3us sup:%s [nm:%s]",
 	            priv->address ?: "(none)",
 	            (ssid_to_free = _nm_utils_ssid_to_string (priv->ssid)),
 	            (priv->mode == NM_802_11_MODE_ADHOC
@@ -1017,6 +1021,7 @@ nm_wifi_ap_to_string (const NMWifiAP *self,
 	            chan,
 	            priv->strength,
 	            priv->flags & NM_802_11_AP_FLAGS_PRIVACY ? 'P' : '_',
+	            priv->metered ? 'M' : '_',
 	            priv->wpa_flags & 0xFFFF,
 	            priv->rsn_flags & 0xFFFF,
 	            priv->last_seen > 0 ? ((now_s > 0 ? now_s : nm_utils_get_monotonic_timestamp_s ()) - priv->last_seen) : -1,
@@ -1232,7 +1237,7 @@ nm_wifi_ap_new_fake_from_connection (NMConnection *connection)
 	const char *mode, *band, *key_mgmt;
 	guint32 channel;
 	NM80211ApSecurityFlags flags;
-	gboolean psk = FALSE, eap = FALSE;
+	gboolean psk = FALSE, eap = FALSE, adhoc = FALSE;
 
 	g_return_val_if_fail (connection != NULL, NULL);
 
@@ -1252,9 +1257,10 @@ nm_wifi_ap_new_fake_from_connection (NMConnection *connection)
 	if (mode) {
 		if (!strcmp (mode, "infrastructure"))
 			nm_wifi_ap_set_mode (ap, NM_802_11_MODE_INFRA);
-		else if (!strcmp (mode, "adhoc"))
+		else if (!strcmp (mode, "adhoc")) {
 			nm_wifi_ap_set_mode (ap, NM_802_11_MODE_ADHOC);
-		else if (!strcmp (mode, "mesh"))
+			adhoc = TRUE;
+		} else if (!strcmp (mode, "mesh"))
 			nm_wifi_ap_set_mode (ap, NM_802_11_MODE_MESH);
 		else if (!strcmp (mode, "ap")) {
 			nm_wifi_ap_set_mode (ap, NM_802_11_MODE_INFRA);
@@ -1293,7 +1299,7 @@ nm_wifi_ap_new_fake_from_connection (NMConnection *connection)
 
 	psk = !strcmp (key_mgmt, "wpa-psk");
 	eap = !strcmp (key_mgmt, "wpa-eap");
-	if (psk || eap) {
+	if (!adhoc && (psk || eap)) {
 		if (has_proto (s_wireless_sec, PROTO_WPA)) {
 			flags = priv->wpa_flags | (eap ? NM_802_11_AP_SEC_KEY_MGMT_802_1X : NM_802_11_AP_SEC_KEY_MGMT_PSK);
 			nm_wifi_ap_set_wpa_flags (ap, flags);
@@ -1305,42 +1311,27 @@ nm_wifi_ap_new_fake_from_connection (NMConnection *connection)
 
 		add_pair_ciphers (ap, s_wireless_sec);
 		add_group_ciphers (ap, s_wireless_sec);
-	} else if (!strcmp (key_mgmt, "wpa-none")) {
-		guint32 i;
-
-		/* Ad-Hoc has special requirements: proto=WPA, pairwise=(none), and
-		 * group=TKIP/CCMP (but not both).
+	} else if (adhoc && psk) {
+		/* Ad-Hoc has special requirements: proto=RSN, pairwise=CCMP and
+		 * group=CCMP.
 		 */
-
 		flags = priv->wpa_flags | NM_802_11_AP_SEC_KEY_MGMT_PSK;
 
-		/* Clear ciphers; pairwise must be unset anyway, and group gets set below */
+		/* Clear ciphers; only CCMP is supported */
 		flags &= ~(  NM_802_11_AP_SEC_PAIR_WEP40
 		           | NM_802_11_AP_SEC_PAIR_WEP104
 		           | NM_802_11_AP_SEC_PAIR_TKIP
-		           | NM_802_11_AP_SEC_PAIR_CCMP
 		           | NM_802_11_AP_SEC_GROUP_WEP40
 		           | NM_802_11_AP_SEC_GROUP_WEP104
-		           | NM_802_11_AP_SEC_GROUP_TKIP
-		           | NM_802_11_AP_SEC_GROUP_CCMP);
+		           | NM_802_11_AP_SEC_GROUP_TKIP);
 
-		for (i = 0; i < nm_setting_wireless_security_get_num_groups (s_wireless_sec); i++) {
-			if (!strcmp (nm_setting_wireless_security_get_group (s_wireless_sec, i), "ccmp")) {
-				flags |= NM_802_11_AP_SEC_GROUP_CCMP;
-				break;
-			}
-		}
-
-		/* Default to TKIP since not all WPA-capable cards can do CCMP */
-		if (!(flags & NM_802_11_AP_SEC_GROUP_CCMP))
-			flags |= NM_802_11_AP_SEC_GROUP_TKIP;
+		flags |= NM_802_11_AP_SEC_PAIR_CCMP;
+		flags |= NM_802_11_AP_SEC_GROUP_CCMP;
+		nm_wifi_ap_set_rsn_flags (ap, flags);
 
-		nm_wifi_ap_set_wpa_flags (ap, flags);
-
-		/* Don't use Ad-Hoc RSN yet */
-		nm_wifi_ap_set_rsn_flags (ap, NM_802_11_AP_SEC_NONE);
+		/* Don't use Ad-Hoc WPA (WPA-none) anymore */
+		nm_wifi_ap_set_wpa_flags (ap, NM_802_11_AP_SEC_NONE);
 	}
-
 done:
 	return ap;
 
diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h
index 755e722c..472dfdf9 100644
--- a/src/devices/wifi/nm-wifi-ap.h
+++ b/src/devices/wifi/nm-wifi-ap.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2004 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
@@ -95,6 +81,7 @@ gboolean          nm_wifi_ap_get_fake                 (const NMWifiAP *ap);
 gboolean          nm_wifi_ap_set_fake                 (NMWifiAP *ap,
                                                        gboolean fake);
 NM80211ApFlags    nm_wifi_ap_get_flags                (const NMWifiAP *self);
+gboolean          nm_wifi_ap_get_metered              (const NMWifiAP *self);
 
 const char       *nm_wifi_ap_to_string                (const NMWifiAP *self,
                                                        char *str_buf,
diff --git a/src/devices/wifi/nm-wifi-common.c b/src/devices/wifi/nm-wifi-common.c
index 96828d59..087465b5 100644
--- a/src/devices/wifi/nm-wifi-common.c
+++ b/src/devices/wifi/nm-wifi-common.c
@@ -1,20 +1,6 @@
+// SPDX-License-Identifier: LGPL-2.1+
 /*
- * 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.
+ * Copyright (C) 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
diff --git a/src/devices/wifi/nm-wifi-common.h b/src/devices/wifi/nm-wifi-common.h
index 81d657ec..829df826 100644
--- a/src/devices/wifi/nm-wifi-common.h
+++ b/src/devices/wifi/nm-wifi-common.h
@@ -1,20 +1,6 @@
+// SPDX-License-Identifier: LGPL-2.1+
 /*
- * 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.
+ * Copyright (C) 2018 Red Hat, Inc.
  */
 
 #ifndef __NM_WIFI_COMMON_H__
diff --git a/src/devices/wifi/nm-wifi-factory.c b/src/devices/wifi/nm-wifi-factory.c
index 2f069882..821460a5 100644
--- a/src/devices/wifi/nm-wifi-factory.c
+++ b/src/devices/wifi/nm-wifi-factory.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2011 - 2014 Red Hat, Inc.
  */
 
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.c b/src/devices/wifi/nm-wifi-p2p-peer.c
index 4b524623..f8da0046 100644
--- a/src/devices/wifi/nm-wifi-p2p-peer.c
+++ b/src/devices/wifi/nm-wifi-p2p-peer.c
@@ -1,20 +1,5 @@
-/* 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.
- *
+// SPDX-License-Identifier: LGPL-2.1+
+/*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.h b/src/devices/wifi/nm-wifi-p2p-peer.h
index d6ff7abc..07f25cc1 100644
--- a/src/devices/wifi/nm-wifi-p2p-peer.h
+++ b/src/devices/wifi/nm-wifi-p2p-peer.h
@@ -1,20 +1,5 @@
-/* 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.
- *
+// SPDX-License-Identifier: LGPL-2.1+
+/*
  * Copyright (C) 2018 Red Hat, Inc.
  */
 
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index 426eeea8..b9b7ec42 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -1,20 +1,6 @@
+// SPDX-License-Identifier: LGPL-2.1+
 /*
- * 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 2011 Red Hat, Inc.
+ * Copyright (C) 2011 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -297,96 +283,79 @@ verify_wpa_psk (NMSettingWirelessSecurity *s_wsec,
                 guint32 rsn_flags,
                 GError **error)
 {
-	const char *key_mgmt, *auth_alg, *tmp;
-	int n;
+	const char *key_mgmt, *auth_alg;
 
 	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec);
 	auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec);
 
-	if (key_mgmt) {
-		if (!strcmp (key_mgmt, "wpa-psk") || !strcmp (key_mgmt, "wpa-none")) {
-			if (s_8021x) {
-				g_set_error_literal (error,
-				                     NM_CONNECTION_ERROR,
-				                     NM_CONNECTION_ERROR_INVALID_SETTING,
-				                     _("WPA-PSK authentication is incompatible with 802.1x"));
-				g_prefix_error (error, "%s: ", NM_SETTING_802_1X_SETTING_NAME);
-				return FALSE;
-			}
+	if (!nm_streq0 (key_mgmt, "wpa-psk"))
+		return TRUE;
 
-			if (auth_alg && strcmp (auth_alg, "open")) {
-				/* WPA must use "open" authentication */
-				g_set_error_literal (error,
-				                     NM_CONNECTION_ERROR,
-				                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-				                     _("WPA-PSK requires 'open' authentication"));
-				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-				                NM_SETTING_WIRELESS_SECURITY_AUTH_ALG);
-				return FALSE;
-			}
-		}
+	if (s_8021x) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_SETTING,
+		                     _("WPA-PSK authentication is incompatible with 802.1x"));
+		g_prefix_error (error, "%s: ", NM_SETTING_802_1X_SETTING_NAME);
+		return FALSE;
+	}
 
-		if (!strcmp (key_mgmt, "wpa-none")) {
-			if (!adhoc) {
-				g_set_error_literal (error,
-				                     NM_CONNECTION_ERROR,
-				                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-				                     _("WPA Ad-Hoc authentication requires an Ad-Hoc mode AP"));
-				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME,
-				                NM_SETTING_WIRELESS_MODE);
-				return FALSE;
-			}
+	if (auth_alg && !nm_streq (auth_alg, "open")) {
+		/* WPA must use "open" authentication */
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("WPA-PSK requires 'open' authentication"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+		                NM_SETTING_WIRELESS_SECURITY_AUTH_ALG);
+		return FALSE;
+	}
 
-			/* Ad-Hoc WPA requires 'wpa' proto, 'none' pairwise, and 'tkip' group */
-			n = nm_setting_wireless_security_get_num_protos (s_wsec);
-			tmp = (n > 0) ? nm_setting_wireless_security_get_proto (s_wsec, 0) : NULL;
-			if (n > 1 || !tmp || strcmp (tmp, "wpa")) {
-				g_set_error_literal (error,
-				                     NM_CONNECTION_ERROR,
-				                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-				                     _("WPA Ad-Hoc authentication requires 'wpa' protocol"));
-				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-				                NM_SETTING_WIRELESS_SECURITY_PROTO);
-				return FALSE;
-			}
+	/* Make sure the AP's capabilities support WPA-PSK */
+	if (   !(wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
+	    && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("Access point does not support PSK but setting requires it"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+		                NM_SETTING_WIRELESS_SECURITY_KEY_MGMT);
+		return FALSE;
+	}
 
-			n = nm_setting_wireless_security_get_num_pairwise (s_wsec);
-			tmp = (n > 0) ? nm_setting_wireless_security_get_pairwise (s_wsec, 0) : NULL;
-			if (n > 1 || g_strcmp0 (tmp, "none")) {
-				g_set_error_literal (error,
-				                     NM_CONNECTION_ERROR,
-				                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-				                     _("WPA Ad-Hoc authentication requires 'none' pairwise cipher"));
-				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-				                NM_SETTING_WIRELESS_SECURITY_PAIRWISE);
-				return FALSE;
-			}
+	if (adhoc) {
+		/* Ad-Hoc RSN requires 'rsn' proto, 'ccmp' pairwise, and 'ccmp' group */
+		if (   nm_setting_wireless_security_get_num_protos (s_wsec) != 1
+		    || !nm_streq0 (nm_setting_wireless_security_get_proto (s_wsec, 0), "rsn")) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("WPA Ad-Hoc authentication requires 'rsn' protocol"));
+			g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+			                NM_SETTING_WIRELESS_SECURITY_PROTO);
+			return FALSE;
+		}
 
-			n = nm_setting_wireless_security_get_num_groups (s_wsec);
-			tmp = (n > 0) ? nm_setting_wireless_security_get_group (s_wsec, 0) : NULL;
-			if (n > 1 || !tmp || strcmp (tmp, "tkip")) {
-				g_set_error_literal (error,
-				                     NM_CONNECTION_ERROR,
-				                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-				                     _("WPA Ad-Hoc requires 'tkip' group cipher"));
-				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-				                NM_SETTING_WIRELESS_SECURITY_GROUP);
-				return FALSE;
-			}
+		if (   nm_setting_wireless_security_get_num_pairwise (s_wsec) != 1
+		    || !nm_streq0 (nm_setting_wireless_security_get_pairwise (s_wsec, 0), "ccmp")) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("WPA Ad-Hoc authentication requires 'ccmp' pairwise cipher"));
+			g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+			                NM_SETTING_WIRELESS_SECURITY_PAIRWISE);
+			return FALSE;
 		}
 
-		if (!strcmp (key_mgmt, "wpa-psk")) {
-			/* Make sure the AP's capabilities support WPA-PSK */
-			if (   !(wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
-			    && !(rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) {
-				g_set_error_literal (error,
-				                     NM_CONNECTION_ERROR,
-				                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-				                     _("Access point does not support PSK but setting requires it"));
-				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-				                NM_SETTING_WIRELESS_SECURITY_KEY_MGMT);
-				return FALSE;
-			}
+		if (   nm_setting_wireless_security_get_num_groups (s_wsec) != 1
+		    || !nm_streq0 (nm_setting_wireless_security_get_group (s_wsec, 0), "ccmp")) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			                     _("WPA Ad-Hoc requires 'ccmp' group cipher"));
+			g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+			                NM_SETTING_WIRELESS_SECURITY_GROUP);
+			return FALSE;
 		}
 	}
 
@@ -463,61 +432,52 @@ verify_adhoc (NMSettingWirelessSecurity *s_wsec,
 {
 	const char *key_mgmt = NULL, *leap_username = NULL, *auth_alg = NULL;
 
+	if (!adhoc)
+		return TRUE;
+
 	if (s_wsec) {
 		key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec);
 		auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec);
 		leap_username = nm_setting_wireless_security_get_leap_username (s_wsec);
 	}
 
-	if (adhoc) {
-		if (key_mgmt && strcmp (key_mgmt, "wpa-none") && strcmp (key_mgmt, "none")) {
-			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			                     _("Access point mode is Ad-Hoc but setting requires Infrastructure security"));
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-			                NM_SETTING_WIRELESS_SECURITY_KEY_MGMT);
-			return FALSE;
-		}
+	if (key_mgmt && !NM_IN_STRSET (key_mgmt, "none", "wpa-psk")) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("Ad-Hoc mode requires 'none' or 'wpa-psk' key management"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+		                NM_SETTING_WIRELESS_SECURITY_KEY_MGMT);
+		return FALSE;
+	}
 
-		if (s_8021x) {
-			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     NM_CONNECTION_ERROR_INVALID_SETTING,
-			                     _("Ad-Hoc mode is incompatible with 802.1x security"));
-			g_prefix_error (error, "%s: ", NM_SETTING_802_1X_SETTING_NAME);
-			return FALSE;
-		}
+	if (s_8021x) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_SETTING,
+		                     _("Ad-Hoc mode is incompatible with 802.1x security"));
+		g_prefix_error (error, "%s: ", NM_SETTING_802_1X_SETTING_NAME);
+		return FALSE;
+	}
 
-		if (leap_username) {
-			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			                     _("Ad-Hoc mode is incompatible with LEAP security"));
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-			                NM_SETTING_WIRELESS_SECURITY_AUTH_ALG);
-			return FALSE;
-		}
+	if (leap_username) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("Ad-Hoc mode is incompatible with LEAP security"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+		                NM_SETTING_WIRELESS_SECURITY_AUTH_ALG);
+		return FALSE;
+	}
 
-		if (auth_alg && strcmp (auth_alg, "open")) {
-			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			                     _("Ad-Hoc mode requires 'open' authentication"));
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-			                NM_SETTING_WIRELESS_SECURITY_AUTH_ALG);
-			return FALSE;
-		}
-	} else {
-		if (key_mgmt && !strcmp (key_mgmt, "wpa-none")) {
-			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			                     _("Access point mode is Infrastructure but setting requires Ad-Hoc security"));
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-			                NM_SETTING_WIRELESS_SECURITY_KEY_MGMT);
-			return FALSE;
-		}
+	if (auth_alg && !nm_streq (auth_alg, "open")) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+		                     _("Ad-Hoc mode requires 'open' authentication"));
+		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+		                NM_SETTING_WIRELESS_SECURITY_AUTH_ALG);
+		return FALSE;
 	}
 
 	return TRUE;
@@ -773,11 +733,13 @@ nm_wifi_utils_complete_connection (GBytes *ap_ssid,
 		return FALSE;
 
 	if (adhoc) {
-		g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-none", NULL);
-		/* Ad-Hoc does not support RSN/WPA2 */
-		nm_setting_wireless_security_add_proto (s_wsec, "wpa");
-		nm_setting_wireless_security_add_pairwise (s_wsec, "none");
-		nm_setting_wireless_security_add_group (s_wsec, "tkip");
+		g_object_set (s_wsec,
+		              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk",
+		              NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open",
+		              NULL);
+		nm_setting_wireless_security_add_proto (s_wsec, "rsn");
+		nm_setting_wireless_security_add_pairwise (s_wsec, "ccmp");
+		nm_setting_wireless_security_add_group (s_wsec, "ccmp");
 	} else if (s_8021x) {
 		g_object_set (s_wsec,
 		              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap",
diff --git a/src/devices/wifi/nm-wifi-utils.h b/src/devices/wifi/nm-wifi-utils.h
index 251d122f..982080b9 100644
--- a/src/devices/wifi/nm-wifi-utils.h
+++ b/src/devices/wifi/nm-wifi-utils.h
@@ -1,20 +1,6 @@
+// SPDX-License-Identifier: LGPL-2.1+
 /*
- * 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 2011 Red Hat, Inc.
+ * Copyright (C) 2011 Red Hat, Inc.
  */
 
 #ifndef __NM_WIFI_UTILS_H__
diff --git a/src/devices/wifi/tests/meson.build b/src/devices/wifi/tests/meson.build
deleted file mode 100644
index ba756d53..00000000
--- a/src/devices/wifi/tests/meson.build
+++ /dev/null
@@ -1,14 +0,0 @@
-test_unit = 'test-devices-wifi'
-
-exe = executable(
-  test_unit,
-  [test_unit + '.c'] + common_sources,
-  dependencies: test_nm_dep,
-)
-
-test(
-  test_unit,
-  test_script,
-  args: test_args + [exe.full_path()],
-  timeout: default_test_timeout,
-)
diff --git a/src/devices/wifi/tests/test-devices-wifi.c b/src/devices/wifi/tests/test-devices-wifi.c
index a0b3e17f..a960e7a2 100644
--- a/src/devices/wifi/tests/test-devices-wifi.c
+++ b/src/devices/wifi/tests/test-devices-wifi.c
@@ -1,20 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0+
 /*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
  * Copyright (C) 2011 Red Hat, Inc.
- *
  */
 
 #include "nm-default.h"
diff --git a/src/devices/wwan/libnm-wwan.ver b/src/devices/wwan/libnm-wwan.ver
index 7ccebcb5..c368a590 100644
--- a/src/devices/wwan/libnm-wwan.ver
+++ b/src/devices/wwan/libnm-wwan.ver
@@ -3,6 +3,7 @@ global:
 	nm_modem_act_stage1_prepare;
 	nm_modem_act_stage2_config;
 	nm_modem_check_connection_compatible;
+	nm_modem_claim;
 	nm_modem_complete_connection;
 	nm_modem_deactivate;
 	nm_modem_deactivate_async;
@@ -14,15 +15,17 @@ global:
 	nm_modem_get_device_id;
 	nm_modem_get_driver;
 	nm_modem_get_iid;
-	nm_modem_get_path;
 	nm_modem_get_ip_ifindex;
 	nm_modem_get_operator_code;
+	nm_modem_get_path;
 	nm_modem_get_secrets;
 	nm_modem_get_state;
 	nm_modem_get_type;
 	nm_modem_get_uid;
 	nm_modem_ip4_pre_commit;
+	nm_modem_is_claimed;
 	nm_modem_manager_get;
+	nm_modem_manager_get_modems;
 	nm_modem_manager_get_type;
 	nm_modem_manager_name_owner_get;
 	nm_modem_manager_name_owner_ref;
@@ -32,6 +35,7 @@ global:
 	nm_modem_stage3_ip4_config_start;
 	nm_modem_stage3_ip6_config_start;
 	nm_modem_state_to_string;
+	nm_modem_unclaim;
 local:
 	*;
 };
diff --git a/src/devices/wwan/meson.build b/src/devices/wwan/meson.build
index 482dc205..ed6f8010 100644
--- a/src/devices/wwan/meson.build
+++ b/src/devices/wwan/meson.build
@@ -1,13 +1,15 @@
-sources = files(
+nm_service_providers_source = files('nm-service-providers.c')
+
+sources = nm_service_providers_source + files(
   'nm-modem-broadband.c',
   'nm-modem.c',
   'nm-modem-manager.c',
 )
 
 deps = [
+  daemon_nm_default_dep,
   libsystemd_dep,
   mm_glib_dep,
-  nm_dep,
 ]
 
 if enable_ofono
@@ -20,16 +22,17 @@ libnm_wwan = shared_module(
   'nm-wwan',
   sources: sources,
   dependencies: deps,
-  link_args: [
-    '-Wl,--version-script,@0@'.format(linker_script),
-  ],
+  c_args: daemon_c_flags,
+  link_args: '-Wl,--version-script,@0@'.format(linker_script),
   link_depends: linker_script,
   install: true,
   install_dir: nm_plugindir,
 )
 
+wwan_inc = include_directories('.')
+
 libnm_wwan_dep = declare_dependency(
-  include_directories: include_directories('.'),
+  include_directories: wwan_inc,
   link_with: libnm_wwan,
 )
 
@@ -50,6 +53,7 @@ libnm_device_plugin_wwan = shared_module(
   'nm-device-plugin-wwan',
   sources: sources,
   dependencies: deps,
+  c_args: daemon_c_flags,
   link_with: libnm_wwan,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
@@ -66,11 +70,21 @@ run_target(
   depends: libnm_device_plugin_wwan,
 )
 
-# FIXME: check_so_symbols replacement
-'''
-check-local-devices-wwan: src/devices/wwan/libnm-device-plugin-wwan.la src/devices/wwan/libnm-wwan.la
-  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wwan/.libs/libnm-device-plugin-wwan.so "$(srcdir)/linker-script-devices.ver"
-  $(call check_so_symbols,$(builddir)/src/devices/wwan/.libs/libnm-device-plugin-wwan.so)
-  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wwan/.libs/libnm-wwan.so "$(srcdir)/src/devices/wwan/libnm-wwan.ver"
-  $(call check_so_symbols,$(builddir)/src/devices/wwan/.libs/libnm-wwan.so)
-'''
+if enable_tests
+  test_unit = 'test-service-providers'
+
+  exe = executable(
+    test_unit,
+    ['tests/' + test_unit + '.c'] + nm_service_providers_source,
+    include_directories: wwan_inc,
+    dependencies: libnetwork_manager_test_dep,
+    c_args: test_c_flags,
+  )
+
+  test(
+    'wwan/' + test_unit,
+    test_script,
+    timeout: default_test_timeout,
+    args: test_args + [exe.full_path()],
+  )
+endif
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index 042a6ca4..3e6ccc8f 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2009 - 2019 Red Hat, Inc.
  */
 
@@ -48,10 +34,11 @@ typedef struct {
 	NMModem *modem;
 	NMDeviceModemCapabilities caps;
 	NMDeviceModemCapabilities current_caps;
-	gboolean rf_enabled;
 	char *device_id;
 	char *operator_code;
 	char *apn;
+	bool rf_enabled:1;
+	NMDeviceStageState stage1_state:3;
 } NMDeviceModemPrivate;
 
 struct _NMDeviceModem {
@@ -119,16 +106,17 @@ modem_prepare_result (NMModem *modem,
                       gpointer user_data)
 {
 	NMDeviceModem *self = NM_DEVICE_MODEM (user_data);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
-	NMDeviceState state;
 	NMDeviceStateReason reason = i_reason;
 
-	state = nm_device_get_state (device);
-	g_return_if_fail (state == NM_DEVICE_STATE_PREPARE);
+	if (   nm_device_get_state (device) != NM_DEVICE_STATE_PREPARE
+	    || priv->stage1_state != NM_DEVICE_STAGE_STATE_PENDING) {
+		nm_assert_not_reached ();
+		success = FALSE;
+	}
 
-	if (success)
-		nm_device_activate_schedule_stage2_device_config (device);
-	else {
+	if (!success) {
 		/* There are several reasons to block autoconnection at device level:
 		 *
 		 *  - Wrong SIM-PIN: The device won't autoconnect because it doesn't make sense
@@ -164,7 +152,11 @@ modem_prepare_result (NMModem *modem,
 			break;
 		}
 		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
+		return;
 	}
+
+	priv->stage1_state = NM_DEVICE_STAGE_STATE_COMPLETED;
+	nm_device_activate_schedule_stage1_device_prepare (device);
 }
 
 static void
@@ -187,16 +179,19 @@ static void
 modem_auth_result (NMModem *modem, GError *error, gpointer user_data)
 {
 	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device);
+
+	g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
 
 	if (error) {
 		nm_device_state_changed (device,
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
-	} else {
-		/* Otherwise, on success for modem secrets we need to schedule stage1 again */
-		g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
-		nm_device_activate_schedule_stage1_device_prepare (device);
+		return;
 	}
+
+	priv->stage1_state = NM_DEVICE_STAGE_STATE_INIT;
+	nm_device_activate_schedule_stage1_device_prepare (device);
 }
 
 static void
@@ -355,7 +350,7 @@ modem_state_cb (NMModem *modem,
 	NMModemState new_state = new_state_i;
 	NMModemState old_state = old_state_i;
 	NMDevice *device = NM_DEVICE (user_data);
-	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device);
 	NMDeviceState dev_state = nm_device_get_state (device);
 
 	if (new_state <= NM_MODEM_STATE_DISABLING &&
@@ -409,7 +404,7 @@ modem_removed_cb (NMModem *modem, gpointer user_data)
 static gboolean
 owns_iface (NMDevice *device, const char *iface)
 {
-	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device);
 
 	g_return_val_if_fail (priv->modem, FALSE);
 
@@ -447,7 +442,7 @@ get_generic_capabilities (NMDevice *device)
 static const char *
 get_type_description (NMDevice *device)
 {
-	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device);
 
 	if (NM_FLAGS_HAS (priv->current_caps, NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS))
 		return "gsm";
@@ -464,7 +459,7 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 	if (!NM_DEVICE_CLASS (nm_device_modem_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
-	if (!nm_modem_check_connection_compatible (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem,
+	if (!nm_modem_check_connection_compatible (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem,
 	                                           connection,
 	                                           error ? &local : NULL)) {
 		if (error) {
@@ -530,7 +525,7 @@ complete_connection (NMDevice *device,
                      NMConnection *const*existing_connections,
                      GError **error)
 {
-	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device);
 
 	return nm_modem_complete_connection (priv->modem,
 	                                     nm_device_get_iface (device),
@@ -542,7 +537,10 @@ complete_connection (NMDevice *device,
 static void
 deactivate (NMDevice *device)
 {
-	nm_modem_deactivate (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem, device);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device);
+
+	nm_modem_deactivate (priv->modem, device);
+	priv->stage1_state = NM_DEVICE_STAGE_STATE_INIT;
 }
 
 /*****************************************************************************/
@@ -583,28 +581,32 @@ deactivate_async (NMDevice *self,
 static NMActStageReturn
 act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-	NMActStageReturn ret;
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device);
 	NMActRequest *req;
 
-	ret = NM_DEVICE_CLASS (nm_device_modem_parent_class)->act_stage1_prepare (device, out_failure_reason);
-	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
-		return ret;
-
 	req = nm_device_get_act_request (device);
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
 
-	return nm_modem_act_stage1_prepare (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem, req, out_failure_reason);
+	if (priv->stage1_state == NM_DEVICE_STAGE_STATE_INIT) {
+		priv->stage1_state = NM_DEVICE_STAGE_STATE_PENDING;
+		return nm_modem_act_stage1_prepare (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem,
+		                                    req,
+		                                    out_failure_reason);
+	}
+
+	if (priv->stage1_state == NM_DEVICE_STAGE_STATE_PENDING)
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+
+	nm_assert (priv->stage1_state == NM_DEVICE_STAGE_STATE_COMPLETED);
+	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
 static NMActStageReturn
 act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-	NMActRequest *req;
-
-	req = nm_device_get_act_request (device);
-	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
+	nm_modem_act_stage2_config (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem);
 
-	return nm_modem_act_stage2_config (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem, req, out_failure_reason);
+	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
 static NMActStageReturn
@@ -632,7 +634,7 @@ act_stage3_ip_config_start (NMDevice *device,
 static void
 ip4_config_pre_commit (NMDevice *device, NMIP4Config *config)
 {
-	nm_modem_ip4_pre_commit (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem, device, config);
+	nm_modem_ip4_pre_commit (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem, device, config);
 }
 
 static gboolean
@@ -654,7 +656,7 @@ get_ip_iface_identifier (NMDevice *device, NMUtilsIPv6IfaceId *out_iid)
 static gboolean
 get_enabled (NMDevice *device)
 {
-	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device);
 	NMModemState modem_state = nm_modem_get_state (priv->modem);
 
 	return priv->rf_enabled && (modem_state >= NM_MODEM_STATE_LOCKED);
@@ -710,7 +712,7 @@ set_modem (NMDeviceModem *self, NMModem *modem)
 
 	g_return_if_fail (modem != NULL);
 
-	priv->modem = g_object_ref (modem);
+	priv->modem = nm_modem_claim (modem);
 
 	g_signal_connect (modem, NM_MODEM_PPP_FAILED, G_CALLBACK (ppp_failed), self);
 	g_signal_connect (modem, NM_MODEM_PREPARE_RESULT, G_CALLBACK (modem_prepare_result), self);
@@ -745,7 +747,7 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) object);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_MODEM:
@@ -776,7 +778,7 @@ static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
-	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) object);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_MODEM:
@@ -834,11 +836,11 @@ nm_device_modem_new (NMModem *modem)
 static void
 dispose (GObject *object)
 {
-	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) object);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (object);
 
 	if (priv->modem) {
 		g_signal_handlers_disconnect_by_data (priv->modem, NM_DEVICE_MODEM (object));
-		g_clear_object (&priv->modem);
+		nm_clear_pointer (&priv->modem, nm_modem_unclaim);
 	}
 
 	g_clear_pointer (&priv->device_id, g_free);
diff --git a/src/devices/wwan/nm-device-modem.h b/src/devices/wwan/nm-device-modem.h
index 0a557f57..842096e2 100644
--- a/src/devices/wwan/nm-device-modem.h
+++ b/src/devices/wwan/nm-device-modem.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2011 Red Hat, Inc.
  */
 
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 216fedfe..a1ae8671 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -1,25 +1,12 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2012 Aleksander Morgado <aleksander@gnu.org>
  */
 
 #include "nm-default.h"
 
 #include "nm-modem-broadband.h"
+#include "nm-service-providers.h"
 
 #include <arpa/inet.h>
 #include <libmm-glib.h>
@@ -274,7 +261,10 @@ create_cdma_connect_properties (NMConnection *connection)
 }
 
 static MMSimpleConnectProperties *
-create_gsm_connect_properties (NMConnection *connection)
+create_gsm_connect_properties (NMConnection *connection,
+                               const char *apn,
+                               const char *username,
+                               const char *password)
 {
 	NMSettingGsm *setting;
 	NMSettingPpp *s_ppp;
@@ -282,11 +272,14 @@ create_gsm_connect_properties (NMConnection *connection)
 	const char *str;
 
 	setting = nm_connection_get_setting_gsm (connection);
+
 	properties = mm_simple_connect_properties_new ();
 
-	/* Blank APN ("") means the default subscription APN */
-	str = nm_setting_gsm_get_apn (setting);
-	mm_simple_connect_properties_set_apn (properties, str ?: "");
+	mm_simple_connect_properties_set_apn (properties, apn ?: "");
+	if (username)
+		mm_simple_connect_properties_set_user (properties, username);
+	if (password)
+		mm_simple_connect_properties_set_password (properties, password);
 
 	str = nm_setting_gsm_get_network_id (setting);
 	if (str)
@@ -296,14 +289,6 @@ create_gsm_connect_properties (NMConnection *connection)
 	if (str)
 		mm_simple_connect_properties_set_pin (properties, str);
 
-	str = nm_setting_gsm_get_username (setting);
-	if (str)
-		mm_simple_connect_properties_set_user (properties, str);
-
-	str = nm_setting_gsm_get_password (setting);
-	if (str)
-		mm_simple_connect_properties_set_password (properties, str);
-
 	/* Roaming */
 	if (nm_setting_gsm_get_home_only (setting))
 		mm_simple_connect_properties_set_allow_roaming (properties, FALSE);
@@ -356,16 +341,26 @@ connect_ready (MMModemSimple *simple_iface,
                GAsyncResult *res,
                NMModemBroadband *self)
 {
-	ConnectContext *ctx = self->_priv.ctx;
+	ConnectContext *ctx;
 	GError *error = NULL;
 	NMModemIPMethod ip4_method = NM_MODEM_IP_METHOD_UNKNOWN;
 	NMModemIPMethod ip6_method = NM_MODEM_IP_METHOD_UNKNOWN;
+	MMBearer *bearer;
 
-	self->_priv.bearer = mm_modem_simple_connect_finish (simple_iface, res, &error);
+	bearer = mm_modem_simple_connect_finish (simple_iface, res, &error);
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_error_free (error);
+		return;
+	}
+
+	ctx = self->_priv.ctx;
 
 	if (!ctx)
 		return;
 
+	self->_priv.bearer = bearer;
+
 	if (!self->_priv.bearer) {
 		if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN) ||
 		    (g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNAUTHORIZED) &&
@@ -457,6 +452,95 @@ send_pin_ready (MMSim *sim, GAsyncResult *result, NMModemBroadband *self)
 }
 
 static void
+find_gsm_apn_cb (const char *apn,
+                 const char *username,
+                 const char *password,
+                 const char *gateway,
+                 const char *auth_method,
+                 const GSList *dns,
+                 GError *error,
+                 gpointer user_data)
+{
+	NMModemBroadband *self = user_data;
+	NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE (self);
+	ConnectContext *ctx = priv->ctx;
+
+	if (error) {
+		_LOGW ("failed to connect '%s': APN not found: %s",
+		       nm_connection_get_id (ctx->connection), error->message);
+
+		nm_modem_emit_prepare_result (NM_MODEM (self), FALSE, NM_DEVICE_STATE_REASON_GSM_APN_FAILED);
+		connect_context_clear (self);
+		return;
+	}
+
+	/* Blank APN ("") means the default subscription APN */
+	ctx->connect_properties = create_gsm_connect_properties (ctx->connection,
+	                                                         apn,
+	                                                         username,
+	                                                         password);
+	g_return_if_fail (ctx->connect_properties);
+	connect_context_step (self);
+}
+
+static gboolean
+try_create_connect_properties (NMModemBroadband *self)
+{
+	NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE (self);
+	ConnectContext *ctx = priv->ctx;
+
+	if (MODEM_CAPS_3GPP (ctx->caps)) {
+		NMSettingGsm *s_gsm = nm_connection_get_setting_gsm (ctx->connection);
+
+		if (!s_gsm || nm_setting_gsm_get_auto_config (s_gsm)) {
+			gs_unref_object MMModem3gpp *modem_3gpp = NULL;
+			const char *network_id = NULL;
+
+			s_gsm = nm_connection_get_setting_gsm (ctx->connection);
+			if (s_gsm)
+				network_id = nm_setting_gsm_get_network_id (s_gsm);
+			if (!network_id) {
+				if (mm_modem_get_state (self->_priv.modem_iface) < MM_MODEM_STATE_REGISTERED)
+					return FALSE;
+				modem_3gpp = mm_object_get_modem_3gpp (priv->modem_object);
+				network_id = mm_modem_3gpp_get_operator_code (modem_3gpp);
+			}
+			if (!network_id) {
+				_LOGW ("failed to connect '%s': unable to determine the network id",
+				       nm_connection_get_id (ctx->connection));
+				goto out;
+			}
+
+			nm_service_providers_find_gsm_apn (MOBILE_BROADBAND_PROVIDER_INFO_DATABASE,
+			                                   network_id,
+			                                   ctx->cancellable,
+			                                   find_gsm_apn_cb,
+			                                   self);
+		} else {
+			ctx->connect_properties = create_gsm_connect_properties (ctx->connection,
+			                                                         nm_setting_gsm_get_apn (s_gsm),
+			                                                         nm_setting_gsm_get_username (s_gsm),
+			                                                         nm_setting_gsm_get_password (s_gsm));
+			g_return_val_if_fail (ctx->connect_properties, TRUE);
+		}
+
+		return TRUE;
+	} else if (MODEM_CAPS_3GPP2 (ctx->caps)) {
+		ctx->connect_properties = create_cdma_connect_properties (ctx->connection);
+		g_return_val_if_fail (ctx->connect_properties, FALSE);
+		return TRUE;
+	} else {
+		_LOGW ("failed to connect '%s': not a mobile broadband modem",
+		       nm_connection_get_id (ctx->connection));
+	}
+
+out:
+	nm_modem_emit_prepare_result (NM_MODEM (self), FALSE, NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED);
+	connect_context_clear (self);
+	return TRUE;
+}
+
+static void
 connect_context_step (NMModemBroadband *self)
 {
 	ConnectContext *ctx = self->_priv.ctx;
@@ -500,22 +584,11 @@ connect_context_step (NMModemBroadband *self)
 		if (mm_modem_get_state (self->_priv.modem_iface) <= MM_MODEM_STATE_LOCKED)
 			break;
 
-		/* Create core connect properties based on the modem capabilities */
-		g_assert (!ctx->connect_properties);
-
-		if (MODEM_CAPS_3GPP (ctx->caps))
-			ctx->connect_properties = create_gsm_connect_properties (ctx->connection);
-		else if (MODEM_CAPS_3GPP2 (ctx->caps))
-			ctx->connect_properties = create_cdma_connect_properties (ctx->connection);
-		else {
-			_LOGW ("failed to connect '%s': not a mobile broadband modem",
-			       nm_connection_get_id (ctx->connection));
+		if (!try_create_connect_properties (self))
+			break;
 
-			nm_modem_emit_prepare_result (NM_MODEM (self), FALSE, NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED);
-			connect_context_clear (self);
+		if (!self->_priv.ctx)
 			break;
-		}
-		g_assert (ctx->connect_properties);
 
 		/* Build up list of IP types that we need to use in the retries */
 		ctx->ip_types = nm_modem_get_connection_ip_type (NM_MODEM (self), ctx->connection, &error);
@@ -534,6 +607,9 @@ connect_context_step (NMModemBroadband *self)
 	}
 		/* fall through */
 	case CONNECT_STEP_CONNECT:
+		if (!ctx->connect_properties)
+			break;
+
 		if (ctx->ip_types_i < ctx->ip_types->len) {
 			NMModemIPType current;
 
@@ -546,7 +622,7 @@ connect_context_step (NMModemBroadband *self)
 			else if (current == NM_MODEM_IP_TYPE_IPV4V6)
 				mm_simple_connect_properties_set_ip_type (ctx->connect_properties, MM_BEARER_IP_FAMILY_IPV4V6);
 			else
-				g_assert_not_reached ();
+				g_return_if_reached ();
 
 			_nm_modem_set_apn (NM_MODEM (self), mm_simple_connect_properties_get_apn (ctx->connect_properties));
 
@@ -556,7 +632,7 @@ connect_context_step (NMModemBroadband *self)
 
 			mm_modem_simple_connect (self->_priv.simple_iface,
 			                         ctx->connect_properties,
-			                         NULL,
+			                         ctx->cancellable,
 			                         (GAsyncReadyCallback) connect_ready,
 			                         self);
 			break;
@@ -586,9 +662,9 @@ connect_context_step (NMModemBroadband *self)
 }
 
 static NMActStageReturn
-act_stage1_prepare (NMModem *_self,
-                    NMConnection *connection,
-                    NMDeviceStateReason *out_failure_reason)
+modem_act_stage1_prepare (NMModem *_self,
+                          NMConnection *connection,
+                          NMDeviceStateReason *out_failure_reason)
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
 
@@ -689,6 +765,9 @@ complete_connection (NMModem *modem,
 		if (!s_gsm) {
 			s_gsm = (NMSettingGsm *) nm_setting_gsm_new ();
 			nm_connection_add_setting (connection, NM_SETTING (s_gsm));
+			g_object_set (G_OBJECT (s_gsm),
+			              NM_SETTING_GSM_AUTO_CONFIG, TRUE,
+			              NULL);
 		}
 
 		if (!nm_setting_gsm_get_device_id (s_gsm)) {
@@ -876,8 +955,8 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	guint32 ip4_route_table, ip4_route_metric;
 	NMPlatformIP4Route *r;
 
-	g_assert (self->_priv.ipv4_config);
-	g_assert (self->_priv.bearer);
+	g_return_val_if_fail (self->_priv.ipv4_config, FALSE);
+	g_return_val_if_fail (self->_priv.bearer, FALSE);
 
 	self->_priv.idle_id_ip4 = 0;
 
@@ -908,7 +987,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	}
 
 	data_port = mm_bearer_get_interface (self->_priv.bearer);
-	g_assert (data_port);
+	g_return_val_if_fail (data_port, FALSE);
 	config = nm_ip4_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET),
 	                            nm_platform_link_get_ifindex (NM_PLATFORM_GET, data_port));
 
@@ -983,7 +1062,7 @@ stage3_ip6_done (NMModemBroadband *self)
 	const char **dns;
 	guint i;
 
-	g_assert (self->_priv.ipv6_config);
+	g_return_val_if_fail (self->_priv.ipv6_config, FALSE);
 
 	self->_priv.idle_id_ip6 = 0;
 	memset (&address, 0, sizeof (address));
@@ -1015,7 +1094,8 @@ stage3_ip6_done (NMModemBroadband *self)
 	_LOGI ("IPv6 base configuration:");
 
 	data_port = mm_bearer_get_interface (self->_priv.bearer);
-	g_assert (data_port);
+	g_return_val_if_fail (data_port, FALSE);
+
 	config = nm_ip6_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET),
 	                            nm_platform_link_get_ifindex (NM_PLATFORM_GET, data_port));
 
@@ -1383,8 +1463,9 @@ set_property (GObject *object,
 		/* construct-only */
 		self->_priv.modem_object = g_value_dup_object (value);
 		self->_priv.modem_iface = mm_object_get_modem (self->_priv.modem_object);
+		g_return_if_fail (self->_priv.modem_iface);
 		self->_priv.modem_3gpp_iface = mm_object_get_modem_3gpp (self->_priv.modem_object);
-		g_assert (self->_priv.modem_iface != NULL);
+
 		g_signal_connect (self->_priv.modem_iface,
 		                  "state-changed",
 		                  G_CALLBACK (modem_state_changed),
@@ -1512,7 +1593,7 @@ nm_modem_broadband_class_init (NMModemBroadbandClass *klass)
 	modem_class->get_user_pass = get_user_pass;
 	modem_class->check_connection_compatible_with_modem = check_connection_compatible_with_modem;
 	modem_class->complete_connection = complete_connection;
-	modem_class->act_stage1_prepare = act_stage1_prepare;
+	modem_class->modem_act_stage1_prepare = modem_act_stage1_prepare;
 	modem_class->owns_port = owns_port;
 
 	obj_properties[PROP_MODEM] =
diff --git a/src/devices/wwan/nm-modem-broadband.h b/src/devices/wwan/nm-modem-broadband.h
index 9404f0b9..43041e2b 100644
--- a/src/devices/wwan/nm-modem-broadband.h
+++ b/src/devices/wwan/nm-modem-broadband.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2012 - Aleksander Morgado <aleksander@gnu.org>
  */
 
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index db2c0192..daccf68a 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2009 - 2014 Red Hat, Inc.
  * Copyright (C) 2009 Novell, Inc.
  * Copyright (C) 2009 - 2013 Canonical Ltd.
@@ -149,6 +135,20 @@ remove_one_modem (gpointer key, gpointer value, gpointer user_data)
 
 /*****************************************************************************/
 
+NMModem **
+nm_modem_manager_get_modems (NMModemManager *self,
+                             guint *out_len)
+{
+	g_return_val_if_fail (NM_IS_MODEM_MANAGER (self), NULL);
+
+	return (NMModem **) nm_utils_hash_values_to_array (NM_MODEM_MANAGER_GET_PRIVATE (self)->modems,
+	                                                   NULL,
+	                                                   NULL,
+	                                                   out_len);
+}
+
+/*****************************************************************************/
+
 static void
 modm_clear_manager (NMModemManager *self)
 {
diff --git a/src/devices/wwan/nm-modem-manager.h b/src/devices/wwan/nm-modem-manager.h
index 1a26fd9f..97a2126a 100644
--- a/src/devices/wwan/nm-modem-manager.h
+++ b/src/devices/wwan/nm-modem-manager.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2009 - 2014 Red Hat, Inc.
  * Copyright (C) 2009 Novell, Inc.
  * Copyright (C) 2009 Canonical Ltd.
@@ -51,4 +37,7 @@ void nm_modem_manager_name_owner_unref (NMModemManager *self);
 
 const char *nm_modem_manager_name_owner_get (NMModemManager *self);
 
+NMModem **nm_modem_manager_get_modems (NMModemManager *self,
+                                       guint *out_len);
+
 #endif /* __NETWORKMANAGER_MODEM_MANAGER_H__ */
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
index 31111b62..b68cd7e8 100644
--- a/src/devices/wwan/nm-modem-ofono.c
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2013 - 2016 Canonical Ltd.
  */
 
@@ -1114,9 +1100,9 @@ create_connect_properties (NMConnection *connection)
 }
 
 static NMActStageReturn
-act_stage1_prepare (NMModem *modem,
-                    NMConnection *connection,
-                    NMDeviceStateReason *out_failure_reason)
+modem_act_stage1_prepare (NMModem *modem,
+                          NMConnection *connection,
+                          NMDeviceStateReason *out_failure_reason)
 {
 	NMModemOfono *self = NM_MODEM_OFONO (modem);
 	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
@@ -1308,6 +1294,6 @@ nm_modem_ofono_class_init (NMModemOfonoClass *klass)
 	modem_class->deactivate_cleanup = deactivate_cleanup;
 	modem_class->check_connection_compatible_with_modem = check_connection_compatible_with_modem;
 
-	modem_class->act_stage1_prepare = act_stage1_prepare;
+	modem_class->modem_act_stage1_prepare = modem_act_stage1_prepare;
 	modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start;
 }
diff --git a/src/devices/wwan/nm-modem-ofono.h b/src/devices/wwan/nm-modem-ofono.h
index 1dcd79b0..4ff2fbdd 100644
--- a/src/devices/wwan/nm-modem-ofono.h
+++ b/src/devices/wwan/nm-modem-ofono.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2013 - Canonical Ltd.
  */
 
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index fb2316d5..ad6449b1 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2009 - 2014 Red Hat, Inc.
  * Copyright (C) 2009 Novell, Inc.
  */
@@ -110,6 +96,8 @@ typedef struct _NMModemPrivate {
 	/* PPP stats */
 	guint32 in_bytes;
 	guint32 out_bytes;
+
+	bool claimed:1;
 } NMModemPrivate;
 
 G_DEFINE_TYPE (NMModem, nm_modem, G_TYPE_OBJECT)
@@ -187,6 +175,53 @@ nm_modem_state_to_string (NMModemState state)
 	return NULL;
 }
 
+/*****************************************************************************/
+
+gboolean
+nm_modem_is_claimed (NMModem *self)
+{
+	g_return_val_if_fail (NM_IS_MODEM (self), FALSE);
+
+	return NM_MODEM_GET_PRIVATE (self)->claimed;
+}
+
+NMModem *
+nm_modem_claim (NMModem *self)
+{
+	NMModemPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_MODEM (self), NULL);
+
+	priv = NM_MODEM_GET_PRIVATE (self);
+
+	g_return_val_if_fail (!priv->claimed, NULL);
+
+	priv->claimed = TRUE;
+	return g_object_ref (self);
+}
+
+void
+nm_modem_unclaim (NMModem *self)
+{
+	NMModemPrivate *priv;
+
+	g_return_if_fail (NM_IS_MODEM (self));
+
+	priv = NM_MODEM_GET_PRIVATE (self);
+
+	g_return_if_fail (priv->claimed);
+
+	/* we don't actually unclaim the instance. This instance should not be re-used
+	 * by another owner, that is because we only claim modems as we receive them.
+	 * There is no mechanism that somebody else would later re-use them again.
+	 *
+	 * // priv->claimed = FALSE; */
+
+	g_object_unref (self);
+}
+
+/*****************************************************************************/
+
 NMModemState
 nm_modem_get_state (NMModem *self)
 {
@@ -976,9 +1011,9 @@ nm_modem_get_secrets (NMModem *self,
 /*****************************************************************************/
 
 static NMActStageReturn
-act_stage1_prepare (NMModem *modem,
-                    NMConnection *connection,
-                    NMDeviceStateReason *out_failure_reason)
+modem_act_stage1_prepare (NMModem *modem,
+                          NMConnection *connection,
+                          NMDeviceStateReason *out_failure_reason)
 {
 	NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_UNKNOWN);
 	return NM_ACT_STAGE_RETURN_FAILURE;
@@ -995,6 +1030,8 @@ nm_modem_act_stage1_prepare (NMModem *self,
 	NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
 	NMConnection *connection;
 
+	g_return_val_if_fail (NM_IS_ACT_REQUEST (req), NM_ACT_STAGE_RETURN_FAILURE);
+
 	if (priv->act_request)
 		g_object_unref (priv->act_request);
 	priv->act_request = g_object_ref (req);
@@ -1005,7 +1042,7 @@ nm_modem_act_stage1_prepare (NMModem *self,
 	setting_name = nm_connection_need_secrets (connection, &hints);
 	if (!setting_name) {
 		nm_assert (!hints);
-		return NM_MODEM_GET_CLASS (self)->act_stage1_prepare (self, connection, out_failure_reason);
+		return NM_MODEM_GET_CLASS (self)->modem_act_stage1_prepare (self, connection, out_failure_reason);
 	}
 
 	/* Secrets required... */
@@ -1029,19 +1066,18 @@ nm_modem_act_stage1_prepare (NMModem *self,
 
 /*****************************************************************************/
 
-NMActStageReturn
-nm_modem_act_stage2_config (NMModem *self,
-                            NMActRequest *req,
-                            NMDeviceStateReason *out_failure_reason)
+void
+nm_modem_act_stage2_config (NMModem *self)
 {
-	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
+	NMModemPrivate *priv;
+
+	g_return_if_fail (NM_IS_MODEM (self));
 
+	priv = NM_MODEM_GET_PRIVATE (self);
 	/* Clear secrets tries counter since secrets were successfully used
 	 * already if we get here.
 	 */
 	priv->secrets_tries = 0;
-
-	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
 /*****************************************************************************/
@@ -1800,7 +1836,7 @@ nm_modem_class_init (NMModemClass *klass)
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
 
-	klass->act_stage1_prepare = act_stage1_prepare;
+	klass->modem_act_stage1_prepare = modem_act_stage1_prepare;
 	klass->stage3_ip6_config_request = stage3_ip6_config_request;
 	klass->deactivate_cleanup = deactivate_cleanup;
 
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index fccb4fec..f2de990b 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2009 - 2011 Red Hat, Inc.
  * Copyright (C) 2009 Novell, Inc.
  */
@@ -136,9 +122,9 @@ typedef struct {
 	                                            NMConnection *const*existing_connections,
 	                                            GError **error);
 
-	NMActStageReturn (*act_stage1_prepare)     (NMModem *modem,
-	                                            NMConnection *connection,
-	                                            NMDeviceStateReason *out_failure_reason);
+	NMActStageReturn (*modem_act_stage1_prepare) (NMModem *modem,
+	                                              NMConnection *connection,
+	                                              NMDeviceStateReason *out_failure_reason);
 
 	NMActStageReturn (*static_stage3_ip4_config_start) (NMModem *self,
 	                                                    NMActRequest *req,
@@ -167,6 +153,10 @@ typedef struct {
 
 GType nm_modem_get_type (void);
 
+gboolean nm_modem_is_claimed (NMModem *modem);
+NMModem *nm_modem_claim (NMModem *modem);
+void nm_modem_unclaim (NMModem *modem);
+
 const char *nm_modem_get_path            (NMModem *modem);
 const char *nm_modem_get_uid             (NMModem *modem);
 const char *nm_modem_get_control_port    (NMModem *modem);
@@ -222,9 +212,7 @@ NMActStageReturn nm_modem_act_stage1_prepare (NMModem *modem,
                                               NMActRequest *req,
                                               NMDeviceStateReason *out_failure_reason);
 
-NMActStageReturn nm_modem_act_stage2_config (NMModem *modem,
-                                             NMActRequest *req,
-                                             NMDeviceStateReason *out_failure_reason);
+void nm_modem_act_stage2_config (NMModem *modem);
 
 NMActStageReturn nm_modem_stage3_ip4_config_start (NMModem *modem,
                                                    NMDevice *device,
diff --git a/src/devices/wwan/nm-service-providers.c b/src/devices/wwan/nm-service-providers.c
new file mode 100644
index 00000000..5140f7ed
--- /dev/null
+++ b/src/devices/wwan/nm-service-providers.c
@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2009 Novell, Inc.
+ * Author: Tambet Ingo (tambet@gmail.com).
+ * Copyright (C) 2009 - 2019 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ */
+
+#include "nm-default.h"
+
+#include "nm-service-providers.h"
+
+typedef enum {
+	PARSER_TOPLEVEL = 0,
+	PARSER_COUNTRY,
+	PARSER_PROVIDER,
+	PARSER_METHOD_GSM,
+	PARSER_METHOD_GSM_APN,
+	PARSER_METHOD_CDMA,
+	PARSER_DONE,
+	PARSER_ERROR
+} ParseContextState;
+
+typedef struct {
+	char *mccmnc;
+	NMServiceProvidersGsmApnCallback callback;
+	gpointer user_data;
+	GCancellable *cancellable;
+	GMarkupParseContext *ctx;
+	char buffer[4096];
+
+	char *text_buffer;
+	ParseContextState state;
+
+	gboolean mccmnc_matched;
+	gboolean found_internet_apn;
+	char *apn;
+	char *username;
+	char *password;
+	char *gateway;
+	char *auth_method;
+	GSList *dns;
+} ParseContext;
+
+/*****************************************************************************/
+
+static void
+parser_toplevel_start (ParseContext *parse_context,
+                       const char *name,
+                       const char **attribute_names,
+                       const char **attribute_values)
+{
+	int i;
+
+	if (strcmp (name, "serviceproviders") == 0) {
+		for (i = 0; attribute_names && attribute_names[i]; i++) {
+			if (strcmp (attribute_names[i], "format") == 0) {
+				if (strcmp (attribute_values[i], "2.0")) {
+					g_warning ("%s: mobile broadband provider database format '%s'"
+					           " not supported.", __func__, attribute_values[i]);
+					parse_context->state = PARSER_ERROR;
+					break;
+				}
+			}
+		}
+	} else if (strcmp (name, "country") == 0) {
+		parse_context->state = PARSER_COUNTRY;
+	}
+}
+
+static void
+parser_country_start (ParseContext *parse_context,
+                      const char *name,
+                      const char **attribute_names,
+                      const char **attribute_values)
+{
+	if (strcmp (name, "provider") == 0)
+		parse_context->state = PARSER_PROVIDER;
+}
+
+static void
+parser_provider_start (ParseContext *parse_context,
+                       const char *name,
+                       const char **attribute_names,
+                       const char **attribute_values)
+{
+	parse_context->mccmnc_matched = FALSE;
+	if (strcmp (name, "gsm") == 0)
+		parse_context->state = PARSER_METHOD_GSM;
+	else if (strcmp (name, "cdma") == 0)
+		parse_context->state = PARSER_METHOD_CDMA;
+}
+
+static void
+parser_gsm_start (ParseContext *parse_context,
+                  const char *name,
+                  const char **attribute_names,
+                  const char **attribute_values)
+{
+	int i;
+
+	if (strcmp (name, "network-id") == 0) {
+		const char *mcc = NULL, *mnc = NULL;
+
+		for (i = 0; attribute_names && attribute_names[i]; i++) {
+			if (strcmp (attribute_names[i], "mcc") == 0)
+				mcc = attribute_values[i];
+			else if (strcmp (attribute_names[i], "mnc") == 0)
+				mnc = attribute_values[i];
+			if (mcc && strlen (mcc) && mnc && strlen (mnc)) {
+				char *mccmnc = g_strdup_printf ("%s%s", mcc, mnc);
+
+				if (strcmp (mccmnc, parse_context->mccmnc) == 0)
+					parse_context->mccmnc_matched = TRUE;
+				g_free (mccmnc);
+				break;
+			}
+		}
+	} else if (strcmp (name, "apn") == 0) {
+		parse_context->found_internet_apn = FALSE;
+		g_clear_pointer (&parse_context->apn, g_free);
+		g_clear_pointer (&parse_context->username, g_free);
+		g_clear_pointer (&parse_context->password, g_free);
+		g_clear_pointer (&parse_context->gateway, g_free);
+		g_clear_pointer (&parse_context->auth_method, g_free);
+		g_slist_free_full (parse_context->dns, g_free);
+		parse_context->dns = NULL;
+
+		for (i = 0; attribute_names && attribute_names[i]; i++) {
+			if (strcmp (attribute_names[i], "value") == 0) {
+				parse_context->state = PARSER_METHOD_GSM_APN;
+				parse_context->apn = g_strstrip (g_strdup (attribute_values[i]));
+				break;
+			}
+		}
+	}
+}
+
+static void
+parser_gsm_apn_start (ParseContext *parse_context,
+                  const char *name,
+                  const char **attribute_names,
+                  const char **attribute_values)
+{
+	int i;
+
+	if (strcmp (name, "usage") == 0) {
+		for (i = 0; attribute_names && attribute_names[i]; i++) {
+			if (   (strcmp (attribute_names[i], "type") == 0)
+			    && (strcmp (attribute_values[i], "internet") == 0)) {
+				parse_context->found_internet_apn = TRUE;
+				break;
+			}
+		}
+	} else if (strcmp (name, "authentication") == 0) {
+		for (i = 0; attribute_names && attribute_names[i]; i++) {
+			if (strcmp (attribute_names[i], "method") == 0) {
+				g_clear_pointer (&parse_context->auth_method, g_free);
+				parse_context->auth_method = g_strstrip (g_strdup (attribute_values[i]));
+				break;
+			}
+		}
+	}
+}
+
+static void
+parser_start_element (GMarkupParseContext *context,
+                             const char *element_name,
+                             const char **attribute_names,
+                             const char **attribute_values,
+                             gpointer user_data,
+                             GError **error)
+{
+	ParseContext *parse_context = user_data;
+
+	g_clear_pointer (&parse_context->text_buffer, g_free);
+
+	switch (parse_context->state) {
+	case PARSER_TOPLEVEL:
+		parser_toplevel_start (parse_context, element_name, attribute_names, attribute_values);
+		break;
+	case PARSER_COUNTRY:
+		parser_country_start (parse_context, element_name, attribute_names, attribute_values);
+		break;
+	case PARSER_PROVIDER:
+		parser_provider_start (parse_context, element_name, attribute_names, attribute_values);
+		break;
+	case PARSER_METHOD_GSM:
+		parser_gsm_start (parse_context, element_name, attribute_names, attribute_values);
+		break;
+	case PARSER_METHOD_GSM_APN:
+		parser_gsm_apn_start (parse_context, element_name, attribute_names, attribute_values);
+		break;
+	case PARSER_METHOD_CDMA:
+		break;
+	case PARSER_ERROR:
+		break;
+	case PARSER_DONE:
+		break;
+	}
+}
+
+static void
+parser_country_end (ParseContext *parse_context,
+                    const char *name)
+{
+	if (strcmp (name, "country") == 0) {
+		g_clear_pointer (&parse_context->text_buffer, g_free);
+		parse_context->state = PARSER_TOPLEVEL;
+	}
+}
+
+static void
+parser_provider_end (ParseContext *parse_context,
+                     const char *name)
+{
+	if (strcmp (name, "provider") == 0) {
+		g_clear_pointer (&parse_context->text_buffer, g_free);
+		parse_context->state = PARSER_COUNTRY;
+	}
+}
+
+static void
+parser_gsm_end (ParseContext *parse_context,
+                const char *name)
+{
+	if (strcmp (name, "gsm") == 0) {
+		g_clear_pointer (&parse_context->text_buffer, g_free);
+		parse_context->state = PARSER_PROVIDER;
+	}
+}
+
+static void
+parser_gsm_apn_end (ParseContext *parse_context,
+                    const char *name)
+{
+	if (strcmp (name, "username") == 0) {
+		g_clear_pointer (&parse_context->username, g_free);
+		parse_context->username = g_steal_pointer (&parse_context->text_buffer);
+	} else if (strcmp (name, "password") == 0) {
+		g_clear_pointer (&parse_context->password, g_free);
+		parse_context->password = g_steal_pointer (&parse_context->text_buffer);
+	} else if (strcmp (name, "dns") == 0) {
+		parse_context->dns = g_slist_prepend (parse_context->dns,
+		                                   g_steal_pointer (&parse_context->text_buffer));
+	} else if (strcmp (name, "gateway") == 0) {
+		g_clear_pointer (&parse_context->gateway, g_free);
+		parse_context->gateway = g_steal_pointer (&parse_context->text_buffer);
+	} else if (strcmp (name, "apn") == 0) {
+		g_clear_pointer (&parse_context->text_buffer, g_free);
+
+		if (parse_context->mccmnc_matched && parse_context->found_internet_apn)
+			parse_context->state = PARSER_DONE;
+		else
+			parse_context->state = PARSER_METHOD_GSM;
+
+	}
+}
+
+static void
+parser_cdma_end (ParseContext *parse_context,
+                 const char *name)
+{
+	if (strcmp (name, "cdma") == 0) {
+		g_clear_pointer (&parse_context->text_buffer, g_free);
+		parse_context->state = PARSER_PROVIDER;
+	}
+}
+
+static void
+parser_end_element (GMarkupParseContext *context,
+                           const char *element_name,
+                           gpointer user_data,
+                           GError **error)
+{
+	ParseContext *parse_context = user_data;
+
+	switch (parse_context->state) {
+	case PARSER_TOPLEVEL:
+		break;
+	case PARSER_COUNTRY:
+		parser_country_end (parse_context, element_name);
+		break;
+	case PARSER_PROVIDER:
+		parser_provider_end (parse_context, element_name);
+		break;
+	case PARSER_METHOD_GSM:
+		parser_gsm_end (parse_context, element_name);
+		break;
+	case PARSER_METHOD_GSM_APN:
+		parser_gsm_apn_end (parse_context, element_name);
+		break;
+	case PARSER_METHOD_CDMA:
+		parser_cdma_end (parse_context, element_name);
+		break;
+	case PARSER_ERROR:
+		break;
+	case PARSER_DONE:
+		break;
+	}
+}
+
+static void
+parser_text (GMarkupParseContext *context,
+             const char *text,
+             gsize text_len,
+             gpointer user_data,
+             GError **error)
+{
+	ParseContext *parse_context = user_data;
+
+	g_free (parse_context->text_buffer);
+	parse_context->text_buffer = g_strdup (text);
+}
+
+static const GMarkupParser parser = {
+	.start_element  = parser_start_element,
+	.end_element    = parser_end_element,
+	.text           = parser_text,
+	.passthrough    = NULL,
+	.error          = NULL,
+};
+
+/*****************************************************************************/
+
+static void
+finish_parse_context (ParseContext *parse_context, GError *error)
+{
+	if (parse_context->callback) {
+		if (error) {
+			parse_context->callback (NULL, NULL, NULL, NULL, NULL,
+			                         NULL, error,
+			                         parse_context->user_data);
+		} else {
+			parse_context->callback (parse_context->apn,
+			                         parse_context->username,
+			                         parse_context->password,
+			                         parse_context->gateway,
+			                         parse_context->auth_method,
+			                         parse_context->dns,
+			                         error,
+			                         parse_context->user_data);
+		}
+	}
+
+	g_free (parse_context->mccmnc);
+	g_markup_parse_context_free (parse_context->ctx);
+
+	g_free (parse_context->text_buffer);
+	g_free (parse_context->apn);
+	g_free (parse_context->username);
+	g_free (parse_context->password);
+	g_free (parse_context->gateway);
+	g_free (parse_context->auth_method);
+	g_slist_free_full (parse_context->dns, g_free);
+
+	g_slice_free (ParseContext, parse_context);
+}
+
+static void
+read_next_chunk (GInputStream *stream, ParseContext *parse_context);
+
+static void
+stream_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+	GInputStream *stream = G_INPUT_STREAM (source_object);
+	ParseContext *parse_context = user_data;
+	gssize len;
+	GError *error = NULL;
+
+	len = g_input_stream_read_finish (stream, res, &error);
+	if (len == -1) {
+		g_prefix_error (&error, "Error reading service provider database: ");
+		finish_parse_context (parse_context, error);
+		g_clear_error (&error);
+		return;
+	}
+
+	if (len == 0) {
+		g_set_error (&error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             "Operator ID '%s' not found in service provider database",
+		             parse_context->mccmnc);
+		finish_parse_context (parse_context, error);
+		g_clear_error (&error);
+		return;
+	}
+
+	if (!g_markup_parse_context_parse (parse_context->ctx, parse_context->buffer, len, &error)) {
+		g_prefix_error (&error, "Error parsing service provider database: ");
+		finish_parse_context (parse_context, error);
+		g_clear_error (&error);
+		return;
+	}
+
+	if (parse_context->state == PARSER_DONE) {
+		finish_parse_context (parse_context, NULL);
+		return;
+	}
+
+	read_next_chunk (stream, parse_context);
+}
+
+static void
+read_next_chunk (GInputStream *stream, ParseContext *parse_context)
+{
+	g_input_stream_read_async (stream,
+	                           parse_context->buffer,
+	                           sizeof (parse_context->buffer),
+	                           G_PRIORITY_DEFAULT,
+	                           parse_context->cancellable,
+	                           stream_read_cb,
+	                           parse_context);
+}
+
+static void
+file_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+	GFile *file = G_FILE (source_object);
+	ParseContext *parse_context = user_data;
+	GFileInputStream *stream;
+	gs_free_error GError *error = NULL;
+
+	stream = g_file_read_finish (file, res, &error);
+	if (!stream) {
+		g_prefix_error (&error, "Error opening service provider database: ");
+		finish_parse_context (parse_context, error);
+		return;
+	}
+
+	read_next_chunk (G_INPUT_STREAM (stream), parse_context);
+
+	g_object_unref (stream);
+}
+
+/*****************************************************************************/
+
+void
+nm_service_providers_find_gsm_apn (const char *service_providers,
+                                   const char *mccmnc,
+                                   GCancellable *cancellable,
+                                   NMServiceProvidersGsmApnCallback callback,
+                                   gpointer user_data)
+{
+	GFile *file;
+	ParseContext *parse_context;
+
+	parse_context = g_slice_new0 (ParseContext);
+	parse_context->mccmnc = g_strdup (mccmnc);
+	parse_context->cancellable = cancellable;
+	parse_context->callback = callback;
+	parse_context->user_data = user_data;
+	parse_context->ctx = g_markup_parse_context_new (&parser, 0, parse_context, NULL);
+
+	file = g_file_new_for_path (service_providers);
+
+	g_file_read_async (file, G_PRIORITY_DEFAULT, cancellable, file_read_cb, parse_context);
+
+	g_object_unref (file);
+}
diff --git a/src/devices/wwan/nm-service-providers.h b/src/devices/wwan/nm-service-providers.h
new file mode 100644
index 00000000..35ad2fc1
--- /dev/null
+++ b/src/devices/wwan/nm-service-providers.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_SERVICE_PROVIDERS_H__
+#define __NETWORKMANAGER_SERVICE_PROVIDERS_H__
+
+typedef void (*NMServiceProvidersGsmApnCallback) (const char *apn,
+                                                  const char *username,
+                                                  const char *password,
+                                                  const char *gateway,
+                                                  const char *auth_method,
+                                                  const GSList *dns,
+                                                  GError *error,
+                                                  gpointer user_data);
+
+void nm_service_providers_find_gsm_apn (const char *service_providers,
+                                        const char *mccmnc,
+                                        GCancellable *cancellable,
+                                        NMServiceProvidersGsmApnCallback callback,
+                                        gpointer user_data);
+
+#endif /* __NETWORKMANAGER_SERVICE_PROVIDERS_H__ */
diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/devices/wwan/nm-wwan-factory.c
index a0e5c160..9a85f936 100644
--- a/src/devices/wwan/nm-wwan-factory.c
+++ b/src/devices/wwan/nm-wwan-factory.c
@@ -1,19 +1,5 @@
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
+// SPDX-License-Identifier: GPL-2.0+
+/*
  * Copyright (C) 2014 Red Hat, Inc.
  */
 
@@ -77,11 +63,10 @@ modem_added_cb (NMModemManager *manager,
                 gpointer user_data)
 {
 	NMWwanFactory *self = NM_WWAN_FACTORY (user_data);
-	NMDevice *device;
+	gs_unref_object NMDevice *device = NULL;
 	const char *driver;
 
-	/* Do nothing if the modem was consumed by some other plugin */
-	if (nm_device_factory_emit_component_added (NM_DEVICE_FACTORY (self), G_OBJECT (modem)))
+	if (nm_modem_is_claimed (modem))
 		return;
 
 	driver = nm_modem_get_driver (modem);
@@ -90,17 +75,16 @@ modem_added_cb (NMModemManager *manager,
 	 * it.  The rfcomm port (and thus the modem) gets created automatically
 	 * by the Bluetooth code during the connection process.
 	 */
-	if (driver && strstr (driver, "bluetooth")) {
-		nm_log_info (LOGD_MB, "ignoring modem '%s' (no associated Bluetooth device)",
-		             nm_modem_get_control_port (modem));
+	if (   driver
+	    && strstr (driver, "bluetooth")) {
+		nm_log_dbg (LOGD_MB, "WWAN factory ignores bluetooth modem '%s' which should be handled by bluetooth plugin",
+		            nm_modem_get_control_port (modem));
 		return;
 	}
 
 	/* Make the new modem device */
 	device = nm_device_modem_new (modem);
-	g_assert (device);
 	g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device);
-	g_object_unref (device);
 }
 
 static NMDevice *
diff --git a/src/devices/wwan/tests/test-service-providers.c b/src/devices/wwan/tests/test-service-providers.c
new file mode 100644
index 00000000..33402cd5
--- /dev/null
+++ b/src/devices/wwan/tests/test-service-providers.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: LGPL-2.1+
+/*
+ * Copyright (C) 2019 Red Hat
+ */
+
+#include "nm-default.h"
+
+#include "nm-service-providers.h"
+
+#include "nm-test-utils-core.h"
+
+static void
+test_positive_cb (const char *apn,
+                  const char *username,
+                  const char *password,
+                  const char *gateway,
+                  const char *auth_method,
+                  const GSList *dns,
+                  GError *error,
+                  gpointer user_data)
+{
+	GMainLoop *loop = user_data;
+
+	g_main_loop_quit (loop);
+	g_assert_no_error (error);
+	g_assert_cmpstr (apn, ==, "gprs.example.com");
+	g_assert_cmpstr (username, ==, "praise");
+	g_assert_cmpstr (password, ==, "santa");
+	g_assert_cmpstr (gateway, ==, "192.0.2.3");
+	g_assert_cmpstr (auth_method, ==, "pap");
+
+	g_assert_nonnull (dns);
+	g_assert_cmpstr (dns->data, ==, "192.0.2.2");
+	dns = dns->next;
+	g_assert_nonnull (dns);
+	g_assert_cmpstr (dns->data, ==, "192.0.2.1");
+	g_assert_null (dns->next);
+}
+
+static void
+test_positive (void)
+{
+	GMainLoop *loop = g_main_loop_new (NULL, FALSE);
+
+	nm_service_providers_find_gsm_apn (NM_BUILD_SRCDIR"/src/devices/wwan/tests/test-service-providers.xml",
+	                                   "13337", NULL, test_positive_cb, loop);
+	g_main_loop_run (loop);
+	g_main_loop_unref (loop);
+}
+
+/*****************************************************************************/
+
+static void
+test_negative_cb (const char *apn,
+                  const char *username,
+                  const char *password,
+                  const char *gateway,
+                  const char *auth_method,
+                  const GSList *dns,
+                  GError *error,
+                  gpointer user_data)
+{
+	GMainLoop *loop = user_data;
+
+	g_main_loop_quit (loop);
+	g_assert_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN);
+}
+
+static void
+test_negative (void)
+{
+	GMainLoop *loop = g_main_loop_new (NULL, FALSE);
+
+	nm_service_providers_find_gsm_apn (NM_BUILD_SRCDIR"/src/devices/wwan/tests/test-service-providers.xml",
+	                                   "78130", NULL, test_negative_cb, loop);
+	g_main_loop_run (loop);
+	g_main_loop_unref (loop);
+}
+
+/*****************************************************************************/
+
+static void
+test_nonexistent_cb (const char *apn,
+                     const char *username,
+                     const char *password,
+                     const char *gateway,
+                     const char *auth_method,
+                     const GSList *dns,
+                     GError *error,
+                     gpointer user_data)
+{
+	GMainLoop *loop = user_data;
+
+	g_main_loop_quit (loop);
+	g_assert_error (error, G_IO_ERROR, G_IO_ERROR_AGAIN);
+}
+
+static void
+test_nonexistent (void)
+{
+	GMainLoop *loop = g_main_loop_new (NULL, FALSE);
+
+	nm_service_providers_find_gsm_apn ("nonexistent.xml", "13337", NULL,
+	                                   test_nonexistent_cb, loop);
+	g_main_loop_run (loop);
+	g_main_loop_unref (loop);
+}
+
+/*****************************************************************************/
+
+NMTST_DEFINE ();
+
+int
+main (int argc, char **argv)
+{
+	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
+
+	g_test_add_func ("/service-providers/positive", test_positive);
+	g_test_add_func ("/service-providers/negative", test_negative);
+	g_test_add_func ("/service-providers/nonexistent", test_nonexistent);
+
+	return g_test_run ();
+}
+
diff --git a/src/devices/wwan/tests/test-service-providers.xml b/src/devices/wwan/tests/test-service-providers.xml
new file mode 100644
index 00000000..f0ca2deb
--- /dev/null
+++ b/src/devices/wwan/tests/test-service-providers.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding='utf-8'?>
+<!DOCTYPE serviceproviders SYSTEM "serviceproviders.2.dtd">
+
+<serviceproviders format="2.0">
+
+<country code="feh">
+	<provider>
+		<name>Sophia</name>
+		<gsm>
+			<network-id mcc="666" mnc="999"/>
+			<apn value="access.example.com">
+				<plan type="postpaid"/>
+				<usage type="internet"/>
+				<name>APN</name>
+				<dns>192.0.2.1</dns>
+				<dns>192.0.2.2</dns>
+			</apn>
+		</gsm>
+	</provider>
+</country>
+
+<country code="meh">
+	<provider>
+		<name>Demiurge</name>
+		<gsm>
+			<network-id mcc="133" mnc="37"/>
+			<network-id mcc="133" mnc="666"/>
+			<apn value="mms">
+				<usage type="mms"/>
+				<name>Unsolicited Nudes MMS</name>
+				<username>mms</username>
+				<password>mms</password>
+				<mmsc>http://mms.example.com/</mmsc>
+				<mmsproxy>192.0.2.1:8080</mmsproxy>
+			</apn>
+			<apn value="gprs.example.com">
+				<plan type="postpaid"/>
+				<usage type="internet"/>
+				<name>GPRS</name>
+				<username>praise</username>
+				<password>santa</password>
+				<dns>192.0.2.1</dns>
+				<dns>192.0.2.2</dns>
+				<gateway>192.0.2.3</gateway>
+				<authentication method="pap"/>
+			</apn>
+			<apn value="second.example.com">
+				<plan type="postpaid"/>
+				<usage type="internet"/>
+				<name>Second</name>
+				<username>worship</username>
+				<password>doom</password>
+			</apn>
+		</gsm>
+	</provider>
+
+	<provider>
+		<name>Personal</name>
+		<gsm>
+			<network-id mcc="666" mnc="999"/>
+			<apn value="access.example.com">
+				<plan type="postpaid"/>
+				<usage type="internet"/>
+				<name>APN</name>
+				<dns>192.0.2.1</dns>
+				<dns>192.0.2.2</dns>
+			</apn>
+		</gsm>
+	</provider>
+</country>
+
+</serviceproviders>
+